Speex 一个开源的声学回声消除器(Acoustic Echo Cancellation)

简介:
在一般的VOIP软件或视频会议系统中,假设我们只有A和B两个人在通话,首先,A的声音传给B,B然后用喇叭放出来,而这时B的MIC呢则会采集到喇叭放出来的声音,然后传回给A,如果这个传输的过程中时延足够大,A就会听到一个和自己刚才说过的话一样的声音,这就是回声,声学回声消除器的作用就是在B端对B采集到的声音进行处理,把采集到声音包含的A的声音去掉再传给A,这样,A就不会听到自己说过的话了。
声学回声消除的原理我就不说了,这在网上有很多文档,网上缺少的是实现,所以,我在这把一个开源的声学回声消除器介绍一下,希望对有些有人用,如果有人知道怎么把这消除器用的基于实时流的VOIP软件中,希望能一起分享一下。
这个声学回声消除器是一个著名的音频编解码器speex中的一部分,1.1.9版本后的回声消除器才起作用,以前版本的都不行,我用的也是这个版本,测试表明,用同一个模拟文件,它有效果比INTEL IPP库4.1版中的声学回声消除器的还要好。
先说编译。首先,从 www.speex.org 上下载speex1.1.9的源代码,解压,打开speex/win32/libspeex中的libspeex.dsw,这个工作区里有两个工程,一个是libspeex,另一个是libspeex_dynamic。然后,将libspeex中的mdf.c文件添加到工程libspeex中,编译即可。
以下是我根据文档封装的一个类,里面有一个测试程序:
 //file name: speexEC.h
#ifndef SPEEX_EC_H
#define  SPEEX_EC_H
#include 
< stdio.h >
#include 
< stdlib.h >
#include 
" speex/speex_echo.h "
#include 
" speex/speex_preprocess.h "  
class  CSpeexEC
{
public:
CSpeexEC();
~CSpeexEC();
void Init(int frame_size=160int filter_length=1280int sampling_rate=8000); 
void DoAEC(short *mic, short *refshort *out);

protected:
void Reset();

private:
bool      m_bHasInit;
SpeexEchoState
*   m_pState;
    SpeexPreprocessState
* m_pPreprocessorState;
int      m_nFrameSize;
int      m_nFilterLen;
int      m_nSampleRate;
float*      m_pfNoise;
}
;

#endif

//fine name:speexEC.cpp
#include  " SpeexEC.h "

CSpeexEC::CSpeexEC()
{
m_bHasInit   
= false;
m_pState   
= NULL;
m_pPreprocessorState  
= NULL;
m_nFrameSize   
= 160;
m_nFilterLen   
= 160*8;
m_nSampleRate   
= 8000;
m_pfNoise   
= NULL;
}


CSpeexEC::
~ CSpeexEC()
{
Reset();
}


void  CSpeexEC::Init( int  frame_size,  int  filter_length,  int  sampling_rate)
{
Reset(); 

if (frame_size<=0 || filter_length<=0 || sampling_rate<=0)
{
  m_nFrameSize  
=160;
  m_nFilterLen  
= 160*8;
  m_nSampleRate 
= 8000;
}

else
{
  m_nFrameSize  
=frame_size;
  m_nFilterLen  
= filter_length;
  m_nSampleRate 
= sampling_rate;
}


m_pState 
= speex_echo_state_init(m_nFrameSize, m_nFilterLen);
m_pPreprocessorState 
= speex_preprocess_state_init(m_nFrameSize, m_nSampleRate);
m_pfNoise 
= new float[m_nFrameSize+1];
m_bHasInit 
= true;
}


void  CSpeexEC::Reset()
{
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;
}

if (m_pfNoise != NULL)
{
  delete []m_pfNoise;
  m_pfNoise 
= NULL;
}

m_bHasInit 
= false;
}


void  CSpeexEC:DoAEC( short *  mic,  short *   ref short *   out )
{
if (!m_bHasInit)
  
return;

speex_echo_cancel(m_pState, mic, 
refout, m_pfNoise);
    speex_preprocess(m_pPreprocessorState, (__int16 
*)out, m_pfNoise);
    
}

