speex算法在android上的移植

最近在调speex接口参数,将speex算法的一些特性给添加进去,比如:降噪,静音检测,白噪声添加,增益等等。下面我们就先简单介绍一些spexx算

法。speex语音算法主要是针对VOIP应用的一个开源算法,他集合了多种功能,除了如上所述的,还增加了回声消除(ACE)等功能,能够在多种平台

进行应用。下面我主要介绍一下speex在android平台上的应用。

首先我们来介绍一下speex算法的模块划分。在介绍之前,我们最好去speex官网  http://www.speex.org/downloads/ 去下载他的相关文档以及源码。其

中有一个包speex-api-reference.tar.gz 就有speex模块的相关介绍。其API介绍在1.2为止,speex总共分为以下9大模块:

--Speex encoder and decoder。——编码和解码模块。

--SpeexBits:Bit-stream mainpulations。——比特流操作模块,也就是数据的读写模块。

--Various definitions for Speex callbacks supported by the decoder。——解码回调模块。

--SpeexEchoState:Acoustic echo caceller。——回声消除模块。

--SpeexHeader:Makes it easy to writ/parse an Ogg/Speex header。——ogg格式相关的处理模块。

--JitterBuffer:Adaptive jitter buffer。——语音抖动缓冲模块。

--SpeexJitter:Adaptive jitter buffer specifically for Speex。——针对speex算法特点优化的语言抖动处理模块。

--SpeexPreprocessState:The Speex preprocessor。——Speex其他相关特点的处理模块,如:降噪,静音检测等。

--SpeexStereoState:Handing Speex stereo files。——立体声处理的相关模块。

.以上就是Speex算法的主要模块,每个模块都有相关功能的函数接口,具体我们可以去查看其api的相关介绍。

好了现在我们来介绍其在android平台的使用。由于其使用的是C实现的,所以要想在android进行调用其相关方法就必须通过JNI的方法进行调用,所以

我们首先就必须获得speex算法的一个.so文件,因此我们先使用cygwin编译获取.so文件。

一、将speex相关源码复制进项目

下载speex源码,在项目中新建文件夹,命名为jni。将speex源码下的include,libspeex两个文件的源码复制进jni文件夹中。将include文件夹下的

speex_config_types.h.in文件改为speex_config_types.h文件,并且将其中的内容改为以下内容:

[cpp]  view plain copy print ?
  1. #ifndef _SPEEX_CONFIG_TYPES_H  
  2. #define _SPEEX_CONFIG_TYPES_H  
  3.   
  4.    typedef signed short spx_int16_t;  
  5.    typedef unsigned short spx_uint16_t;  
  6.    typedef signed int spx_int32_t;  
  7.    typedef unsigned int spx_uint32_t;  
  8.   
  9. #endif  /* _SPEEX_CONFIG_TYPES_H */  

二、编写Android.mk以及Application.mk相关文件。

Android.mk文件内容如下:

(注意:前面几个参数LOCAL_MODULE,LOCAL_C_INCLUDES要按照自己的文件夹的包含结构写,LOCAL_SRC_FILES:=speex_jni.cpp,这个.cpp文件也要看自己的命名)

[cpp]  view plain copy print ?
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := libspeex  
  6. LOCAL_CFLAGS = -DFIXED_POINT -DUSE_KISS_FFT -DEXPORT="" -UHAVE_CONFIG_H  
  7. LOCAL_C_INCLUDES := $(LOCAL_PATH)/include  
  8.   
  9. LOCAL_SRC_FILES := speex_jni.cpp \  
  10.                 ./libspeex/bits.c \  
  11.                 ./libspeex/buffer.c \  
  12.                 ./libspeex/cb_search.c \  
  13.                 ./libspeex/exc_10_16_table.c \  
  14.                 ./libspeex/exc_10_32_table.c \  
  15.                 ./libspeex/exc_20_32_table.c \  
  16.                 ./libspeex/exc_5_256_table.c \  
  17.                 ./libspeex/exc_5_64_table.c \  
  18.                 ./libspeex/exc_8_128_table.c \  
  19.                 ./libspeex/fftwrap.c \  
  20.                 ./libspeex/filterbank.c \  
  21.                 ./libspeex/filters.c \  
  22.                 ./libspeex/gain_table.c \  
  23.                 ./libspeex/gain_table_lbr.c \  
  24.                 ./libspeex/hexc_10_32_table.c \  
  25.                 ./libspeex/hexc_table.c \  
  26.                 ./libspeex/high_lsp_tables.c \  
  27.                 ./libspeex/jitter.c \  
  28.                 ./libspeex/kiss_fft.c \  
  29.                 ./libspeex/kiss_fftr.c \  
  30.                 ./libspeex/lpc.c \  
  31.                 ./libspeex/lsp.c \  
  32.                 ./libspeex/lsp_tables_nb.c \  
  33.                 ./libspeex/ltp.c \  
  34.                 ./libspeex/mdf.c \  
  35.                 ./libspeex/modes.c \  
  36.                 ./libspeex/modes_wb.c \  
  37.                 ./libspeex/nb_celp.c \  
  38.                 ./libspeex/preprocess.c \  
  39.                 ./libspeex/quant_lsp.c \  
  40.                 ./libspeex/resample.c \  
  41.                 ./libspeex/sb_celp.c \  
  42.                 ./libspeex/scal.c \  
  43.                 ./libspeex/smallft.c \  
  44.                 ./libspeex/speex.c \  
  45.                 ./libspeex/speex_callbacks.c \  
  46.                 ./libspeex/speex_header.c \  
  47.                 ./libspeex/stereo.c \  
  48.                 ./libspeex/vbr.c \  
  49.                 ./libspeex/vq.c \  
  50.                 ./libspeex/window.c  
  51.   
  52. include $(BUILD_SHARED_LIBRARY)  

