音频数据编解码——在.NET中使用Speex(附下载)

简介:   Speex是一套开源的音频编解码库,最新版本还包含了回音消除和防抖动等功能,如果我们想开发语音聊天或视频会议这样的系统,Speex将是一个不错的选择。到 http://www.speex.org可以下载Speex的源码(编译后的dll为libspeex.dll),最新版本为1.2。

  Speex是一套开源的音频编解码库,最新版本还包含了回音消除和防抖动等功能,如果我们想开发语音聊天或视频会议这样的系统,Speex将是一个不错的选择。到 http://www.speex.org可以下载Speex的源码(编译后的dll为libspeex.dll),最新版本为1.2。不过源码是用C++开发的,直接在.NET中使用会有诸多不便,为此,我用C#将其封装,使得编解码的调用相当简单。

  由于Speex原始导出的API不是很方便C#调用,所以,在用C#封装之前,先要用C++对Speex的原始API进行简化,新建一个名为Speex的VC项目,然后引用libspeex.dll的相关库文件,添加cpp文件后,复制下列源码到文件中:

#include  " speex\speex.h "
#include 
< windows.h >
#include 
< stdio.h >
#include 
< stdlib.h >

#include 
" speex/speex_echo.h "
#include 
" speex/speex_preprocess.h "  
#include 
" Speex.h "

#define  FRAME_SIZE 160

float  encoder_input[FRAME_SIZE];
void   * encoder_state;
SpeexBits encoder_bits;

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    
return  TRUE;
}
 
extern   " C "  __declspec(dllexport)  void  encoder_init( int  quality)
{
    encoder_state 
=  speex_encoder_init( & speex_nb_mode);
    speex_encoder_ctl(encoder_state, SPEEX_SET_QUALITY, 
& quality);
    speex_bits_init(
& encoder_bits);
}

extern   " C "  __declspec(dllexport)  void  encoder_dispose()
{
    speex_encoder_destroy(encoder_state);
    speex_bits_destroy(
& encoder_bits);
}

extern   " C "  __declspec(dllexport)  int  encoder_encode( const   short   * data,  char   * output)
{
    
for  ( int  i  =   0 ; i  <  FRAME_SIZE; i ++ )
        encoder_input[i] 
=  data[i];
    speex_bits_reset(
& encoder_bits);
    speex_encode(encoder_state, encoder_input, 
& encoder_bits);
    
return  speex_bits_write( & encoder_bits, output,  200 );
}


float  decoder_output[FRAME_SIZE];
void   * decoder_state;
SpeexBits decoder_bits;

extern   " C "  __declspec(dllexport)  void  decoder_init()
{
    decoder_state 
=  speex_decoder_init( & speex_nb_mode);
    
int  tmp  =   1 ;
    speex_decoder_ctl(decoder_state, SPEEX_SET_ENH, 
& tmp);
    speex_bits_init(
& decoder_bits);
}
extern   " C "  __declspec(dllexport)  void  decoder_dispose()
{
    speex_decoder_destroy(decoder_state);
    speex_bits_destroy(
& decoder_bits);
}
extern   " C "  __declspec(dllexport)  void  decoder_decode( int  nbBytes,  char   * data,  short   * output)
{
    speex_bits_read_from(
& decoder_bits, data, nbBytes);
    speex_decode(decoder_state, 
& decoder_bits, decoder_output);
    
for  ( int  i  =   0 ; i  <  FRAME_SIZE; i ++ )
    {
        output[i] 
=  decoder_output[i];
    }
}


/* **************************************************  回音消除 ************************************* */

bool       m_bSpeexEchoHasInit;
SpeexEchoState
*    m_SpeexEchoState;
SpeexPreprocessState
*  m_pPreprocessorState;
int       m_nFilterLen;
int       m_nSampleRate;
float *    m_pfNoise;

extern   " C "  __declspec(dllexport)  void  SpeexEchoCapture( short *  input_frame,  short *  output_frame)
{
    speex_echo_capture(m_SpeexEchoState, input_frame, output_frame);
}

extern   " C "  __declspec(dllexport)  void  SpeexEchoPlayback( short *  echo_frame)
{
    speex_echo_playback(m_SpeexEchoState, echo_frame);
}

