在.NET中使用Speex -- 音訊資料編解碼 (轉)

iDotNetSpace發表於2010-09-16
 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檔案後,複製下列原始碼到檔案中:

<!--Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--&gt#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* refshort* 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匯出的簡單函式了,最終封裝的原始碼如下:

<!--Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--&gt    /// 
    
/// 對Speex的C#封裝。
    
/// zhuweisky 2010.05.13
    
/// 
    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
        
/// 
        
/// 初始化。
        
/// 
        
/// 編碼質量,取值0~10
        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
        
/// 
        
/// 將採集到的音訊資料進行編碼。
        
///        
        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
        
/// 
        
/// 將編碼後的資料進行解碼得到原始的音訊資料。
        
///       
        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給我一起探討。  

 

  下載 libspeex.dll和Speex.dll

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-674149/,如需轉載,請註明出處,否則將追究法律責任。

相關文章