具体每行的含义可以参看我之前的一篇博客,或者自行搜索Android.mk的编写方法。

Applicatio.mk 内容如下:

[cpp]  view plain copy print ?
  1. APP_ABI := armeabi armeabi-v7a  

三、编写本地方法接口文件speex

[java]  view plain copy print ?
  1. public native int open(int compression);  
  2. public native int getFrameSize();  
  3. public native int decode(byte encoded[], short lin[], int size);  
  4. public native int encode(short lin[], int offset, byte encoded[], int size);  
  5. public native void close();  

四、使用java当中的javah工具编译这个jni接口文件。

使用cmd进入到项目bin/classes目录下,输入以下命令:javah -jni xxx.xxx.xxx.speex。前面的xxx为speex文件的包名。编译完成后会在classes文件下看到

一个com_poctalk_codec_Speex.h文件,将这个文件复制进jni目录下。

(注意:若出现javah不是内部也不是外部命令···的错误,参见网址:http://blog.163.com/caoguoqiang_dlut/blog/static/106589142201131824557488/,我在把含有了javah.exe的文件的路径F:\soft install\java\bin加入环境变量后,javah仍不是内部命令也不是外部命令,然后再将F:\soft install\java\jre\bin加入环境变量后,就可以了,其中F:\soft install\java为JDK安装时,自己选择的安装路径

五、编写speex.cpp文件

[cpp]  view plain copy print ?
  1. #include   
  2. #include "com_poctalk_codec_Speex.h"  
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #pragma comment(lib,"libspeexdsp.lib")  
  9.   
  10. static int codec_open = 0;  
  11. static int dec_frame_size;  
  12. static int enc_frame_size;  
  13.   
  14. static SpeexBits ebits, dbits;  
  15. void *enc_state;  
  16. void *dec_state;  
  17. SpeexPreprocessState *preprocess_state;  
  18. //SpeexEchoState *echo_state;  
  19. static JavaVM *gJavaVM;  
  20.   
  21. extern "C"{  
  22.      JNIEXPORT jint JNICALL Java_com_poctalk_codec_Speex_open(JNIEnv *env, jobject obj, jint compression) {  
  23.         int tmp;  
  24.   
  25.         if (codec_open++ != 0)  
  26.             return (jint)0;  
  27.   
  28.         speex_bits_init(&ebits);  
  29.         speex_bits_init(&dbits);  
  30.         //设置编码为窄带编码  
  31.         enc_state = speex_encoder_init(&speex_nb_mode);  
  32.         dec_state = speex_decoder_init(&speex_nb_mode);  
  33.         //设置编码为宽带编码  
  34.         //enc_state = speex_encoder_init(&speex_wb_mode);  
  35.         //dec_state = speex_decoder_init(&speex_wb_mode);  
  36.         tmp = compression;  
  37.         speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &tmp);//设置编码的比特率,即语音质量。由参数tmp控制  
  38.         speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &enc_frame_size);  
  39.         speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &dec_frame_size);  
  40.   
  41.         preprocess_state =speex_preprocess_state_init(160, 8000);//创建预处理对象  
  42.   
  43.         //echo_state = speex_echo_state_init(160, 5000);//创建回声消除对象  
  44.         //int sampleRate = 8000;  
  45.         //speex_echo_ctl(echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);  
  46.   
  47.         int denoise = 1;  
  48.         int noiseSuppress = -25;  
  49.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_DENOISE, &denoise); //降噪  
  50.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noiseSuppress); //设置噪声的dB  
  51.   
  52.   
  53.         int agc = 1;  
  54.         float q=24000;  
  55.         //actually default is 8000(0,32768),here make it louder for voice is not loudy enough by default. 8000  
  56.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_AGC, &agc);//增益  
  57.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_AGC_LEVEL,&q);  
  58.   
  59.         int vad = 1;  
  60.         int vadProbStart = 80;  
  61.         int vadProbContinue = 65;  
  62.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_VAD, &vad); //静音检测  
  63.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_PROB_START , &vadProbStart); //Set probability required for the VAD to go from silence to voice  
  64.         speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_PROB_CONTINUE, &vadProbContinue); //Set probability required for the VAD to stay in the voice state (integer percent)  
  65.   
  66.         return (jint)0;  
  67.      }  
  68.       
  69.      JNIEXPORT jint JNICALL Java_com_poctalk_codec_Speex_encode  
  70.         (JNIEnv *env, jobject obj, jshortArray lin, jint offset, jbyteArray encoded, jint size) {  
  71.   
  72.         jshort buffer[enc_frame_size];  
  73.         jbyte output_buffer[enc_frame_size];  
  74.         int nsamples = (size-1)/enc_frame_size + 1;  
  75.         int i, tot_bytes = 0;  
  76.   
  77.         if (!codec_open)  
  78.             return 0;  
  79.   
  80.         speex_bits_reset(&ebits);//在每帧输入之前将所有的编码状态重置  
  81.   
  82.         speex_echo_state_reset(echo_state);//  
  83.   
  84.         for (i = 0; i < nsamples; i++) {  
  85.             env->GetShortArrayRegion(lin, offset + i*enc_frame_size, enc_frame_size, buffer);  
  86.   
  87.             //input_frame麦克风采集到的数据,Echo_Data是从speaker处获取到的数据,out_frame为回声消除后的数据  
  88.             //speex_echo_cancellation(echo_state,input_frame,Echo_Data,out_frame);//回声消除  
  89.   
  90.             speex_preprocess_run(preprocess_state, buffer);  
  91.             speex_encode_int(enc_state, buffer, &ebits);  
  92.         }  
  93.   
  94.         tot_bytes = speex_bits_write(&ebits, (char *)output_buffer,enc_frame_size);//返回实际被写入的字节数  
  95.         env->SetByteArrayRegion(encoded, 0, tot_bytes,output_buffer);  
  96.   
  97.         return (jint)tot_bytes;  
  98.      }  
  99.   
  100.      JNIEXPORT jint JNICALL Java_com_poctalk_codec_Speex_decode  
  101.         (JNIEnv *env, jobject obj, jbyteArray encoded, jshortArray lin, jint size) {  
  102.   
  103.             jbyte buffer[dec_frame_size];  
  104.             jshort output_buffer[dec_frame_size];  
  105.             jsize encoded_length = size;  
  106.   
  107.         if (!codec_open)  
  108.             return 0;  
  109.   
  110.         env->GetByteArrayRegion(encoded, 0, encoded_length, buffer);  
  111.         speex_bits_read_from(&dbits, (char *)buffer, encoded_length);  
  112.         speex_decode_int(dec_state, &dbits, output_buffer);  
  113.         env->SetShortArrayRegion(lin, 0, dec_frame_size,  
  114.                      output_buffer);  
  115.   
  116.         return (jint)dec_frame_size;  
  117.      }  
  118.   
  119.      JNIEXPORT jint JNICALL Java_com_poctalk_codec_Speex_getFrameSize(JNIEnv *env, jobject obj) {  
  120.   
  121.         if (!codec_open)  
  122.             return 0;  
  123.         return (jint)enc_frame_size;  
  124.      }  
  125.   
  126.      JNIEXPORT void JNICALL Java_com_poctalk_codec_Speex_close(JNIEnv *env, jobject obj) {  
  127.   
  128.         if (--codec_open != 0){  
  129.             return;  
  130.         }  
  131.         //speex_echo_state_destroy(echo_state);//  
  132.   
  133.         speex_preprocess_state_destroy(preprocess_state);  
  134.         speex_bits_destroy(&ebits);  
  135.         speex_bits_destroy(&dbits);  
  136.   
  137.         speex_decoder_destroy(dec_state);  
  138.         speex_encoder_destroy(enc_state);  
  139.      }  
  140. }  

