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

转载于:https://www.cnblogs.com/jianglijs/p/8583603.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值