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

时间: 2024-10-04 15:55:13

Android 平台语音通话及回音消除、噪音消除研究(转)的相关文章

Android P2P语音通话实现

1.http://www.cnblogs.com/milospooner/archive/2012/07/13/2590950.html 2.http://my.oschina.net/sanshang/blog/11151 3.http://bashell.sinaapp.com/archives/voip-on-android-use-juv-red5-speex-2.html 4.http://blog.csdn.net/ranxiedao/article/category/1194641

安卓语音通话

这段时间在做公司的语音这一块,主要是实现 手机端和pc端的语音通话问题(回音和噪音消除暂时没有考虑)用得是amr得方案.. 安卓手机端自带的录音是可以直接录成amr的格式,省去了压缩的功夫.但是播放的时候如果要是直接播amr的话 只能通过文件的方式进行,这就会导致延迟. 目前软件是用得安卓自己提供的硬解码方案,没有使用自己的软解码,主要是如果软解码的话程序cpu占用率会很高. 录音方面用得是MediaRecord边录边发送.基本上没有难点. 手机播放方面 由于MediaPlay  只能从文件或者

【iOS开发】封装聊天输入框MKInputBar,语音支持iOS &amp; Android平台

最近做的一个项目,有聊天的功能,最开始从网上找了个被人封装好的输入框,写的很复杂(反正我有点被看迷糊了),用起来呢又有点问题,最终放弃,自己封装了一个聊天输入框MKInputBar,难度不大.语音支持iOS和Android平台,其实就是把caf转换为mp3.底部给出了Demo工程,用起来很简单. 先上几张图吧       用法很简答,封装好只有两个文件MKInputBar.h & MKInputBar.m,实现三个代理方法: 1 - (void)inputBar:(MKInputBar *)in

Android平台播放语音时支持听筒、喇叭之间切换

AnyChat for Android SDK默认是通过喇叭进行播放声音. AnyChat r4112版本新增接口,可以在听筒.喇叭之间进行播放声音的切换. AnyChatCoreSDK.mAudioHelper.SwitchPlayMode(0); 复制代码 如果需要指定切换到听筒,则调用API: AnyChatCoreSDK.mAudioHelper.SwitchPlayMode(AnyChatAudioHelper.PLAY_MODE_RECEIVER); 复制代码 如果需要指定切换到喇叭

转:android实时语音问题分析

转:http://ticktick.blog.51cto.com/823160/1746136 PigeonCall:一款Android VoIP网络电话App架构分析 2016-02-29 20:12:19 标签:Android Pigeoncall 飞鸽电话 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://ticktick.blog.51cto.com/823160/1746136 1.概述 PigeonCall,中文名“

基于Android平台的i-jetty网站智能农业监控系统

基于android平台i-jetty网站的智能农业监控系统 摘要:传统的监控系统,一般是基于PC的有线通信传输,其有很多不足之处,如功耗较高.布线成本高.难度大,适应性差,可扩展性不强,增加新的通信线路需要再次布线施工,而且维护起来也比较麻烦,一旦线路出问题,需要繁琐的检查.而嵌入式Web监控系统是基于物联网技术,其无线通信技术具有成本低廉.适应性强.扩展性强.信息安全.使用维护简单等优点. 智能农业中,种植大棚是通过大棚内安装温湿度以及光照传感器,来对农作物的环境参数进行实时采集,由Web监控

调研Android平台的开发环境的发展演变

一.发展演变 1.Android版本进化史 阿童木(Android beta)和发条机器人(Android1.0)->Cupcake(Android1.5)->Dount(Android1.6)->Eclair(Android2.0/2.1)->Froyo(Android2.2/2.2.1)->Gingerbread(Android2.3.x)->Honeycomb(Android3.0/3.1/3.2)->Ice Cream Sandwich(Android4.

Android平台GPS系统的应用开发

第一部分.前述: Android作为Google移动互联网战略的重要组成部分,将进一步推进“随时随地为每个人提供信息”这一企业目标的实现.Google的目标是让移动通信不依赖于设备,甚至是平台.出于这个目的,Android将完善而不是替代Google长期以来推行的移动发展战略:通过与全球各地的手机制造商和移动运营商成为合作伙伴,开发既实用又有吸引力的移动服务,并推广这些产品. 随着城市化的进展和家用轿车的普及.原本根遥远的全球卫星定位系统(Global Position System.6Ps)的

小梅科普:Android平台的快递迹平台开发

目前Android平台移动应用开发正如火如荼的发展,智能手机和平板电脑的出货量正快速上升,人们正越来越习惯于在移动平台进行娱乐和各种操作.目前电子商务的发展使人们在每次网购之后都需要了解购买的商品的物流信息,在这样的大背景下,很多手机用户就需要一款实用的.简便的而且是准确的快递信息查询工具来帮助自己. 在此前提下开发出一款便捷的快递轨迹查询工具,使用可靠的第三方快递查询接口-爱查快递提供快递关键信息数据,整个个应用界面风格简洁,功能设计合理,讲解课程由浅入深,以开发的顺序为主线,分成若干个模块,