博客地址:http://blog.csdn.net/kevindgkhtml
GitHub地址:https://github.com/KevinDGK/MyAudioDemojava
如今有個需求,在局域網內實現實時語音,傳輸層協議使用UDP協議,若是直接使用AudioRecord進行錄製音頻流併發送到另外一端進行播放,音質會很是差,並且斷斷續續,緣由以下:android
採樣頻率: fm = 44.1KHzgit
量化位數:16bitgithub
聲道配置:2(雙聲道)編程
那麼,碼率 V = 44.1K * 16 *2 = 1411.2 Kbps = 176.4KBps,即每秒傳輸速率大概176.4KB,api
若音頻幀時間爲20ms,每一個音頻數據包大小爲 size = 176.4KBps * 0.02s = 3.528KB,數組
通常狀況下,咱們每次讀取一個音頻幀的數據,能夠取整爲3600Byte,微信
因此 每秒大概發送 176.4/3.6=49 個數據包,每一個數據包大小爲3.6KB。網絡
若是再考慮到數據報頭,實測每秒發送約45個數據包,每秒傳輸速率大概180KB。
因爲通常都是使用手機鏈接Wifi,這就要求網絡質量和硬件設備必須很好,並且信道干擾較弱,而且連接的設備不能過多。只要稍微信號很差,就會致使丟包率特別高,並且延時十分大,根本沒法知足通訊的須要。在這種狀況下,咱們就須要進行語音壓縮、降噪等處理。
若是傳輸的僅僅是語音信息,那麼不須要很高的採樣頻率,可使用8KHz進行採樣,單通道便可。
private int DEFAULT_SAMPLERATEINHZ = 8000; // 採樣頻率 private int DEFAULT_AUDIOFORMAT = AudioFormat.ENCODING_PCM_16BIT; // 數據格式 private int DEFAULT_STREAMTYPE = AudioManager.STREAM_MUSIC; // 音頻類型 private int DEFAULT_CHANNELCONFIG_OUT = AudioFormat.CHANNEL_OUT_MONO; // 聲道配置 private int DEFAULT_MODE = AudioTrack.MODE_STREAM; // 輸出模式 private int DEFAULT_CHANNELCONFIG_IN = AudioFormat.CHANNEL_IN_MONO; // 聲道配置 private int DEFAULT_AUDIOSOURCE = MediaRecorder.AudioSource.MIC; // 音頻來源
Speex是一套主要針對語音的開源免費,無專利保護的音頻壓縮格式。Speex工程着力於經過提供一個能夠替代高性能語音編解碼來下降語音應用輸入門檻 。另外,相對於其它編解碼器,Speex也很適合網絡應用,在網絡應用上有着本身獨特的優點。同時,Speex仍是GNU工程的一部分,在改版的BSD協議中獲得了很好的支持。
Speex是基於CELP而且專門爲碼率在2-44kbps的語音壓縮而設計的。它的特色有:
因爲語音壓縮的底層代碼都是用C/C++寫的,因此對於咱們可憐的Android來講須要使用NDK進行JNI開發了,若是對這方面不瞭解的,能夠參考一下小編整理的 JNI(一) - Android Studio簡單開發流程 ,而後須要再瞭解一下C語言的一些基礎知識,就能夠進行簡單的JNI開發了,在本文的Demo中,小編也寫了大量的註解,但願可以幫到你們。
將代碼集成到本身的項目中的步驟:
第一步:將Demo的整個jni目錄複製到本身的main目錄下;
第二步:修改複製過來的jni文件夾中的speex_jni.cpp中的方法名
方法名爲Java _ 包名 _ 類名 _ 方法名(),中間使用單個下劃線鏈接,詳見demo;
第三步:添加gradle配置
將紅色框圈住的地方複製到本身的項目中便可。
第四步:編譯項目生成.so文件
選擇Build->Make Project,而後找到.so庫複製到本身的libs下便可:
第五步:在程序中使用
將Speex這個編解碼工具類複製到本身的項目中,就能夠正常使用了,具體使用方式詳見Demo。
private Speex speex; // Speex音頻編解碼器 speex = new Speex(); // 建立Speex編解碼實例 speex.open(4); speex.encode(recordData,0,encodedbytes,readNumber); // 語音壓縮-對音頻數據編碼 int decode = speex.decode(audioData, decodedShorts, audioData.length); // 對音頻數據解碼
採樣頻率: fm = 8KHz
量化位數:16bit
聲道配置:1
那麼,碼率 V = 8K * 16 * 1 = 128Kbps = 16KBps,即每秒傳輸速率大概16KB,
若音頻幀時間爲20ms,每幀音頻數據大小爲 size = 16KBps * 0.02s = 320KB,即160 Short,
設置壓縮質量爲4,每幀音頻數據壓縮完後只有20Byte,壓縮比爲 320:20,即 16:1,每秒發送1/0.02=50個數據包,即單單傳遞音頻數據佔用的帶寬爲 1 KBps,若是須要添加一些數據報頭,基本上也能維持在5KBps左右!
將176.4KBps變成16KBps,而後再壓縮成1KBps,好了,如今能夠知足基本的局域網內的語音傳輸需求了。若是帶上耳機的話,基本上可以很完美的語音通話了。若是不帶耳機,隨着通話,麥克風或者環境的回聲會愈來愈強,將會嚴重下降通話質量,咱們以後須要作的就是作回聲處理了。這個在以後的博客中專門介紹。
儘管Speex也十分的優秀,可是仍是被新的技術給踢下了寶座,連它的官網上都寫明瞭該技術已經被Opus給替代,而且宣稱Opus的各項性能都將比Speex優秀的多。接下來小編將爲你們初步介紹一下Opus的特色和API~
Opus是一個徹底開放的、免費的、多功能的音頻編解碼器。 它在交互式的語音和音樂在互聯網中的傳輸方面有着無與倫比的優點,可是一樣致力於存儲和流媒體應用程序。它是由互聯網工程任務組(IETF)制定的標準,標準格式爲RFC 6716,由Skype的SILK編解碼器和Xiph.Org的CELT編解碼器合併發展而來,號稱音頻編解碼器中的瑞士軍刀(來自官方視頻)。
Opus能夠處理普遍的音頻應用程序,包括IP電話、視頻會議、遊戲內聊天、甚至遠程現場音樂表演。從低比特率窄帶語音到很是高質量的立體聲音樂,它均可以適用。技術特色:
你能夠在RFC 6716標準中閱讀完整的規範,包括參考實現。也能夠在下載頁面得到一個最新的Opus 標準。
Libopus是Opus編解碼器的參考實現,咱們能夠參考該代碼進行開發。
爲了能在Firefox中支持Opus,Mozilla提供了專門的Opus工具。Opus-tools提供命令行程序來進行編碼、檢查和解碼.opus文件。在HTML中語音相關的可使用,原來不是Android的,須要用的本身去官方主頁下載便可。
小編翻譯到這裏尚未找到和Android相關的,藍瘦,香菇!
儘管Opus如今是由IETF指定標準,可是Opus的實現也會一直持續改進。固然,全部將來版本仍將徹底符合標準IETF規範。能夠在開發界面查看最新的開發版本信息。
小編曾說過,沒有對比,就沒有傷害。
下圖說明了不一樣的編解碼器的質量和比特率直接的函數關係。
narrowband - 窄頻
wideband - 寬頻
super-wideband - 超寬頻
fullband - 全頻段
fullband stereo - 全頻段立體聲
從圖上能夠看出,Opus的優點十分明顯。尤爲是較以前的Speex,具備更大的比特率範圍以及帶寬。
從圖中能夠看出,Opus在任何比特率下都具備較少的延遲。
Opus進行了幾回測試,可是下面僅僅列出基於比特流的幾個測試結果。儘管在Opus發佈的時候應該給出一個比較好的質量標準化的意見,可是咱們但願更新的和更高級的編碼器將達到更好的質量。
截止到目前位置,最新的版本是1.13穩定發行版,因此在這裏翻譯的都是該版本的API。
Opus 編碼器
- typedef struct OpusEncoder OpusEncoder Opus編碼器,包含了編碼器的所有狀態。
- int opus_encoder_get_size (int channels) 獲取一個OpusEncoder編碼器的大小。 - OpusEncoder * opus_encoder_create (opus_int32 Fs, int channels, int application, int *error) 分配和初始化一個編碼器狀態。 - int opus_encoder_init (OpusEncoder *st, opus_int32 Fs, int channels, int application) 初始化以前分配的編碼器指針st指向的內存中的編碼器,而且必須經過使用opus_encoder_get_size()方法來返回最小內存大小。 - opus_int32 opus_encode (OpusEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes) 編碼。 - opus_int32 opus_encode_float (OpusEncoder *st, const float *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes) 編碼音頻流。 - void opus_encoder_destroy (OpusEncoder *st) 釋放一個經過opus_encoder_create()分配的OpusEncoder。 - int opus_encoder_ctl (OpusEncoder *st, int request,...) 在Opus編碼器中執行CTL函數。
經過官方文檔解釋和C語言基礎,咱們知道OpusEncoder *enc 表示一個Opus編碼器結構體的指針,指向該編碼器的內存,該結構體內部包含了該編碼器的所有狀態。C語言中是沒有類和對象的概念的,可是有結構體,能夠用來模擬Java中的類,因此結構體的實例也就能夠比做對象。從如今開始,我就成稱enc爲該編碼器對象(的指針),這樣說比較習慣。
由於Opus是有狀態編碼,編碼過程始於建立一個編碼器狀態:
int error; OpusEncoder *enc; enc = opus_encoder_create(Fs, channels, application, &error); // 建立
從這一點開來,enc能夠用來編碼一個音頻流。一個編碼器在同一時刻僅僅只能用於一個音頻流編碼,而且對於每一種音頻格式初始化的編碼器狀態,不能夠再次初始化。
當執行 opus_encoder_create() 爲編碼器分配了內存以後,就能夠初始化這個預分配的內存:
int size; int error; OpusEncoder *enc; size = opus_encoder_get_size(channels); // 獲取須要的最小內存 enc = malloc(size); // 分配內存 error = opus_encoder_init(enc, Fs, channels, application); // 初始化內存
opus_encoder_get_size() 返回編碼器對象所須要的內存大小,注意,該代碼在將來的版本中可能會變成改變內存大小,因此不要根據這個代碼有任何假定,即不要根據獲取內存大小這個方法來有一些邏輯上的處理,由於以後的版本變更有可能會影響你的代碼。
編碼器的狀態會一直保存在內存中,而且只有淺複製纔能有效的複製該狀態,例如:memcpy()。
使用 opus_encoder_ctl() 接口能夠改變一些編碼器的設置。全部的設置已經被默認設置成推薦值,因此只有在必要的時候纔去改變它們。最多見的想要改變的設置以下:
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type));
查看CTLS相關編碼來獲取完整的設置參數列表,大部分參數能夠在一個音頻流的任什麼時候候設置和改變。
爲了對一幀數據編碼, opus_encode() 或者 opus_encode_float() 在調用的時候必須使用的是剛好的一幀(2.5,5,10,20,40,60毫秒)音頻數據。
len = opus_encode(enc, audio_frame, frame_size, packet, max_packet);
opus_encode() 和 opus_encode_float() 返回實際接入到packet中的編碼後的音頻數據的字節數。返回值也許是無效的,代表編碼錯誤。若是返回值小於2字節或者更小,那麼這個packet不須要發送出去。
一旦這個編碼器對象不須要,能夠銷燬掉:
opus_encoder_destroy(enc);
若是編碼器對象是使用opus_encoder_init() 而不是 opus_encoder_create() 方法建立的,那麼除了可能須要釋放咱們手動分配的內存以外,不須要其餘的動做。
typedef struct OpusEncoder OpusEncoder 編碼器結構體,包含了一個編碼器全部的狀態。它是位置獨立的,能夠被隨意的複製。
- opus_int32 opus_encode( OpusEncoder∗ st, const opus_int16∗ pcm, int frame_size, unsigned char∗ data, opus_int32 max_data_bytes)
參數:
參數 | 參數類型 | 入參或出參 | 解釋 |
---|---|---|---|
st | OpusEncoder∗ | in | 編碼器對象 |
pcm | const opus_int16* | in | 輸入信號(雙聲道則爲交錯模式),長度爲frame_size × channels × sizeof(opus_int16),即採樣個數 × 聲道數 × 16。 |
frame_size | int | in | 輸入的音頻信號的每一個聲道的採樣數量,這必定是一個Opus框架編碼器採樣率的大小。例如,當採樣率爲48KHz的時候,採樣數量容許的數值爲120、240、480、960、1920和2880。傳遞一個持續時間少於10 ms的音頻數據(480個樣本48 kHz),編碼器將不會使用LPC或混合模式。 |
data | unsigned char∗ | out | 輸出編碼結果,至少包含max_data_bytes個字節數。 |
max_data_bytes | opus_int32 | in | 爲了輸出編碼結果分配的內存,它可能用於控制一個即時比特率的上線,可是不該該做爲惟一的比特率控制。 |
關於採樣率和採樣數量的關係,上文已經提到過,opus_encode() 或者 opus_encode_float() 在調用的時候必須使用的是剛好的一幀(2.5,5,10,20,40,60毫秒)音頻數據,若是採樣頻率爲48KHz,那麼:
∵ 採樣頻率 Fm = 48KHz
∴ 採樣間隔 T = 1/Fm = 1/48000 s = 1/48 ms
∴ 當T0 = 2.5 ms 時,N = T0/T = 2.5/(1/48) = 120,
當T0 = 5.0 ms 時,N = T0/T = 2.5/(1/48) = 240,
當T0 = 10 ms 時,N = T0/T = 2.5/(1/48) = 480,
當T0 = 20 ms 時,N = T0/T = 2.5/(1/48) = 960,
當T0 = 40 ms 時,N = T0/T = 2.5/(1/48) = 1920,
當T0 = 60 ms 時,N = T0/T = 2.5/(1/48) = 2880,
即,當Fm = 48KHz時:
採樣時間(ms) | 2.5 | 5 | 10 | 20 | 40 | 60 |
---|---|---|---|---|---|---|
採樣個數 | 120 | 240 | 480 | 960 | 1920 | 2880 |
- opus_int32 opus_encode_float( OpusEncoder∗ st, const float∗ pcm, int frame_size, unsigned char∗ data, opus_int32 max_data_bytes)
實在是翻譯不動了。。。
其實有了上面的介紹,看官們應該能有個大概的認知,下面貼出一箇中文版的翻譯文檔,你們本身瀏覽,由於目前我也差很少看了通常,因此小編對該文檔的後面章節正確性與否並不負責~
若是對比一下Speex和Opus,會發現從集成和使用的角度來看,十分的類似,API的時候方式也差很少,只不過Opus的功能更加的強大,API也更加豐富,可是也變相的增長了咱們的開發難度,須要對C語言有至關好的功底,才能夠定製實現本身的需求。
編程之路,任重而道遠。先是爲了生活而編程,後是爲了超越而編程!