Android 平台語音通話及回音消除、噪音消除研究(轉)


一 Android操作系統由來

Android是一種基於Linux的自由及開放源代碼的操作系統,主要使用於移動設備,如智能手機和平
板電腦,由Google公司和開放手機聯盟領導及開發。尚未有統一中文名稱,中國大陸地區較多人使用“安
卓”或“安致”。Android操作系統最初由Andy Rubin開發,主要支持手機。2005年8月由Google收購注資。
2007年11月,Google與84家硬件制造商、軟件開發商及電信營運商組建開放手機聯盟共同研發改良Androi
d系統。隨后Google以Apache開源許可證的授權方式,發布了Android的源代碼。第一部Android智能手機發
布於2008年10月。Android逐漸擴展到平板電腦及其他領域上,如電視、數碼相機、游戲機等。2011年第
一季度,Android在全球的市場份額首次超過塞班系統,躍居全球第一。 2012年11月數據顯示,Android
占據全球智能手機操作系統市場76%的份額,中國市場占有率為90%。2013年09月24日谷歌開發的操作系
統Android在迎來了5歲生日,全世界采用這款系統的設備數量已經達到10億台。

二 Android平台語音通訊

正因為Android平台優越的性能、美觀的界面,越來越多人使用Android手機,從而在Android平台上的
語音通話越來越多。語音通話大概流程如下:我認為一個語音通話系統至少有四個模塊。分別是PCM(Pulse
Code Modulation,即 脈碼編碼調制)語音采集,編解碼,網絡傳輸以及語音播放。如果算上UI交互的話,
就是五個模塊了。整體流程大概是:A打電話給B,A聲音通過MIC被采集成PCM原始數據,然后經過編碼壓縮,
再通過網絡(建立P2P連接)將編碼后的數據傳輸出去;B端通過網絡收到數據后進行解碼處理,然后調用播
放模塊,進行播放數據。如果想通話音質提供些,可以在編碼前加入 噪音消除,回音消除。

三 錄音、放音、編碼、解碼、網絡發送、接收

1、語音采集模塊
 Android平台上的實現是通過AudioRecord接口來實現PCM數據的采集,這一步比較容易的。但需要注意的是
AudioRecord接口的使用方法。構造AudioRecord 實例需要參數 public AudioRecord (int audioSource, int
sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
比如錄音代碼如下:

    static final int frequency = 8000; static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; int recBufSize,playBufSize; AudioRecord audioRecord; recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, recBufSize); 
2、語音播放

當語音數據采集好了之后,接着可以實現語音播放模塊。Android上實現PCM數據的播放也很簡單,直接
使用AudioTrack這個接口就行了。同樣需要注意該接口
的使用方法。AudioTrack的構造方式跟AudioRecord是對應的

    static final int frequency = 8000; static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; int recBufSize,playBufSize; AudioTrack audioPlayer ; playBufSize = AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); audioPlayer = new AudioTrack(AudioManager.STREAM_MUSIC,frequency,AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,playBufSize, AudioTrack.MODE_STREAM) ; 
3、語音編解碼