可以看出,这个回声消除器类很简单,只要初始化一下就可以调用了。但是,要注意的是,传给回声消除器的两个声音信号,必须同步得非常的好,就是说,在B端,接收到A说的话以后,要把这些话音数据传给回声消除器做参考,然后再传给声卡,声卡再放出来,这有一段延时,这时,B再采集,然后传给回声消除器,与那个参考数据比较,从采集到的数据中把频域和参考数据相同的部分消除掉。如果传给消除器的两个信号同步得不好,即两个信号找不到频域相同的部分,就没有办法进行消除了。
测试程序:
#define  NN 160
void  main()
{
FILE
* ref_fd, *mic_fd, *out_fd;
short ref[NN], mic[NN], out[NN];
ref_fd 
= fopen ("ref.pcm""rb"); //打开参考文件,即要消除的声音
mic_fd = fopen ("mic.pcm",  "rb");//打开mic采集到的声音文件,包含回声在里面
out_fd = fopen ("echo.pcm""wb");//消除了回声以后的文件

CSpeexEC ec;
ec.Init();

while (fread(mic, 1, NN*2, mic_fd))
   
{
      fread(
ref1, NN*2, ref_fd);  
      ec.DoAEC(mic, 
refout);
      fwrite(
out1, NN*2, out_fd);
   }

  
   fclose(ref_fd);
   fclose(mic_fd);
   fclose(out_fd);
}
以上的程序是用文件来模拟回声和MIC,但在实时流中是大不一样的,在一般的VOIP软件中,接收对方的声音并传到声卡中播放是在一个线程中进行的,而采集本地的声音并传送到对方又是在另一个线程中进行的,而声学回声消除器在对采集到的声音进行回声消除的同时,还需要播放线程中的数据作为参考,而要同步这两个线程中的数据是非常困难的,因为稍稍有些不同步,声学回声消除器中的自适应滤波器就会发散,不但消除不了回声,还会破坏原始采集到的声音,使被破坏的声音难以分辨。我做过好多尝试,始终无法用软件来实现对这两个线程中的数据进行同步,导致实现失败,希望有经验的网友们一起分享一下这方面的经验。

示例代码:

Sample code

This section shows sample code for encoding and decoding speech using the Speex API. The commands can be used to encode and decode a file by calling:  
% sampleenc in_file.sw | sampledec out_file.sw 
where both files are raw (no header) files encoded at 16 bits per sample (in the machine natural endianness).

 

sampleenc.c

sampleenc takes a raw 16 bits/sample file, encodes it and outputs a Speex stream to stdout. Note that the packing used is NOT compatible with that of speexenc/speexdec.

#include  < speex / speex.h >
#include 
< stdio.h >
/*The frame size in hardcoded for this sample code but it doesn't have to be*/
#define  FRAME_SIZE 160
int  main( int  argc,  char   ** argv)
{
char *inFile;
FILE 
*fin;
short in[FRAME_SIZE];
float input[FRAME_SIZE];
char cbits[200];
int nbBytes;
/*Holds the state of the encoder*/
void *state;
/*Holds bits so they can be read and written to by the Speex routines*/
SpeexBits bits;
int i, tmp;
/*Create a new encoder state in narrowband mode*/
state 
= speex_encoder_init(&speex_nb_mode);
/*Set the quality to 8 (15 kbps)*/
tmp
=8;
speex_encoder_ctl(state, SPEEX_SET_QUALITY, 
&tmp);
inFile 
= argv[1];
fin 
= fopen(inFile, "r");
/*Initialization of the structure that holds the bits*/
speex_bits_init(
&bits);
while (1)
{
/*Read a 16 bits/sample audio frame*/
fread(
insizeof(short), FRAME_SIZE, fin);
if (feof(fin))
break;
/*Copy the 16 bits values to float so Speex can work on them*/
for (i=0;i<FRAME_SIZE;i++)
input[i]
=in[i];
/*Flush all the bits in the struct so we can encode a new frame*/
speex_bits_reset(
&bits);
/*Encode the frame*/
speex_encode(state, input, 
&bits);
/*Copy the bits to an array of char that can be written*/
nbBytes 
= speex_bits_write(&bits, cbits, 200);
/*Write the size of the frame first. This is what sampledec expects but
it's likely to be different in your own application
*/

fwrite(
&nbBytes, sizeof(int), 1, stdout);
/*Write the compressed data*/
fwrite(cbits, 
1, nbBytes, stdout);
}

/*Destroy the encoder state*/
speex_encoder_destroy(state);
/*Destroy the bit-packing struct*/
speex_bits_destroy(
&bits);
fclose(fin);
return 0;
}

 

