音频数据编解码——在.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 点击下载

 

  

 

目录
相关文章
|
6月前
|
C++ Windows
.NET Framework安装不成功,下载`NET Framework 3.5`文件,Microsoft Visual C++
.NET Framework常见问题及解决方案汇总,涵盖缺失组件、安装失败、错误代码等,提供多种修复方法,包括全能王DLL修复工具、微软官方运行库及命令行安装等,适用于Windows系统,解决应用程序无法运行问题。
969 3
|
3月前
|
开发框架 安全 .NET
Microsoft .NET Framework 3.5、4.5.2、4.8.1,适用于 Windows 版本的 .NET,Microsoft C Runtime等下载
.NET Framework是Windows平台的开发框架,包含CLR和FCL,支持多种语言开发桌面、Web应用。常用版本有3.5、4.5.2、4.8.1,系统可同时安装多个版本,确保软件兼容运行。
899 0
Microsoft .NET Framework 3.5、4.5.2、4.8.1,适用于 Windows 版本的 .NET,Microsoft C Runtime等下载
|
5月前
.NET Framework 3.5离线安装包合集下载
本文介绍了如何获取和安装.NET Framework运行库离线合集包。用户可通过提供的链接下载安装包,安装过程简单,按提示逐步操作即可完成。安装时可选择所需版本,工具会自动适配架构,无需手动判断,方便高效。
4584 0
|
10月前
|
存储 XML 开发工具
【Azure Storage Account】利用App Service作为反向代理, 并使用.NET Storage Account SDK实现上传/下载操作
本文介绍了如何在Azure上使用App Service作为反向代理,以自定义域名访问Storage Account。主要内容包括: 1. **设置反向代理**:通过配置`applicationhost.xdt`和`web.config`文件,启用IIS代理功能并设置重写规则。 2. **验证访问**:测试原生URL和自定义域名的访问效果,确保两者均可正常访问Storage Account。 3. **.NET SDK连接**:使用共享访问签名(SAS URL)初始化BlobServiceClient对象,实现通过自定义域名访问存储服务。
174 1
|
9月前
|
网络协议 定位技术 网络安全
IPIP.NET-IP地理位置数据
IPIP.NET 是一家专注于 IP 地理位置数据的提供商,基于 BGP/ASN 数据与全球 800+ 网络监测点技术,提供高精度的 IPv4 和 IPv6 定位服务。其核心服务包括地理位置查询、详细地理信息和网络工具等,广泛应用于网络安全、广告营销、CDN 优化等领域。数据覆盖全球,支持多语言,每日更新确保实时性。IPIP.NET 提供 API 接口、离线数据库及多种语言 SDK,方便开发者集成使用。
1769 0
|
SQL XML 关系型数据库
入门指南:利用NHibernate简化.NET应用程序的数据访问
【10月更文挑战第13天】NHibernate是一个面向.NET的开源对象关系映射(ORM)工具,它提供了从数据库表到应用程序中的对象之间的映射。通过使用NHibernate,开发者可以专注于业务逻辑和领域模型的设计,而无需直接编写复杂的SQL语句来处理数据持久化问题。NHibernate支持多种数据库,并且具有高度的灵活性和可扩展性。
352 2
|
开发框架 .NET 数据库连接
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
253 1
一款.NET开源、跨平台的DASH/HLS/MSS下载工具
一款.NET开源、跨平台的DASH/HLS/MSS下载工具
202 1
|
开发框架 JSON 前端开发
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理

热门文章

最新文章