在.NET中使用Speex -- 音訊資料編解碼 (轉)
Speex是一套開源的音訊編解碼庫,最新版本還包含了迴音消除和防抖動等功能,如果我們想開發語音聊天或視訊會議這樣的系統,Speex將是一個不錯的選擇。到 http://www.speex.org可以下載Speex的原始碼(編譯後的dll為libspeex.dll),最新版本為1.2。不過原始碼是用C++開發的,直接在.NET中使用會有諸多不便,為此,我用C#將其封裝,使得編解碼的呼叫相當簡單。
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原始匯出的API不是很方便C#呼叫,所以,在用C#封裝之前,先要用C++對Speex的原始API進行簡化,新建一個名為Speex的VC專案,然後引用libspeex.dll的相關庫檔案,新增cpp檔案後,複製下列原始碼到檔案中:
<!--Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->#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;
}
#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匯出的簡單函式了,最終封裝的原始碼如下:
<!--Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> ///
/// 對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
}
/// 對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給我一起探討。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-674149/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 音訊編解碼-speex庫的使用方法音訊
- 音訊編解碼標準音訊
- 音訊編解碼·實戰篇(1)WAV轉至AAC(AAC編碼)音訊
- 音訊編解碼·實戰篇(1)PCM轉至AAC(AAC編碼)音訊
- 音訊編碼基礎詳解音訊
- Android 音視訊 - MediaCodec 編解碼音視訊Android
- ffmpeg音訊編碼之pcm轉碼aac音訊
- Mac 使用音訊工具分析音訊資料Mac音訊
- Android 音視訊開發 視訊編碼,音訊編碼格式Android音訊
- Netty 中的訊息解析和編解碼器Netty
- FFMPEG視音訊編解碼學習(1)音訊
- 各種音視訊編解碼學習詳解
- Android如何回撥編碼後的音視訊資料Android
- 各種音訊視訊編碼方法音訊
- 在ASP.NET中,向資料庫批次插入資料 (轉)ASP.NET資料庫
- FFmpeg音訊解碼音訊
- AVAssetWriter視訊資料編碼
- Android音視訊(四)MediaCodec編解碼AACAndroid
- 音訊後設資料編輯器:Tagr for Mac音訊Mac
- 音訊編碼基礎知識音訊
- FFmpeg命令影片音訊轉碼引數詳解音訊
- 再談Net Framework中的編解碼Framework
- 音影片編解碼技術在直播平臺中是如何運用的?
- DM6446的音訊編解碼及播放實現音訊
- ffmpeg解碼音訊流音訊
- iOS socket通訊,編解碼,浮點型資料解析iOS
- 音訊後設資料編輯器Metadatas for Mac音訊Mac
- Metadatics for Mac音訊後設資料編輯工具Mac音訊
- 什麼是Netty編解碼,Netty編解碼器有哪些?Protostuff怎麼使用?Netty
- 轉載:iOS音視訊實時採集硬體編碼iOS
- 在VB中編輯資料庫和電子表格 (轉)資料庫
- FFmpeg開發筆記(八):ffmpeg解碼音訊並使用SDL同步音訊播放筆記音訊
- 音訊解碼基礎講解音訊
- 在.NET Framework中輕鬆處理XML資料(一) (轉)FrameworkXML
- 在.NET Framework中輕鬆處理XML資料(五) (轉)FrameworkXML
- 音訊編解碼·格式篇(1)Wave PCM audio format(WAV)音訊ORM
- 使用jave1.0.2.jar進行音視訊轉碼JAR
- C# / VB.NET 在Word中嵌入多媒體(視訊、音訊)檔案C#音訊