RTMP語音通訊

前言

最近在搞即時語音聊天的功能,經過幾個月的努力,目前基本穩定,達到了可以上線的標準,在此寫篇博客記錄下。

客戶端採用rtmp協議做推流和拉流,在網絡穩定的情況下,實測延遲在200ms~500ms。

RTMP/RTSP協議說明

RTMP僅支持TCP協議、RTSP支持TCP和UDP兩種協議。針對推流端,不管使用RTMP還是RTSP都需要使用TCP協議,以保證源頭數據的正確性。拉流端如果使用RTSP的話,可以使用UDP協議。

使用RTMP作爲拉流端的問題

在網絡狀態變化的時候,可能導致拉流端聲音來不及消費,使緩存區數據過多,從而使延遲變得嚴重。解決辦法即:需要判斷緩存區已緩存的大小,計算出需要消費的時間,根據自己的需要,在超出預期的時候,斷開重連。RTMP只支持TCP協議,如果不斷開重連,數據不會丟失,必定要消費掉,延遲沒法解決。

聲音錄製

初始化AudioRecord

int bufferSize = AudioRecord.getMinBufferSize(sampleRate,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, sampleRate,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);

開始錄製

audioRecord.startRecording();

獲取音頻流

int bufferReadResult;
byte[] data = new byte[bufferSize];
while (isRecording() && (bufferReadResult = audioRecord.read(data, 0, bufferSize)) > 0) {
    listener.onAudioRecorded(data, bufferReadResult);
}
boolean isRecording() {
    return audioRecord != null
        && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;
}

停止錄製

audioRecord.release();

音頻參數

從初始化AudioRecord中可以看出,我們需要掌握的概念知識有4個: 
採樣率(sampleRate) 
聲道(AudioFormat.CHANNEL_IN_MONO) 
採樣位數(AudioFormat.ENCODING_PCM_16BIT) 
聲源類型(MediaRecorder.AudioSource.VOICE_COMMUNICATION)。

採集

採樣率

private int sampleRate = 44100;

錄音設備在一秒鐘內對聲音信號的採樣次數,採樣頻率越高聲音的還原就越真實越自然。在當今的主流採集卡上,採樣頻率一般共分爲11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五個等級,11025Hz能達到AM調幅廣播的聲音品質,而22050Hz和24000HZ能達到FM調頻廣播的聲音品質,44100Hz則是理論上的CD音質界限,48000Hz則更加精確一些。

聲道

常有單聲道和立體聲之分,單聲道的聲音只能使用一個喇叭發聲(有的也處理成兩個喇叭輸出同一個聲道的聲音),立體聲可以使兩個喇叭都發聲(一般左右聲道有分工) ,更能感受到空間效果,當然還有更多的通道數。

CHANNEL_IN_DEFAULT = 1;
CHANNEL_IN_LEFT = 0x4;
CHANNEL_IN_RIGHT = 0x8;
CHANNEL_IN_FRONT = 0x10;
CHANNEL_IN_BACK = 0x20;
CHANNEL_IN_LEFT_PROCESSED = 0x40;
CHANNEL_IN_RIGHT_PROCESSED = 0x80;
CHANNEL_IN_FRONT_PROCESSED = 0x100;
CHANNEL_IN_BACK_PROCESSED = 0x200;
CHANNEL_IN_PRESSURE = 0x400;
CHANNEL_IN_X_AXIS = 0x800;
CHANNEL_IN_Y_AXIS = 0x1000;
CHANNEL_IN_Z_AXIS = 0x2000;
CHANNEL_IN_VOICE_UPLINK = 0x4000;
CHANNEL_IN_VOICE_DNLINK = 0x8000;
CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
CHANNEL_IN_FRONT_BACK = CHANNEL_IN_FRONT | CHANNEL_IN_BACK;

通常使用CHANNEL_IN_MONO作爲單聲道,CHANNEL_IN_STEREO作爲雙聲道。

採樣位數

即採樣值或取樣值(就是將採樣樣本幅度量化)。它是用來衡量聲音波動變化的一個參數,也可以說是聲卡的分辨率。它的數值越大,分辨率也就越高,所發出聲音越精細。 
每個採樣數據記錄的是振幅, 採樣精度取決於採樣位數的大小: 
1 字節(也就是8bit) 只能記錄 256 個數, 也就是隻能將振幅劃分成 256 個等級; 
2 字節(也就是16bit) 可以細到 65536 個數, 這已是 CD 標準了;

ENCODING_INVALID = 0;
ENCODING_DEFAULT = 1;