extern   " C "  __declspec(dllexport)  void  SpeexEchoReset()
{
    
if  (m_SpeexEchoState  !=  NULL)
    {
        speex_echo_state_destroy(m_SpeexEchoState);
        m_SpeexEchoState 
=  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_bSpeexEchoHasInit 
=   false ;
}

extern   " C "  __declspec(dllexport)  void  SpeexEchoInit( int  filter_length,  int  sampling_rate , bool  associatePreprocesser)
{
    SpeexEchoReset(); 

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

    m_SpeexEchoState 
=  speex_echo_state_init(FRAME_SIZE, m_nFilterLen);
    m_pPreprocessorState 
=  speex_preprocess_state_init(FRAME_SIZE, m_nSampleRate);
    
if (associatePreprocesser)
    {
        speex_preprocess_ctl(m_pPreprocessorState, SPEEX_PREPROCESS_SET_ECHO_STATE,m_SpeexEchoState);
    }
    m_pfNoise 
=   new   float [FRAME_SIZE + 1 ];
    m_bSpeexEchoHasInit 
=   true ;
}

extern   " C "  __declspec(dllexport)  void  SpeexEchoDoAEC( short *  mic,  short *   ref short *   out )
{
    
if  ( ! m_bSpeexEchoHasInit)
    {
      
return ;
    }

    speex_echo_cancellation(m_SpeexEchoState,(
const  __int16  * ) mic,( const  __int16  * ref ,(__int16  * out );
    
}

  编译便生成Speex.dll。

  如果对VC不熟悉也没关系,文末会直接给出libspeex.dll和Speex.dll的下载,直接使用就OK了。

 

  现在,C#可以调用Speex.dll导出的简单函数了,最终封装的源码如下:

    ///   <summary>
    
///  对Speex的C#封装。
    
///  zhuweisky 2010.05.13
    
///   </summary>
     public   class  Speex :IAudioCodec
    {
        
private   const   int  FrameSize  =   160 ;

        
#region  IsDisposed
        
private   volatile   bool  isDisposed  =   false ;
        
public   bool  IsDisposed
        {
            
get  {  return  isDisposed; }
        } 
        
#endregion

        
#region  Ctor
        
///   <summary>
        
///  初始化。
        
///   </summary>
        
///   <param name="quality"> 编码质量,取值0~10 </param>
         public  Speex( int  quality)
        {
            
if  (quality  <   0   ||  quality  >   10 )
            {
                
throw   new  Exception( " quality value must be between 0 and 10. " );
            }

            Speex.encoder_init(quality);
            Speex.decoder_init();
        }
        
#endregion

        
#region  Dispose
        
public   void  Dispose()
        {
            
this .isDisposed  =   true ;
            System.Threading.Thread.Sleep(
100 );
            Speex.decoder_dispose();
            Speex.encoder_dispose();
        }
        
#endregion

        
#region  Encode
        
///   <summary>
        
///  将采集到的音频数据进行编码。
        
///   </summary>        
         public   byte [] Encode( byte [] data)
        {
            
if  ( this .isDisposed)
            {
                
return   null ;
            }

            
if  (data.Length  %  (FrameSize  *   2 !=   0 )
            {
                
throw   new  ArgumentException( " Invalid Data Length. " );
            }

            
int  nbBytes;
            
short [] input  =   new   short [FrameSize];
            
byte [] buffer  =   new   byte [ 200 ];
            
byte [] output  =   new   byte [ 0 ];
            
for  ( int  i  =   0 ; i  <  data.Length  /  (FrameSize  *   2 ); i ++ )
            {
                
for  ( int  j  =   0 ; j  <  input.Length; j ++ )
                {
                    input[j] 
=  ( short )(data[i  *  FrameSize  *   2   +  j  *   2 +  data[i  *  FrameSize  *   2   +  j  *   2   +   1 *   0x100 );
                }

                nbBytes 
=  Speex.encoder_encode(input, buffer);
                Array.Resize
< byte > ( ref  output, output.Length  +  nbBytes  +   sizeof ( int ));
                Array.Copy(buffer, 
0 , output, output.Length  -  nbBytes, nbBytes);

                
for  ( int  j  =   0 ; j  <   sizeof ( int ); j ++ )
                {
                    output[output.Length 
-  nbBytes  -   sizeof ( int +  j]  =  ( byte )(nbBytes  %   0x100 );
                    nbBytes 
/=   0x100 ;
                }
            }
            
return  output;
        }
        
#endregion

        
#region  Decode
        
///   <summary>
        
///  将编码后的数据进行解码得到原始的音频数据。
        
///   </summary>       
         public   byte [] Decode( byte [] data)
        {
            
if  ( this .isDisposed)
            {
                
return   null ;
            }

            
int  nbBytes, index  =   0 ;
            
byte [] input;
            
short [] buffer  =   new   short [FrameSize];
            
byte [] output  =   new   byte [ 0 ];
            
while  (index  <  data.Length)
            {
                nbBytes 
=   0 ;
                index 
+=   sizeof ( int );
                
for  ( int  i  =   1 ; i  <=   sizeof ( int ); i ++ )
                    nbBytes 
=  nbBytes  *   0x100   +  data[index  -  i];
                input 
=   new   byte [nbBytes];
                Array.Copy(data, index, input, 
0 , input.Length);
                index 
+=  input.Length;
                Speex.decoder_decode(nbBytes, input, buffer);
                Array.Resize
< byte > ( ref  output, output.Length  +  FrameSize  *   2 );
                
for  ( int  i  =   0 ; i  <  FrameSize; i ++ )
                {
                    output[output.Length 
-  FrameSize  *   2   +  i  *   2 =  ( byte )(buffer[i]  %   0x100 );
                    output[output.Length 
-  FrameSize  *   2   +  i  *   2   +   1 =  ( byte )(buffer[i]  /   0x100 );
                }
            }
            
return  output;
        }
        
#endregion

        
#region  Pinvoke
        [DllImport(
" Speex.dll " , EntryPoint  =   " encoder_init " )]
        
internal   extern   static   void  encoder_init( int  quality);
        [DllImport(
" Speex.dll " , EntryPoint  =   " encoder_dispose " )]
        
internal   extern   static   void  encoder_dispose();
        [DllImport(
" Speex.dll " , EntryPoint  =   " encoder_encode " )]
        
internal   extern   static   int  encoder_encode( short [] data,  byte [] output);
        [DllImport(
" Speex.dll " , EntryPoint  =   " decoder_init " )]
        
internal   extern   static   void  decoder_init();
        [DllImport(
" Speex.dll " , EntryPoint  =   " decoder_dispose " )]
        
internal   extern   static   void  decoder_dispose();
        [DllImport(
" Speex.dll " , EntryPoint  =   " decoder_decode " )]
        
internal   extern   static   void  decoder_decode( int  nbBytes,  byte [] data,  short [] output);      
        
#endregion
    }

    只有四个方法:Initialize、Encode、Decode、Dispose。方法参数的含义也非常明显。

  一般音频对话的整个流程是这样的:采集 -> 编码 -> 网络传输 -> 解码 -> 播放。参见:《浅谈网络语音技术》

  而该封装的Speex类解决了这个过程中的音频编码和解码的问题。你可以复制该源码到你的项目,并将从http://www.speex.org下载的speex.dll放到运行目录下,就可以正常地使用SPEEX的编解码功能了。

  关于Speex更高级的功能,我正在研究中,有兴趣的朋友可以email给我一起探讨。  

 

2012.11.20 我们的研究成果已经全部集成到了OMCS中,其支持回音消除(AEC)、静音检测(VAD)、噪音抑制(DENOISE)、自动增益(AGC)等网络语音技术,有兴趣的可以了解一下。

2014.04.17 Speex dll 点击下载

 

  

 

目录
相关文章
|
7月前
mvc.net分页查询案例——DLL数据访问层(HouseDLL.cs)
mvc.net分页查询案例——DLL数据访问层(HouseDLL.cs)
|
2月前
|
SQL XML 关系型数据库
入门指南:利用NHibernate简化.NET应用程序的数据访问
【10月更文挑战第13天】NHibernate是一个面向.NET的开源对象关系映射(ORM)工具,它提供了从数据库表到应用程序中的对象之间的映射。通过使用NHibernate,开发者可以专注于业务逻辑和领域模型的设计,而无需直接编写复杂的SQL语句来处理数据持久化问题。NHibernate支持多种数据库,并且具有高度的灵活性和可扩展性。
43 2
|
4月前
|
开发框架 .NET 数据库连接
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
|
4月前
|
C#
一款.NET开源、跨平台的DASH/HLS/MSS下载工具
一款.NET开源、跨平台的DASH/HLS/MSS下载工具
|
5月前
|
开发框架 JSON 前端开发
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
|
4月前
|
开发框架 前端开发 算法
分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法
分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法
|
6月前
|
安全 程序员 Shell
老程序员分享:NSIS自定义界面,下载并安装Net.Framework4.8
老程序员分享:NSIS自定义界面,下载并安装Net.Framework4.8
|
7月前
|
存储 测试技术 计算机视觉
高维数据惩罚回归方法:主成分回归PCR、岭回归、lasso、弹性网络elastic net分析基因数据
高维数据惩罚回归方法:主成分回归PCR、岭回归、lasso、弹性网络elastic net分析基因数据
|
7月前
|
存储 移动开发 前端开发
对象存储oss使用问题之OSS SDK .net 使用下载例程报错如何解决
《对象存储OSS操作报错合集》精选了用户在使用阿里云对象存储服务(OSS)过程中出现的各种常见及疑难报错情况,包括但不限于权限问题、上传下载异常、Bucket配置错误、网络连接问题、跨域资源共享(CORS)设定错误、数据一致性问题以及API调用失败等场景。为用户降低故障排查时间,确保OSS服务的稳定运行与高效利用。
112 0
|
7月前
|
安全 C# 开发者
.NET开源的一键自动化下载、安装、激活Microsoft Office利器
.NET开源的一键自动化下载、安装、激活Microsoft Office利器
164 5