前言
本来我只是个纯写一般app的码农,奈何进了这家以音视频为主的公司。刚进公司时,还有好些牛人都没有离职(在
音视频方面浸淫以久的同事)。那关于音视频方面的什么传输库,h264,aac编解码等方面的事,有专门的人员来写和维
护,我作为移动端的开发人员,只需要用他们提供的传输库,使用他们在pc端的功能模块就可以了。我也可以快乐的
下班。可公司发展不景气(没有挣到钱),接下来你们懂的。(人员各种流失。。。)最后,移动原生开发的就剩下
两人(我也算在里面)。ios方面的开发,全走了。交接工作全给了我了,OMY,我对苹果开发可是啥也不会呀。只希
望公司能接下来招几个搞ios的人吧。。。可是过了很长的时间,都没有招人,慢慢的,我也了解到领导就没有再招人
的意思。。很明显,交接给你了(我),那么ios的工作就你来干了。。。。我真是。。。。好吧,说远了!回到主
题,因为有些android手机对回声消除不太好,老板就要求我能不能解决下这个问题。我去,我是谁,,我在哪。。我
不会呀。但总不能直接说不会吧。只能硬着头皮上了。。
上网
不会没有关系,网上有很多相关的文章,经过查找知道手机端可用方案大概有如下几种:
- AcousticEchoCanceler(API4.1提供,部分手机不支持)
- AudioSource.VOICE_COMMUNICATION(使用voip通道录音,不靠谱)
- webrtc(业界有名的回声消除库,支持跨平台,难点是计算delay时间)
- SpeexDsp
参考文章:
- https://blog.csdn.net/badongdyc/article/details/73555007
- https://www.pianshen.com/article/43212237/
- https://blog.csdn.net/jiaoyangwuze/article/details/50618508
先谈speex
说干就干,反正我自己现在(以后)也是没有能力写出回声消除算法的(那都是天才巨人干的事)。我能做的就是站在巨人的肩上。
下载speexdsp
speex现在分成两部分了,主要的speex包含编码解码 ,另外一部分声学处理就是speexdsp专门用来处理回声消除
下传送门:
解压源代码
将用红色箭头标识的两文件夹复制到android工程的cpp(jni)目录下的speexdsp目录中:
这里注意了
复制都完成了,就把libseexdsp中的几个test文件删除,不然会有些麻烦,不删除也没有问题哈。主要是这几个test文件都有main函数。
另外,还得在使用我另外编译iconv中使用的方式先configurate下,生成config.h文件。把这个config.h文件也复制到speexdsp目录的include目录中
export NDK=/Users/xxx/Library/Android/android-ndk-r20
export ANDROID_NDK=/Users/xxx/Library/Android/android-ndk-r20
export NDK=/Users/xxx/Library/Android/android-ndk-r20
export PATH=${PATH}:${ANDROID_NDK}
export PATH=${PATH}:${NDK}
export ANDROID_TOOLS=/Users/xxx/Library/Android/sdk/platform-tools
export ANDROID_HOME=/Users/xxx/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}
export ANDROID_TOOLS
export PATH=${PATH}:${ANDROID_TOOLS}
#PATH=/bin:/usr/bin:/usr/local/bin:${PATH}
# darwin-x86_64 是Mac的路径,实际按目录结构修改
export PATH="$NDK:$NDK/shader-tools/darwin-x86_64:$NDK/build/tools:$PATH"
# 预设值,待会才生成的目录
export SYSROOT=~/my-android-toolchain/sysroot
export PATH=~/my-android-toolchain/bin:$PATH
export CC=arm-linux-androideabi-gcc # or export CC=clang
export CXX=arm-linux-androideabi-g++ # or export CXX=clang++
export CXXFLAGS="-lstdc++"
export CPPFLAGS="-lstdc++"
export CC="arm-linux-androideabi-gcc --sysroot=$SYSROOT"
export LD="arm-linux-androideabi-ld"
export AR="arm-linux-androideabi-ar"
export RANLIB="arm-linux-androideabi-ranlib"
export STRIP="arm-linux-androideabi-strip"
export CPP="arm-linux-androideabi-gcc -E"
cd ~
mkdir my-android-toolchain
$NDK/build/tools/make-standalone-toolchain.sh --arch=arm --install-dir=~/my-android-toolchain --platform=android-21 --stl=gnustl
cd ~/Downloads/speexdsp-1.2rc3
./configurate --host=arm-linux-eabi
接下来就是编写CMakeLists.txt及jni中间文件
- audio_echo.h
//
// Created by vnetoo on 2020/5/8.
//
#include "echo_config.h"
#ifndef SPEEX_AUDIO_ECHO_H
#define SPEEX_AUDIO_ECHO_H
#define NN 128 //帧长 一般都是 80,160,320,
#define TAIL 1024 //尾长 一般都是 80*25 ,160*25 ,320*25
#define ECHO_ERROR -1
#define ECHO_OK 0
// defined speexEcho struct
typedef struct SpeexEcho{
SpeexEchoState* st;
SpeexPreprocessState* den;
int aec_init_flag;
pthread_mutex_t mutex;
int enable_echo;
int frame_size;
int enable_vad;
}SpeexEcho;
int echo_init(SpeexEcho* context,const unsigned short frameSize ,const unsigned short filterSize,const unsigned short sampleRate);
int echo_set_ctl(SpeexEcho* context,const unsigned short key , int value);
int echo_get_ctl(SpeexEcho* context,const unsigned short key , void* result);
int echo_set_preprocess_ctl(SpeexEcho* context,const unsigned short key , void* value);
int echo_get_preprocess_ctl(SpeexEcho* context,const unsigned short key , void* value);
int echo_sync_process(const SpeexEcho* context,unsigned short* near,unsigned short* far, unsigned short* out,size_t size);
int echo_denoise_only(const SpeexEcho* context,unsigned short* near);
int echo_asyn_process_near(const SpeexEcho* context,unsigned short* near,unsigned short *out);
int echo_asyn_process_far(const SpeexEcho* context,unsigned short* far);
void echo_distroy(SpeexEcho* context);
#endif //SPEEX_AUDIO_ECHO_H
- audio_echo.c
//
// Created by vnetoo on 2020/5/8.
//
#include "include/audio_echo.h"
int echo_init(SpeexEcho *context, const unsigned short frameSize, const unsigned short filterSize,
const unsigned short sampleRate) {
pthread_mutex_lock(&(context->mutex));
unsigned short fs = frameSize;
if (fs <= 0) {
return ECHO_ERROR;
}
unsigned short fl = filterSize;
if (fl <= 0) {
return ECHO_ERROR;
}
int mSampleRate = sampleRate;
if(mSampleRate<=0){
return ECHO_ERROR;
}
context->st = speex_echo_state_init(fs, fl);
ASSERT(context->st != NULL, "init echo error");
if (context->st == NULL) {
LOGE("init echo error with frameSize:%d,filterSize:%d", fs, fl);
return ECHO_ERROR;
}
context->den = speex_preprocess_state_init(fs, sampleRate);
ASSERT(context->den != NULL, "init preprocess with sampleRate: %d, error", mSampleRate);
int flag = -1;
flag = speex_echo_ctl(context->st, SPEEX_ECHO_SET_SAMPLING_RATE, &mSampleRate);
ASSERT(flag == 0, "set sampleRate for echo error");
flag = speex_preprocess_ctl(context->den, SPEEX_PREPROCESS_SET_ECHO_STATE, context->st);
ASSERT(flag == 0, "set aec proprocess to st error")
context->aec_init_flag = flag == 0 ? 1 : 0;
pthread_mutex_unlock(&(context->mutex));
LOGI("init speex aec success");
return ECHO_OK;
}
int echo_set_ctl(SpeexEcho *context, const unsigned short key, int value) {
ASSERT(context != NULL, "speexEcho is NULL")
if (context != NULL && context->aec_init_flag == 1) {
int result = speex_echo_ctl(context->st, key, &value);
if (result == 0) {
return ECHO_OK;
} else {
LOGE("set speex_echo_ctl(%d,%d) unknow key", key, value);
}
}
return ECHO_ERROR;
}
int echo_get_ctl(SpeexEcho *context, const unsigned short key, void *result) {
ASSERT(context != NULL, "speexEcho is NULL")
if (context != NULL && context->aec_init_flag == 1) {
int result = speex_echo_ctl(context->st, key, &result);
if (result == 0) {
return ECHO_OK;
} else {
LOGE("get speex_echo_ctl(%d,%d) unknow key", key, result);
}
}
return ECHO_ERROR;
}
int echo_set_preprocess_ctl(SpeexEcho *context, const unsigned short key, void *value) {
ASSERT(context != NULL, "speexEcho is NULL")
if (context != NULL && context->aec_init_flag == 1) {
pthread_mutex_lock(&(context->mutex));
int result = speex_preprocess_ctl(context->den, key, value);
if(key == SPEEX_PREPROCESS_SET_NOISE_SUPPRESS){
int* v = (int*)value;
LOGI("SPEEX_PREPROCESS_SET_NOISE_SUPPRESS=>%d result:%d",*v,result);
}else if(key == SPEEX_PREPROCESS_SET_DENOISE){
int* v = (int*)value;
LOGI("SPEEX_PREPROCESS_SET_DENOISE=>%d result:%d",*v,result);
}
else if(key == SPEEX_PREPROCESS_SET_AGC){
int* v = (int*)value;
LOGI("SPEEX_PREPROCESS_SET_AGC=>%d result:%d",*v,result);
}else if(key == SPEEX_PREPROCESS_SET_DEREVERB){
int* v = (int*)value;
LOGI("SPEEX_PREPROCESS_SET_DEREVERB=>%d result:%d",*v,result);
}else if(key == SPEEX_PREPROCESS_SET_VAD){
int* v = (int*)value;
&context->enable_vad == *v;
LOGI("SPEEX_PREPROCESS_SET_VAD=>%d result:%d",*v,result);
}else if(key == SPEEX_PREPROCESS_SET_NOISE_SUPPRESS){
int* v = (int*)value;
LOGI("SPEEX_PREPROCESS_SET_NOISE_SUPPRESS=>%d result:%d",*v,result);
}else if(key == SPEEX_PREPROCESS_SET_AGC_LEVEL){
int* v = (int*)value;
LOGI("SPEEX_PREPROCESS_SET_AGC_LEVEL=>%d result:%d",*v,result);
}
pthread_mutex_unlock(&(context->mutex));
if (result == 0) {
return ECHO_OK;
} else {
LOGE("set echo_set_preprocess_ctl(%d) unknow key", key);
}
}
return ECHO_ERROR;
}
int echo_get_preprocess_ctl(SpeexEcho *context, const unsigned short key, void *result) {
ASSERT(context != NULL, "speexEcho is NULL")
if (context != NULL && context->aec_init_flag == 1) {
int ret = speex_preprocess_ctl(context->den, key, result);
if (ret == 0) {
return ECHO_OK;
} else {
LOGE("get echo_set_preprocess_ctl(%d) unknow key", key);
}
}
return ECHO_ERROR;
}
int echo_sync_process(const SpeexEcho *context, unsigned short *near, unsigned short *far,
unsigned short *out,size_t size) {
ASSERT(context != NULL, "speexEcho is NULL")
ASSERT(near != NULL, "mic record data is NULL")
ASSERT(far != NULL, "playData is NULL")
ASSERT(out != NULL, "outputBuffer is NULL")
if (context != NULL && context->aec_init_flag == 1) {
pthread_mutex_lock(&(context->mutex));
// speex_preprocess_run(context->den, far);
speex_echo_cancellation(context->st, near, far, out);
int result = speex_preprocess_run(context->den, out);
pthread_mutex_unlock(&(context->mutex));
return result;
}
return ECHO_ERROR;
}
int echo_denoise_only(const SpeexEcho* context,unsigned short* near){
ASSERT(near != NULL, "near data is NULL")
pthread_mutex_lock(&(context->mutex));
int result = speex_preprocess_run(context->den, near);
pthread_mutex_unlock(&(context->mutex));
return result;
}
int echo_asyn_process_near(const SpeexEcho* context,unsigned short* near,unsigned short *out){
if (context != NULL && context->aec_init_flag == 1) {
pthread_mutex_lock(&(context->mutex));
speex_echo_capture(context->st,near,out);
pthread_mutex_unlock(&(context->mutex));
return ECHO_OK;
}
return ECHO_ERROR;
}
int echo_asyn_process_far(const SpeexEcho* context,unsigned short* far){
if (context != NULL && context->aec_init_flag == 1) {
pthread_mutex_lock(&(context->mutex));
speex_echo_playback(context->st,far);
pthread_mutex_unlock(&(context->mutex));
return ECHO_OK;
}
return ECHO_ERROR;
}
void echo_distroy(SpeexEcho *context) {
if (context != NULL && context->aec_init_flag == 1) {
pthread_mutex_lock(&(context->mutex));
speex_preprocess_state_destroy(context->den);
speex_echo_state_destroy(context->st);
context->aec_init_flag = 0;
pthread_mutex_unlock(&(context->mutex));
LOGE("release speex echo");
}
}
- echo_jni.c
//
// Created by vnetoo on 2020/5/8.
//
#include <stdlib.h>
#include <assert.h>
#include <jni.h>
#include <string.h>
#include <pthread.h>
#include "include/audio_echo.h"
#define ClassName "com/vnetoo/speex/echo/SpeexEcho"
#define EchoPk(method) Java_com_vnetoo_speex_echo_SpeexEcho_##method
static SpeexEcho echo;
static short *out;
int debug = 1;
inline void throwException(JNIEnv *env, const char *clasz, const char *errorMsg);
void throwException(JNIEnv *env, const char *clasz, const char *errorMsg) {
jclass newExcCls;
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
newExcCls = (*env)->FindClass(env, clasz);
if (newExcCls != NULL) {
(*env)->ThrowNew(env, newExcCls, errorMsg);
}
}
JNIEXPORT jint JNICALL
EchoPk(aecInit)(JNIEnv *env, jclass jclasz, jint frameSize, jint filterSize, jint sampleRate) {
int result = echo_init(&echo, frameSize, filterSize, sampleRate);
if (result == ECHO_ERROR) {
throwException(env, "java/lang/IllegalStateException", "init aec error");
}
echo.frame_size = frameSize;
LOGI("speex jni init aec success");
return result;
}
JNIEXPORT jint JNICALL
EchoPk(aec_1set_1feature)(JNIEnv *env, jclass jclasz, jint key, jint value) {
if (echo.aec_init_flag) {
return echo_set_ctl(&echo, key, value);
}
return ECHO_ERROR;
}
JNIEXPORT jint JNICALL
EchoPk(aec_1set_1other_1feature__II)(JNIEnv *env, jclass clazz,
jint key, jint value) {
if (echo.aec_init_flag) {
int v = value;
return echo_set_preprocess_ctl(&echo, key, &v);
}
return ECHO_ERROR;
}
JNIEXPORT jint JNICALL
EchoPk(aec_1set_1other_1feature__IF)(JNIEnv *env, jclass clazz,
jint key, jfloat value) {
if (echo.aec_init_flag) {
float v = value;
return echo_set_preprocess_ctl(&echo, key, &v);
}
return ECHO_ERROR;
}
JNIEXPORT jint JNICALL
EchoPk(aec_1process)(JNIEnv *env, jclass clazz, jbyteArray near,
jbyteArray far,jbyteArray out) {
jbyteArray ret = NULL;
jbyte *nearData = (*env)->GetByteArrayElements(env, near, JNI_FALSE);
jbyte *fearData = (*env)->GetByteArrayElements(env, far, JNI_FALSE);
jbyte *outData = (*env)->GetByteArrayElements(env, out, JNI_FALSE);
int result = 0;
bool needRet = false;
if(echo.enable_echo == 1){
result= echo_sync_process(&echo, nearData, fearData, outData, echo.frame_size);
LOGI("echo canncel :%d", result );
}
else{
result = echo_denoise_only(&echo,nearData);
LOGI("echo_denoise_only:%d", result );
}
(*env)->ReleaseByteArrayElements(env, near, nearData, JNI_FALSE);
(*env)->ReleaseByteArrayElements(env, far, fearData, JNI_FALSE);
(*env)->ReleaseByteArrayElements(env, out, outData, JNI_FALSE);
return result;
}
JNIEXPORT void JNICALL
EchoPk(destory)(JNIEnv *env, jclass clazz) {
// pthread_mutex_lock(&(echo.mutex));
echo_distroy(&echo);
free(out);
// pthread_mutex_unlock(&(echo.mutex));
out = NULL;
}
JNIEXPORT jint JNICALL
EchoPk(aec_1set_1enable_1echo)(JNIEnv *env, jclass clazz,
jboolean enable) {
pthread_mutex_lock(&(echo.mutex));
echo.enable_echo = enable ? 1 : 0;
LOGI("echo.enable_echo=%d", echo.enable_echo);
pthread_mutex_unlock(&(echo.mutex));
return ECHO_OK;
}
JNIEXPORT jfloat JNICALL
EchoPk(aec_1get_1other_1float_1feature)(JNIEnv *env, jclass clazz,
jint key) {
pthread_mutex_lock(&(echo.mutex));
float value = 0;
echo_get_preprocess_ctl(&(echo),key,&value);
pthread_mutex_unlock(&(echo.mutex));
return value;
}
JNIEXPORT jint JNICALL
EchoPk(aec_1get_1other_1int_1feature)(JNIEnv *env, jclass clazz,
jint key) {
pthread_mutex_lock(&(echo.mutex));
int value = 0;
echo_get_preprocess_ctl(&(echo),key,&value);
pthread_mutex_unlock(&(echo.mutex));
return value;
}
JNIEXPORT jint JNICALL
Java_com_vnetoo_speex_echo_SpeexEcho_ProcessStream(JNIEnv *env, jclass clazz, jbyteArray data,
jint offset, jint len) {
jbyte *nearData = (*env)->GetByteArrayElements(env, data, JNI_FALSE);
short* out = (short*)malloc(len);
memset(out,0,len);
echo_asyn_process_near(&echo,nearData,out);
memcpy(nearData,out,len);
free(out);
(*env)->ReleaseByteArrayElements(env, data, nearData, JNI_FALSE);
return 0;
}
JNIEXPORT jint JNICALL
Java_com_vnetoo_speex_echo_SpeexEcho_ProcessReverseStream(JNIEnv *env, jclass clazz,
jbyteArray far_end, jint offset,
jint len) {
jbyte *nearData = (*env)->GetByteArrayElements(env, far_end, JNI_FALSE);
echo_asyn_process_far(&echo,nearData);
(*env)->ReleaseByteArrayElements(env, far_end, nearData, JNI_FALSE);
return 0;
}
- SpeexEcho.java
package com.vnetoo.speex.echo;
class SpeexEcho {
public static native int aecInit(int frame_size, int filter_length, int sampling_rate) throws IllegalStateException;
public static native int aec_set_enable_echo(boolean enable);
public static native int aec_set_feature(int key,int value);
public static native int aec_set_other_feature(int key,int value);
public static native int aec_set_other_feature(int key,float value);
public static native float aec_get_other_float_feature(int key);
public static native int aec_get_other_int_feature(int key);
public static native int aec_process(byte[] near, byte[] far,byte[] out);
public static native int ProcessStream(byte[] data,int offset, int len);
public static native int ProcessReverseStream(byte[] far_end,int offset, int len);
public static native void destory();
// public static native int aec_get_feature(int key);
public static final int SPEEX_PREPROCESS_SET_DENOISE = 0;
public static final int SPEEX_PREPROCESS_GET_DENOISE = 1;
public static final int SPEEX_PREPROCESS_SET_AGC = 2;
public static final int SPEEX_PREPROCESS_GET_AGC = 3;
public static final int SPEEX_PREPROCESS_SET_VAD = 4;
public static final int SPEEX_PREPROCESS_GET_VAD = 5;
public static final int SPEEX_PREPROCESS_SET_AGC_LEVEL = 6;
public static final int SPEEX_PREPROCESS_GET_AGC_LEVEL = 7;
public static final int SPEEX_PREPROCESS_SET_DEREVERB = 8;
public static final int SPEEX_PREPROCESS_GET_DEREVERB = 9;
public static final int SPEEX_PREPROCESS_SET_DEREVERB_LEVEL = 10;
public static final int SPEEX_PREPROCESS_GET_DEREVERB_LEVEL = 11;
public static final int SPEEX_PREPROCESS_SET_DEREVERB_DECAY = 12;
public static final int SPEEX_PREPROCESS_GET_DEREVERB_DECAY = 13;
public static final int SPEEX_PREPROCESS_SET_PROB_START = 14;
public static final int SPEEX_PREPROCESS_GET_PROB_START = 15;
public static final int SPEEX_PREPROCESS_SET_PROB_CONTINUE = 16;
public static final int SPEEX_PREPROCESS_GET_PROB_CONTINUE = 17;
public static final int SPEEX_PREPROCESS_SET_NOISE_SUPPRESS = 18;
public static final int SPEEX_PREPROCESS_GET_NOISE_SUPPRESS = 19;
public static final int SPEEX_PREPROCESS_SET_ECHO_SUPPRESS = 20;
public static final int SPEEX_PREPROCESS_GET_ECHO_SUPPRESS = 21;
public static final int SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE = 22;
public static final int SPEEX_PREPROCESS_GET_ECHO_SUPPRESS_ACTIVE = 23;
public static final int SPEEX_PREPROCESS_SET_ECHO_STATE = 24;
public static final int SPEEX_PREPROCESS_GET_ECHO_STATE = 25;
public static final int SPEEX_PREPROCESS_SET_AGC_INCREMENT = 26;
public static final int SPEEX_PREPROCESS_GET_AGC_INCREMENT = 27;
public static final int SPEEX_PREPROCESS_SET_AGC_DECREMENT = 28;
public static final int SPEEX_PREPROCESS_GET_AGC_DECREMENT = 29;
public static final int SPEEX_PREPROCESS_SET_AGC_MAX_GAIN = 30;
public static final int SPEEX_PREPROCESS_GET_AGC_MAX_GAIN = 31;
public static final int SPEEX_PREPROCESS_GET_AGC_LOUDNESS = 33;
public static final int SPEEX_PREPROCESS_GET_AGC_GAIN = 35;
public static final int SPEEX_PREPROCESS_GET_PSD_SIZE = 37;
public static final int SPEEX_PREPROCESS_GET_PSD = 39;
public static final int SPEEX_PREPROCESS_GET_NOISE_PSD_SIZE = 41;
public static final int SPEEX_PREPROCESS_GET_NOISE_PSD = 43;
public static final int SPEEX_PREPROCESS_GET_PROB = 45;
public static final int SPEEX_PREPROCESS_SET_AGC_TARGET = 46;
public static final int SPEEX_PREPROCESS_GET_AGC_TARGET = 47;
}
使用及例子及所有源码
我已经将所有源代码上传于github,各位可以自行下载修改,所有speex支持的参数设置,我已经通过jni公开。只是有很多参数
我也是不得要领,不知道有什么作为!
传送门
speexdsp使用手册
官方例子:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "speex/speex_echo.h"
#include "speex/speex_preprocess.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define NN 128
#define TAIL 1024
int main(int argc, char **argv)
{
FILE *echo_fd, *ref_fd, *e_fd;
short echo_buf[NN], ref_buf[NN], e_buf[NN];
SpeexEchoState *st;
SpeexPreprocessState *den;
int sampleRate = 8000;
if (argc != 4)
{
fprintf(stderr, "testecho mic_signal.sw speaker_signal.sw output.sw\n");
exit(1);
}
echo_fd = fopen(argv[2], "rb");
ref_fd = fopen(argv[1], "rb");
e_fd = fopen(argv[3], "wb");
st = speex_echo_state_init(NN, TAIL);
den = speex_preprocess_state_init(NN, sampleRate);
speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);
speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st);
while (!feof(ref_fd) && !feof(echo_fd))
{
fread(ref_buf, sizeof(short), NN, ref_fd);
fread(echo_buf, sizeof(short), NN, echo_fd);
speex_echo_cancellation(st, ref_buf, echo_buf, e_buf);
speex_preprocess_run(den, e_buf);
fwrite(e_buf, sizeof(short), NN, e_fd);
}
speex_echo_state_destroy(st);
speex_preprocess_state_destroy(den);
fclose(e_fd);
fclose(echo_fd);
fclose(ref_fd);
return 0;
}
本地验证呢,这个nn,与tail是最难确认的了。
结束
有人使用speexdsp作了测试,有人说效果可以,实际环境中使用效果如何,有待验证。
- Speex 回声消除 frame_size和filter_length
- Speex 回声消除接口介绍
- 关于speex及speexdsp详细介绍