ENCODING_PCM_16BIT = 2;
ENCODING_PCM_8BIT = 3;
ENCODING_PCM_FLOAT = 4;

ENCODING_AC3 = 5;
ENCODING_E_AC3 = 6;

ENCODING_DTS = 7;
ENCODING_DTS_HD = 8;

ENCODING_MP3 = 9;

ENCODING_AAC_LC = 10;
ENCODING_AAC_HE_V1 = 11;
ENCODING_AAC_HE_V2 = 12;

通常使用PCM的16BIT作爲採樣位數,能滿足基本需求。

音頻源類型

這裏主要針對手機或平板,根據應用使用的場景,提供多種音頻源類型以供選擇。使用音頻源的場景和手機的麥克風亦有些相同的目的,所以我們先了解下手機的麥克風:

* 麥克風背景 *

早些年的手機都是一個麥克風,一般都在我們的手機下端充電口附近,但是一個麥克風通話時候容易有噪音,影響我們的通話質量。 
但是最近這幾年,由於電子行業的迅速崛起和競爭,手機制造商們也是想盡了辦法來製造更高品質的手機,麥克風已經加到了兩個,大家可以看下自己的手機聽筒的上方或者頂部是不是也有一個小孔,那麼這個孔就是第二個麥克風的位置,一般我們打電話的時候都用的是手機下方的麥克風,當我們打開免提或者視頻通話的時候用的就是上方的麥克,以後對方聽不見可不要傻傻的對着下方的麥克風大聲講話了。 
還有iphone手機爲了通話質量的提升竟然做了三個麥克風,第三個一般都在後置攝像頭閃光燈附近,一般都是錄音用,這麼多麥克風,各有各的功能,通話降噪方面起到了不小的作用! 
通常情況下屏幕底部的小孔是主麥克風,屏幕頂部的麥克風是降噪麥克風。因爲主麥克風主要用於拾取通話聲音,所以靠近人體的嘴巴位置,而降噪麥克風則需要與主麥克風保持一定的距離,通常放在屏幕的頂部,遠離嘴巴位置。通話時,人體發音源嘴巴與主麥克風、降噪麥克風的距離不同,也就導致兩個麥克風接收到的人聲強度有所不同(大約相差6dB)。另一方面,兩個麥克風接收到的環境噪音強度近似相同。降噪麥克的信號經過手機內部做聲音相位反相處理就可以和主麥克風抵消環境噪音帶來的影響,保證通話質量。一般經過雙MIC降噪技術的處理即使身處在鬧市之中通話質量依然槓槓滴。相反,如果沒有這個技術處理的話傳輸出去的聲音就會很嘈雜通話質量自然就會大大的降低。

* 安卓手機提供流如下聲源類型供選擇(真實如何,同時需要考慮各廠家手機是否支持,以及其硬件特殊性和處理方法): *

// 默認的音頻源
DEFAULT
// 手機默認音頻源
MIC
// 語音呼叫上行(TX)音頻源。(需要權限,且權限只有系統app獲取到)
VOICE_UPLINK
// 語音呼叫下行鏈路(RX)音頻源。(需要權限,且權限只有系統app獲取到)
VOICE_UPLINK
// 語音通話上行+下行音頻源 (需要權限,且權限只有系統app獲取到)
VOICE_CALL
// 麥克風音頻源可調諧爲視頻錄製,與相機的方向相同(如果可用)
CAMCORDER
// 爲語音識別調諧的麥克風音頻源
VOICE_RECOGNITION
// 爲語音通信(如VoIP)調諧的麥克風音頻源。例如,它將利用回聲抵消或自動增益控制(如果可用)
VOICE_COMMUNICATION
// 用於遠程呈現音頻流的亞文本的音頻源。應用程序可以使用此音頻源捕獲音頻流的混合,應該傳輸到遠程接收器,如WiFi顯示器。當錄音處於活動狀態時,這些音頻流被重定向到遠程提交,而不是在設備揚聲器或耳機上播放。(需要權限,且權限只有系統app獲取到)
REMOTE_SUBMIX
// 爲未處理(原始)聲音調諧的麥克風音頻源(如果可用)的行爲類似於{@Link #DEFAULT}
UNPROCESSED
// 用於捕獲廣播無線電調諧器輸出的音頻源。(隱藏方法,只供底層調用)
RADIO_TUNER
// 用於可搶佔、低優先級軟件熱詞檢測的音頻源它提供與{@link語音識別}相同的增益和預處理調諧。應用程序應該在希望使用此音頻源時使用此音頻源,始終打開軟件Hotword檢測,同時優雅地向任何其他應用程序屈服。可能想從麥克風裏讀出來。
(隱藏方法,只供底層調用,且需要權限)
HOTWORD