sampledec.c

sampledec reads a Speex stream from stdin, decodes it and outputs it to a raw 16 bits/sample file. Note that the packing used is NOT compatible with that of speexenc/speexdec.

#include  < speex / speex.h >
#include 
< stdio.h >
/*The frame size in hardcoded for this sample code but it doesn't have to be*/
#define  FRAME_SIZE 160
int  main( int  argc,  char   ** argv)
{
char *outFile;
FILE 
*fout;
/*Holds the audio that will be written to file (16 bits per sample)*/
short out[FRAME_SIZE];
/*Speex handle samples as float, so we need an array of floats*/
float output[FRAME_SIZE];
char cbits[200];
int nbBytes;
/*Holds the state of the decoder*/
void *state;
/*Holds bits so they can be read and written to by the Speex routines*/
SpeexBits bits;
int i, tmp;
/*Create a new decoder state in narrowband mode*/
state 
= speex_decoder_init(&speex_nb_mode);
/*Set the perceptual enhancement on*/
tmp
=1;
speex_decoder_ctl(state, SPEEX_SET_ENH, 
&tmp);
outFile 
= argv[1];
fout 
= fopen(outFile, "w");
/*Initialization of the structure that holds the bits*/
speex_bits_init(
&bits);
while (1)
{
/*Read the size encoded by sampleenc, this part will likely be
different in your application
*/

fread(
&nbBytes, sizeof(int), 1, stdin);
fprintf (stderr, 
"nbBytes: %d/n", nbBytes);
if (feof(stdin))
break;
/*Read the "packet" encoded by sampleenc*/
fread(cbits, 
1, nbBytes, stdin);
/*Copy the data into the bit-stream struct*/
speex_bits_read_from(
&bits, cbits, nbBytes);
/*Decode the data*/
speex_decode(state, 
&bits, output);
/*Copy from float to short (16 bits) for output*/
for (i=0;i<FRAME_SIZE;i++)
out[i]=output[i];
/*Write the decoded audio to file*/
fwrite(
outsizeof(short), FRAME_SIZE, fout);
}

/*Destroy the decoder state*/
speex_decoder_destroy(state);
/*Destroy the bit-stream truct*/
speex_bits_destroy(
&bits);
fclose(fout);
return 0;
}
目录
相关文章
|
7天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1922 6
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
5天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
652 111
|
15天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2556 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
7天前
|
人工智能 弹性计算 数据库
阿里云优惠券种类解析:主要券种区别和适用群体及领取和使用指南
2026年阿里云构建了覆盖全用户的七类优惠券,本文逐一拆解了每类优惠券的核心规则、适用人群与使用技巧:大促限定的阶梯满减券分个人、企业双通道,最高可减800元;学生专属300元无门槛券支持全品类通用;按量付费用户可参与消费达标返券形成循环优惠;新用户有低门槛专享满减券尝鲜;老用户可领取系统自动发放的随机福利券;中大型企业迁云可申请最高100万元的专项补贴;云产品通用券还能在活动价基础上实现折上折。不同身份、不同采购场景的用户均可通过精准匹配对应优惠券,最大化享受优惠力度。
462 110
阿里云优惠券种类解析:主要券种区别和适用群体及领取和使用指南
|
13天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
1623 2
|
15天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
1428 2
|
17天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
1500 55
|
2天前
Qoder 一周年 × Qwen3.8-Max 正式上线,多重好礼限时领
8月3日,Qwen3.8-Max 正式上线Qoder,迎来Qoder一周年。新老用户可领800次免费调用,下单再赠2000次;夜间(22:00–08:00)调用5折;邀请好友双方得积分与调用额度。
250 0