標籤:ini buffer div 傳回值 統一 平台 關閉 exception 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日Google開發的操作系
統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/sspeex_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_encint 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 ;}elsereturn 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
Android 平台語音通話及迴音消除、噪音消除研究(轉)