六、使用cygwin对整个项目进行编译。

(如何使用cygwin对Android项目编译,生产.os文件:参见http://blog.csdn.net/zkw12358/article/details/24560499
  0、下载NDK
  1、 下载安装cygwin,参见:http://www.33lc.com/article/7276_4.html,若出现Unable to get setup.ini from····,可以选择第二个地址,有时候需要多试几次;
 2、测试cygwin,输入make -v,gcc -v,看是否正常运行。若出现:cygwin make:command not found,参见http://blog.csdn.net/lanmanck/article/details/5738337 
 3、配置cygwin,

找到cygwin的安装路径下的 .bash_profile文件,我的路径是:C:\cygwin64\home\ASUS,使用记事本打开这个文件,然后

在文件的末尾加上这两段代码:(注意其中ANDK这个名字可以自己随便取,但是两行代码的ANDK必须相同,其中e/doMyself/android-ndk-r6b-windows/android-ndk-r6b 为你下载的NDK的文件夹路径

ANDK=/cygdrive/e/doMyself/android-ndk-r6b-windows/android-ndk-r6b 
export ANDK

4.5.6.7 ```按照参见网址操作;

8、使用cygwin编译生成.so文件:此时,参见网址中有一步:输入命令:ANDK/ndk-build,这里一定要输入命令:$ANDK/ndk-build

编译完成后,refresh项目会在libs目录下生成两个文件夹armeabi,armeabi-v7a 其中分别有一个libspeex.so文件。

至此.so文件的编译已经完成了,我们就可以在项目中对本地方法进行调用,去进行语音的编解码。由于使用方面我已经在项目中进行应用了,所以就不

挂出来了,不过我的语言模块也是参考网上的一个项目进行编写的,名字叫做android-recorder-6.0,你可以下载他的源码进行模仿。不过还有一点需要

说明的是在编写.cpp文件时,我没有将回声消除的功能给加进去,在回声消除的这个问题上浪费了我很多时间,刚开始没有看他的api,不知道回声消除

是哪个模块实现的,不知道该怎样使用回声消除的api,后来看了api,又不知道怎样在调用回声消除的函数时,该怎样传递参数进去,后来问同事知道,

回声消除的功能是针对全双工的通信方式,也就是喇叭和录音模块都打开,如果是半双工的通信方式,比如:手持机,回声消除的功能其实可有可无。

但是既然提到了又浪费了很多时间,那就不妨讲一讲回声消除功能的调用。

首先我们在预处理时,就应该回声消除的预处理:

[cpp]  view plain copy print ?
  1. //echo_state = speex_echo_state_init(160, 5000);//创建回声消除对象  
  2. //int sampleRate = 8000;  
  3. //speex_echo_ctl(echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);  

sampleRate就是我们设置好的录音采用频率。

然后在语音编码的时候,进行回声消除功能的调用:

[cpp]  view plain copy print ?
  1. //input_frame麦克风采集到的数据,Echo_Data是从speaker处获取到的数据,out_frame为回声消除后的数据  
  2. //speex_echo_cancellation(echo_state,input_frame,Echo_Data,out_frame);//回声消除  

上面speex_echo_cancellation函数的三个参数一次为,回声消除对象,inpt_frame为喇叭播放数据,Echo_Data为从麦克风获取的数据,out_frame为最后

回声消除后的数据。可能有人会对这几个参数比较迷惑,那是因为不了解回声的产生原因。由于是全双工通信,当我们在录音的时候,也可能在进行声音

播放,这样就会导致有时候录音也会将喇叭正在播放的声音给录进去,这样就产生了回声的效果,所以第二个参数才要将播放的数据作为参数传递进去。

在编译.so文件的过程中,我还遇到了这样一个问题:multiple definition 。后来才发现在我的android.mk文件中将speex_jni.cpp引用了两次。

直到找到这个博客才发现我的这个问题:http://www.cnblogs.com/hewei2012/p/3370299.html 在这个问题上花费我的时间最久,粗心害死人。

参考博客:

http://www.cppblog.com/tx7do/archive/2012/11/23/195607.html

http://www.cnblogs.com/stay/archive/2011/09/06/2168751.html

http://www.cnblogs.com/chef/archive/2012/07/19/2599067.html

http://www.cnblogs.com/rosesmall/archive/2012/04/18/2455395.html

http://www.cnblogs.com/myitm/archive/2011/07/21/2113299.html

http://www.cnblogs.com/kinyer/p/3326570.html

http://www.cnblogs.com/ldjrl2013/p/3687938.html

http://blog.csdn.net/zblue78/article/details/5841357

你可能感兴趣的:(语音编程)