排除不可用的,可以供手機使用的也只有DEFAULT、MIC、CAMCORDER、VOICE_RECOGNITION、VOICE_COMMUNICATION。一般默認使用MIC,錄製視頻使用CAMCORDER,語音識別使用VOICE_RECOGNITION,自動降噪使用VOICE_COMMUNICATION。

PS:在手機有上下兩個麥克風的情況下,使用VOICE_COMMUNICATION,聲源會使用上方的麥克風,使用MIC則使用下方的麥克風。

錄音時屏蔽揚聲器

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setSpeakerphoneOn(false);
// 判斷是否開啓揚聲器
boolean isOpenSpeaker = audioManager.isSpeakerphoneOn();

設置播放聲音類型

在很多應用、遊戲中,明明播放的是媒體音樂,調整音量的時候發現調的是通話聲音,怎麼做到的?請看下面:

// 摘自博客 https://www.cnblogs.com/loveflycforever/p/4881945.html
// 獲取AudioManager實例對象
AudioManager audioManage = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// 獲取最大音量和當前音量,參數:STREAM_VOICE_CALL(通話)、STREAM_SYSTEM(系統聲音)、STREAM_RING(鈴聲)、STREAM_MUSIC(音樂)和STREAM_ALARM(鬧鈴)
int max = audioManager.getStreamMaxVolume(int streamType);
int current = audioManager.getStreamVolume(int streamType);
// 獲取當前的鈴聲模式,返回值:RINGER_MODE_NORMAL(普通)、RINGER_MODE_SILENT(靜音)或者RINGER_MODE_VIBRATE(震動)
int rMode = audioManager.getRingerMode();
// 獲取當前音頻模式,返回值:MODE_NORMAL(普通)、MODE_RINGTONE(鈴聲)、MODE_IN_CALL(呼叫)或者MODE_IN_COMMUNICATION(通話)
int mode = audioManager.getMode();

// 設置音量大小,第一個參數:STREAM_VOICE_CALL(通話)、STREAM_SYSTEM(系統聲音)、STREAM_RING(鈴聲)、STREAM_MUSIC(音樂)和STREAM_ALARM(鬧鈴);第二個參數:音量值,取值範圍爲0-7;第三個參數:可選標誌位,用於顯示出音量調節UI(AudioManager.FLAG_SHOW_UI)。
audioManager.setStreamVolume(int streamType, int index, int flags);
// 設置鈴聲模式,參數:RINGER_MODE_NORMAL(普通)、RINGER_MODE_SILENT(靜音)或者RINGER_MODE_VIBRATE(震動)
audioManager.getRingerMode(int ringerMode);
// 設置音頻模式,參數:MODE_NORMAL(普通)、MODE_RINGTONE(鈴聲)、MODE_IN_CALL(呼叫)或者MODE_IN_COMMUNICATION(通話)
audioManager.setMode(int mode);
// 設置靜音/取消靜音,第二個參數:請求靜音狀態,true(靜音)false(取消靜音)
audioManager.setStreamMute (int streamType, boolean state);

// 調節手機音量大小,第二個參數:調整音量的方向,可取ADJUST_LOWER(降低)、ADJUST_RAISE(升高)、ADJUST_SAME(不變)。
audioManager.adjustStreamVolume(int streamType, int direction, int flags);

以上說明了各種聲音類型的設置和獲取。 
但在使用是依然還有些需要注意的地方: 
1. setMode(),普通app只能設置MODE_NORMAL,MODE_RINGTONE,MODE_IN_COMMUNICATION三個,且需要在AndroidManifest裏面添加 android.permission.MODIFY_AUDIO_SETTINGS 權限。MODE_IN_CALL只有系統app可以設置。 
2. setMode()設置爲MODE_NORMAL、MODE_RINGTONE調節的是媒體音量;設置爲MODE_IN_COMMUNICATION之後,調節的則爲通話音量。注意這裏的調節僅僅是物理按鍵調節的時候,調節的音頻類型的音量。比如說:當前正在播放的使用的音頻規定好使用的是媒體音量,而又手動設置setMode爲MODE_IN_COMMUNICATION,那麼按物理音量鍵調節的是通話音量,這對正在播放的音頻並沒有作用。所以這裏需要注意匹配,不要給用戶帶來困擾。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章