采集到的PCM數據是原始的語音數據,如果我們直接進行網絡傳輸,那是不可取的。因此,要進行打包編碼。
編碼我們需要第三方的庫,目前我使用的庫是speex(http://www.speex.org)。我看到許多SIP語音電話都
使用到了這個庫進行編解碼。當然也有對這個庫評 價不好的說法,但我覺得作為學習還是可取的,因為speex
使用起來很方便。把speex源碼下載下來,寫好JNI接口,在NDK環境編譯一下,即可在java環境調用。
例如下面一個接口函數

jint Java_com_audiocodec_talkdemo_AudioCodec_InitAudioEncodec( JNIEnv* env, jobject thiz,jint sampling_rate,jint audioLevel) { if(nInitAudioCodecEncodeFlag == 1 || audioLevel < 3 || audioLevel > 8 ) return 0 ; int frame_size ; if(sampling_rate == 8000) { audio_Leval = 0 ; capAudioLength = 160 ; capAudioBitrate = 8000 ; }else if(sampling_rate == 16000) { audio_Leval = 1 ; capAudioLength = 320 ; capAudioBitrate = 16000 ; }else if(sampling_rate == 32000) { audio_Leval = 2 ; capAudioLength = 640 ; capAudioBitrate = 32000 ; }else return 0 ; tmp_Level = audioLevel ; //設置等級 15kbit/s speex_mode = speex_lib_get_mode(audio_Leval) ; enc_state = speex_encoder_init(speex_mode); speex_encoder_ctl(enc_state,SPEEX_SET_QUALITY,&tmp_Level); int tmp = 30 ;//丟包補償 int nRet = speex_encoder_ctl(enc_state, SPEEX_SET_PLC_TUNING, &tmp); nRet = speex_encoder_ctl(enc_state, SPEEX_GET_PLC_TUNING, &tmp); speex_bits_init(&bits); nInitAudioCodecEncodeFlag = 1 ; return 1 ; } //編碼音頻數據 /* 參數 jbyteArray szAudio 等待編碼的音頻數據 jbyteArray szOut 編碼后的音頻數據 返回值 成功返回 編碼后長度 失敗返回 0 */ jint Java_com_audiocodec_talkdemo_AudioCodec_AudioEncode( JNIEnv* env, jobject thiz,jbyteArray szAudio,jbyteArray szOut) { if(nInitAudioCodecEncodeFlag == 0) return 0 ; jbyte* szAudioBuffer = (jbyte *)(*env)->GetByteArrayElements(env,szAudio, 0); jbyte* szOutBuffer = (jbyte *)(*env)->GetByteArrayElements(env,szOut, 0); //清空bits ,以便編碼 speex_bits_reset(&bits); //進行編碼 int nRet = speex_encode_int(enc_state,(spx_int16_t*)szAudioBuffer, &bits); //把編碼后的bits 結構,拷貝到cbits_enc的數據可以從網絡發送出去,長度為nByte_enc int nByte_enc = speex_bits_write(&bits, szOutBuffer, 200); (*env)->ReleaseByteArrayElements(env,szAudio,szAudioBuffer,0) ; (*env)->ReleaseByteArrayElements(env,szOut,szOutBuffer,0) ; return nByte_enc ; } /* 函數功能 初始化編碼器 參數 無參數 返回值 成功返回 1 失敗返回 0 */ jint Java_com_audiocodec_talkdemo_AudioCodec_ExitAudioEncodec( JNIEnv* env, jobject thiz) { if(nInitAudioCodecEncodeFlag == 1) { nInitAudioCodecEncodeFlag = 0 ; //銷毀資源 speex_bits_destroy(&bits); speex_encoder_destroy(enc_state); enc_state = NULL ; }else return 0 ; } 
4 網絡發送、接收
   //定義 DatagramSocket udpSocket ; //生成 try { udpSocket = new DatagramSocket(6789); } catch (SocketException e1) { e1.printStackTrace(); } //發送 try { udpSocket.send(sendPacket) ; } catch (IOException e) { e.printStackTrace(); } //接收 udpSocket.receive(udpPackage); //關閉 udpSocket.close() ; 

四、 回音消除

    從Speex 的介紹可以看出它提供了噪音消除,回音消除,測試比較過噪音消除這功能效果是非
 常棒的,回音消除這功能也很不錯這一功能,現在開源的,比較完善的回音消除模塊就是Speex了
 ,有許多中小公司也拿它作為回音消除功能 。經過測試,Speex的消除效果還是不錯的。
 編寫個jni文件,NDK 環境編譯一下即可得到so 文件,在Android環境中調用即可。
      //初始化回音消除參數 /* * jint frame_size 幀長 一般都是 80,160,320 * jint filter_length 尾長 一般都是 80*25 ,160*25 ,320*25 * jint sampling_rate 采樣頻率 一般都是 8000,16000,32000 * 比如初始化 * InitAudioAEC(80, 80*25,8000) //8K,10毫秒采樣一次 * InitAudioAEC(160,160*25,16000) //16K,10毫秒采樣一次 * InitAudioAEC(320,320*25,32000) //32K,10毫秒采樣一次 */ jint Java_com_audioaec_talkdemo_AudioAEC_InitAudioAEC( JNIEnv* env,jobject thiz, jint frame_size,jint filter_length,jint sampling_rate) { if(nInitSuccessFlag == 1) return 1 ; m_nFrameSize = frame_size; m_nFilterLen = filter_length; m_nSampleRate = sampling_rate; //計算采樣時長,即是10毫秒,還是20毫秒,還是30毫秒 nSampleTimeLong = (frame_size / (sampling_rate / 100)) * 10 ; m_pState = speex_echo_state_init(m_nFrameSize, m_nFilterLen); if(m_pState == NULL) return -1 ; m_pPreprocessorState = speex_preprocess_state_init(m_nFrameSize, m_nSampleRate); if(m_pPreprocessorState == NULL) return -2 ; iArg = m_nSampleRate; speex_echo_ctl(m_pState, SPEEX_SET_SAMPLING_RATE, &iArg); speex_preprocess_ctl(m_pPreprocessorState, SPEEX_PREPROCESS_SET_ECHO_STATE, m_pState); nInitSuccessFlag = 1 ; return 1 ; } /* 參數: jbyteArray recordArray 錄音數據 jbyteArray playArray 放音數據 jbyteArray szOutArray */ jint Java_com_audioaec_talkdemo_AudioAEC_AudioAECProc(JNIEnv* env,jobject thiz, jbyteArray recordArray,jbyteArray playArray,jbyteArray szOutArray ) { if(nInitSuccessFlag == 0) return 0 ; jbyte* recordBuffer = (jbyte *)(*env)->GetByteArrayElements(env,recordArray, 0); jbyte* playBuffer = (jbyte *)(*env)->GetByteArrayElements(env,playArray, 0); jbyte* szOutBuffer = (jbyte *)(*env)->GetByteArrayElements(env,szOutArray, 0); speex_echo_cancellation(m_pState,(spx_int16_t *)recordBuffer, (spx_int16_t *)playBuffer,(spx_int16_t *)szOutBuffer); int flag=speex_preprocess_run(m_pPreprocessorState,(spx_int16_t *)szOutBuffer); (*env)->ReleaseByteArrayElements(env,recordArray,recordBuffer,0) ; (*env)->ReleaseByteArrayElements(env,playArray,playBuffer,0) ; (*env)->ReleaseByteArrayElements(env,szOutArray,szOutBuffer,0) ; return 1 ; } //退出 jint Java_com_sosea_xmeeting_SpeexAEC_ExitSpeexDsp( JNIEnv* env,jobject thiz) { if(nInitSuccessFlag == 0) return 0 ; if (m_pState != NULL) { speex_echo_state_destroy(m_pState); m_pState = NULL; } if (m_pPreprocessorState != NULL) { speex_preprocess_state_destroy(m_pPreprocessorState); m_pPreprocessorState = NULL; } nInitSuccessFlag = 0 ; return 1 ; } 

五 、 噪音消除處理

// 初始化 降噪 Java_com_audioaec_talkdemo_AudioAEC_InitAudioDeNose( JNIEnv* env, jobject thiz) { int denoise_enabled = 1 ; if(nInitDeNoseFlag == 1) return 0 ; nInitDeNoseFlag = 1 ; //8K降噪 audioProcNose8K = speex_preprocess_state_init(80 * (nSampleTimeLong / 10),8000); speex_preprocess_ctl(audioProcNose8K, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled); //16K降噪 audioProcNose16K = speex_preprocess_state_init(160 * (nSampleTimeLong / 10),16000); speex_preprocess_ctl(audioProcNose16K, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled); return 1 ; } //8K降噪 jint Java_com_audioaec_talkdemo_AudioAEC_AudioDeNose8K(JNIEnv* env,jobject thiz,jbyteArray recordArray) { if(nInitDeNoseFlag == 0) return 0 ; jbyte* recordBuffer = (jbyte *)(*env)->GetByteArrayElements(env,recordArray, 0); speex_preprocess(audioProcNose8K,(spx_int16_t*)recordBuffer, NULL); (*env)->ReleaseByteArrayElements(env,recordArray,recordBuffer,0) ; return 1 ; } //16K降噪 jint Java_com_audioaec_talkdemo_AudioAEC_AudioDeNose16K(JNIEnv* env,jobject thiz,jbyteArray recordArray) { if(nInitDeNoseFlag == 0) return 0 ; jbyte* recordBuffer = (jbyte *)(*env)->GetByteArrayElements(env,recordArray, 0); speex_preprocess(audioProcNose16K,(spx_int16_t*)recordBuffer, NULL); (*env)->ReleaseByteArrayElements(env,recordArray,recordBuffer,0) ; return 1 ; } // 釋放降噪 jint Java_com_audioaec_talkdemo_AudioAEC_ExitAudioDeNose( JNIEnv* env, jobject thiz) { if(nInitDeNoseFlag == 0) return 0 ; nInitDeNoseFlag = 0 ; speex_preprocess_state_destroy(audioProcNose8K); speex_preprocess_state_destroy(audioProcNose16K); return 1 ; } 
https://www.jianshu.com/p/e74700dd07cf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM