当前位置:   article > 正文

Android 平台语音通话及回音消除、噪音消除研究_android speex啸叫处理

android speex啸叫处理


一 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)
比如录音代码如下:

  1. static final int frequency = 8000;
  2. static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
  3. static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
  4. int recBufSize,playBufSize;
  5. AudioRecord audioRecord;
  6. recBufSize = AudioRecord.getMinBufferSize(frequency,
  7. channelConfiguration, audioEncoding);
  8. audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
  9. AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, recBufSize);
2、语音播放

  当语音数据采集好了之后,接着可以实现语音播放模块。Android上实现PCM数据的播放也很简单,直接
使用AudioTrack这个接口就行了。同样需要注意该接口
的使用方法。AudioTrack的构造方式跟AudioRecord是对应的

  1. static final int frequency = 8000;
  2. static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
  3. static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
  4. int recBufSize,playBufSize;
  5. AudioTrack audioPlayer ;
  6. playBufSize = AudioTrack.getMinBufferSize(frequency,
  7. channelConfiguration, audioEncoding);
  8. audioPlayer = new AudioTrack(AudioManager.STREAM_MUSIC,frequency,AudioFormat.CHANNEL_OUT_MONO,
  9. AudioFormat.ENCODING_PCM_16BIT,playBufSize,
  10. AudioTrack.MODE_STREAM) ;
3、语音编解码

  采集到的PCM数据是原始的语音数据,如果我们直接进行网络传输,那是不可取的。因此,要进行打包编码。
编码我们需要第三方的库,目前我使用的库是speex(http://www.speex.org)。我看到许多SIP语音电话都
使用到了这个库进行编解码。当然也有对这个库评 价不好的说法,但我觉得作为学习还是可取的,因为speex
使用起来很方便。把speex源码下载下来,写好JNI接口,在NDK环境编译一下,即可在java环境调用。
例如下面一个接口函数

  1. jint
  2. Java_com_audiocodec_talkdemo_AudioCodec_InitAudioEncodec( JNIEnv* env,
  3. jobject thiz,jint sampling_rate,jint audioLevel)
  4. {
  5. if(nInitAudioCodecEncodeFlag == 1 || audioLevel < 3 || audioLevel > 8 )
  6. return 0 ;
  7. int frame_size ;
  8. if(sampling_rate == 8000)
  9. {
  10. audio_Leval = 0 ;
  11. capAudioLength = 160 ;
  12. capAudioBitrate = 8000 ;
  13. }else if(sampling_rate == 16000)
  14. {
  15. audio_Leval = 1 ;
  16. capAudioLength = 320 ;
  17. capAudioBitrate = 16000 ;
  18. }else if(sampling_rate == 32000)
  19. {
  20. audio_Leval = 2 ;
  21. capAudioLength = 640 ;
  22. capAudioBitrate = 32000 ;
  23. }else
  24. return 0 ;
  25. tmp_Level = audioLevel ; //设置等级 15kbit/s
  26. speex_mode = speex_lib_get_mode(audio_Leval) ;
  27. enc_state = speex_encoder_init(speex_mode);
  28. speex_encoder_ctl(enc_state,SPEEX_SET_QUALITY,&tmp_Level);
  29. int tmp = 30 ;//丢包补偿
  30. int nRet = speex_encoder_ctl(enc_state, SPEEX_SET_PLC_TUNING, &tmp);
  31. nRet = speex_encoder_ctl(enc_state, SPEEX_GET_PLC_TUNING, &tmp);
  32. speex_bits_init(&bits);
  33. nInitAudioCodecEncodeFlag = 1 ;
  34. return 1 ;
  35. }
  36. //编码音频数据
  37. /*
  38. 参数
  39. jbyteArray szAudio 等待编码的音频数据
  40. jbyteArray szOut 编码后的音频数据
  41. 返回值
  42. 成功返回 编码后长度
  43. 失败返回 0
  44. */
  45. jint Java_com_audiocodec_talkdemo_AudioCodec_AudioEncode( JNIEnv* env,
  46. jobject thiz,jbyteArray szAudio,jbyteArray szOut)
  47. {
  48. if(nInitAudioCodecEncodeFlag == 0)
  49. return 0 ;
  50. jbyte* szAudioBuffer = (jbyte *)(*env)->GetByteArrayElements(env,szAudio, 0);
  51. jbyte* szOutBuffer = (jbyte *)(*env)->GetByteArrayElements(env,szOut, 0);
  52. //清空bits ,以便编码
  53. speex_bits_reset(&bits);
  54. //进行编码
  55. int nRet = speex_encode_int(enc_state,(spx_int16_t*)szAudioBuffer, &bits);
  56. //把编码后的bits 结构,拷贝到cbits_enc的数据可以从网络发送出去,长度为nByte_enc
  57. int nByte_enc = speex_bits_write(&bits, szOutBuffer, 200);
  58. (*env)->ReleaseByteArrayElements(env,szAudio,szAudioBuffer,0) ;
  59. (*env)->ReleaseByteArrayElements(env,szOut,szOutBuffer,0) ;
  60. return nByte_enc ;
  61. }
  62. /*
  63. 函数功能 初始化编码器
  64. 参数
  65. 无参数
  66. 返回值
  67. 成功返回 1
  68. 失败返回 0
  69. */
  70. jint
  71. Java_com_audiocodec_talkdemo_AudioCodec_ExitAudioEncodec( JNIEnv* env,
  72. jobject thiz)
  73. {
  74. if(nInitAudioCodecEncodeFlag == 1)
  75. {
  76. nInitAudioCodecEncodeFlag = 0 ;
  77. //销毁资源
  78. speex_bits_destroy(&bits);
  79. speex_encoder_destroy(enc_state);
  80. enc_state = NULL ;
  81. }else
  82. return 0 ;
  83. }
4 网络发送、接收
  1. //定义
  2. DatagramSocket udpSocket ;
  3. //生成
  4. try {
  5. udpSocket = new DatagramSocket(6789);
  6. } catch (SocketException e1) {
  7. e1.printStackTrace();
  8. }
  9. //发送
  10. try {
  11. udpSocket.send(sendPacket) ;
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. //接收
  16. udpSocket.receive(udpPackage);
  17. //关闭
  18. udpSocket.close() ;

三、 回音消除

  1. 从Speex 的介绍可以看出它提供了噪音消除,回音消除,测试比较过噪音消除这功能效果是非
  2. 常棒的,回音消除这功能也很不错这一功能,现在开源的,比较完善的回音消除模块就是Speex了
  3. ,有许多中小公司也拿它作为回音消除功能 。经过测试,Speex的消除效果还是不错的。
  4. 编写个jni文件,NDK 环境编译一下即可得到so 文件,在Android环境中调用即可。
  1. //初始化回音消除参数
  2. /*
  3. * jint frame_size 帧长 一般都是 80,160,320
  4. * jint filter_length 尾长 一般都是 80*25 ,160*25 ,320*25
  5. * jint sampling_rate 采样频率 一般都是 8000,16000,32000
  6. * 比如初始化
  7. * InitAudioAEC(80, 80*25,8000) //8K,10毫秒采样一次
  8. * InitAudioAEC(160,160*25,16000) //16K,10毫秒采样一次
  9. * InitAudioAEC(320,320*25,32000) //32K,10毫秒采样一次
  10. */
  11. jint Java_com_audioaec_talkdemo_AudioAEC_InitAudioAEC( JNIEnv* env,jobject thiz,
  12. jint frame_size,jint filter_length,jint sampling_rate)
  13. {
  14. if(nInitSuccessFlag == 1)
  15. return 1 ;
  16. m_nFrameSize = frame_size;
  17. m_nFilterLen = filter_length;
  18. m_nSampleRate = sampling_rate;
  19. //计算采样时长,即是10毫秒,还是20毫秒,还是30毫秒
  20. nSampleTimeLong = (frame_size / (sampling_rate / 100)) * 10 ;
  21. m_pState = speex_echo_state_init(m_nFrameSize, m_nFilterLen);
  22. if(m_pState == NULL)
  23. return -1 ;
  24. m_pPreprocessorState = speex_preprocess_state_init(m_nFrameSize, m_nSampleRate);
  25. if(m_pPreprocessorState == NULL)
  26. return -2 ;
  27. iArg = m_nSampleRate;
  28. speex_echo_ctl(m_pState, SPEEX_SET_SAMPLING_RATE, &iArg);
  29. speex_preprocess_ctl(m_pPreprocessorState, SPEEX_PREPROCESS_SET_ECHO_STATE, m_pState);
  30. nInitSuccessFlag = 1 ;
  31. return 1 ;
  32. }
  33. /*
  34. 参数:
  35. jbyteArray recordArray 录音数据
  36. jbyteArray playArray 放音数据
  37. jbyteArray szOutArray
  38. */
  39. jint Java_com_audioaec_talkdemo_AudioAEC_AudioAECProc(JNIEnv* env,jobject thiz,
  40. jbyteArray recordArray,jbyteArray playArray,jbyteArray szOutArray )
  41. {
  42. if(nInitSuccessFlag == 0)
  43. return 0 ;
  44. jbyte* recordBuffer = (jbyte *)(*env)->GetByteArrayElements(env,recordArray, 0);
  45. jbyte* playBuffer = (jbyte *)(*env)->GetByteArrayElements(env,playArray, 0);
  46. jbyte* szOutBuffer = (jbyte *)(*env)->GetByteArrayElements(env,szOutArray, 0);
  47. speex_echo_cancellation(m_pState,(spx_int16_t *)recordBuffer,
  48. (spx_int16_t *)playBuffer,(spx_int16_t *)szOutBuffer);
  49. int flag=speex_preprocess_run(m_pPreprocessorState,(spx_int16_t *)szOutBuffer);
  50. (*env)->ReleaseByteArrayElements(env,recordArray,recordBuffer,0) ;
  51. (*env)->ReleaseByteArrayElements(env,playArray,playBuffer,0) ;
  52. (*env)->ReleaseByteArrayElements(env,szOutArray,szOutBuffer,0) ;
  53. return 1 ;
  54. }
  55. //退出
  56. jint Java_com_sosea_xmeeting_SpeexAEC_ExitSpeexDsp( JNIEnv* env,jobject thiz)
  57. {
  58. if(nInitSuccessFlag == 0)
  59. return 0 ;
  60. if (m_pState != NULL)
  61. {
  62. speex_echo_state_destroy(m_pState);
  63. m_pState = NULL;
  64. }
  65. if (m_pPreprocessorState != NULL)
  66. {
  67. speex_preprocess_state_destroy(m_pPreprocessorState);
  68. m_pPreprocessorState = NULL;
  69. }
  70. nInitSuccessFlag = 0 ;
  71. return 1 ;
  72. }

四 、 噪音消除处理

  1. // 初始化 降噪
  2. Java_com_audioaec_talkdemo_AudioAEC_InitAudioDeNose( JNIEnv* env,
  3. jobject thiz)
  4. {
  5. int denoise_enabled = 1 ;
  6. if(nInitDeNoseFlag == 1)
  7. return 0 ;
  8. nInitDeNoseFlag = 1 ;
  9. //8K降噪
  10. audioProcNose8K = speex_preprocess_state_init(80 * (nSampleTimeLong / 10),8000);
  11. speex_preprocess_ctl(audioProcNose8K, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled);
  12. //16K降噪
  13. audioProcNose16K = speex_preprocess_state_init(160 * (nSampleTimeLong / 10),16000);
  14. speex_preprocess_ctl(audioProcNose16K, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled);
  15. return 1 ;
  16. }
  17. //8K降噪
  18. jint Java_com_audioaec_talkdemo_AudioAEC_AudioDeNose8K(JNIEnv* env,jobject thiz,jbyteArray recordArray)
  19. {
  20. if(nInitDeNoseFlag == 0)
  21. return 0 ;
  22. jbyte* recordBuffer = (jbyte *)(*env)->GetByteArrayElements(env,recordArray, 0);
  23. speex_preprocess(audioProcNose8K,(spx_int16_t*)recordBuffer, NULL);
  24. (*env)->ReleaseByteArrayElements(env,recordArray,recordBuffer,0) ;
  25. return 1 ;
  26. }
  27. //16K降噪
  28. jint Java_com_audioaec_talkdemo_AudioAEC_AudioDeNose16K(JNIEnv* env,jobject thiz,jbyteArray recordArray)
  29. {
  30. if(nInitDeNoseFlag == 0)
  31. return 0 ;
  32. jbyte* recordBuffer = (jbyte *)(*env)->GetByteArrayElements(env,recordArray, 0);
  33. speex_preprocess(audioProcNose16K,(spx_int16_t*)recordBuffer, NULL);
  34. (*env)->ReleaseByteArrayElements(env,recordArray,recordBuffer,0) ;
  35. return 1 ;
  36. }
  37. // 释放降噪
  38. jint
  39. Java_com_audioaec_talkdemo_AudioAEC_ExitAudioDeNose( JNIEnv* env,
  40. jobject thiz)
  41. {
  42. if(nInitDeNoseFlag == 0)
  43. return 0 ;
  44. nInitDeNoseFlag = 0 ;
  45. speex_preprocess_state_destroy(audioProcNose8K);
  46. speex_preprocess_state_destroy(audioProcNose16K);
  47. return 1 ;
  48. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/988346
推荐阅读
相关标签
  

闽ICP备14008679号