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

  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 点击下载

 

  

 

时间: 2024-09-16 06:46:18

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

object c-用xml传输数据就是将所有数据编到纯文本中吗?

问题描述 用xml传输数据就是将所有数据编到纯文本中吗? 小弟最近在学习python和object-c之间的交互,不知道是不是应该用xml来传输数据呢?如果是用xml传输数据,那么比如说一个数组,是不是将数组中的数据编成一个xml格式的纯文本反馈到object-c然后在object-c上解析就可以了?还是需要其他的技术?希望各位前辈能够赐教,感激不尽! 解决方案 用json吧,这个好用,更现代.python,object-c都有很好的库支持

音频编解码-speex库的使用方法

Speex是近年来开发出的一套功能强大的语音引擎,能够实现高质量和低比特率的编码.它不仅提供了基于码激励线性预测(CELP)算法的编/解码模块, 而且在其最新发布的版本中还提供了声音预处理和声学回声消除模块,为保障IP网络中的语音通信质量提供了技术手段.此外,Speex还具有压缩后的比特率 低(2~44 kbps)的特点,并支持多种比特率.这些特点使得Speex特别适合VoIP的系统. 虽然是开源的,但是使用的人还不是很多,网上的资料也很少,下面简单给大家介绍一下如何使用speex的API.  

Speex手册----Speex编/解码API的使用(libspeex)

he libspeex library contains all the functions for encoding and decoding speech with the Speex codec. When linking on a UNIX system, one must add -lspeex -lm to the compiler command line. One important thing to know is that libspeex calls are reentra

Speex手册----编解码介绍

2.1 概念 Before introducing all the Speex features, here are some concepts in speech coding that help better understand the rest of the manual. Although some are general concepts in speech/audio processing, others are specific to Speex 在介绍Speex特性之前,为了便

详解Android中的多线程断点下载

首先来看一下多线程下载的原理.多线程下载就是将同一个网络上的原始文件根据线程个数分成均等份,然后每个单独的线程下载对应的一部分,然后再将下载好的文件按照原始文件的顺序"拼接"起来就构 成了完整的文件了.这样就大大提高了文件的下载效率.对于文件下载来说,多线程下载是必须要考虑的环节. 多线程下载大致可分为以下几个步骤: 一.获取服务器上的目标文件的大小 显然这一步是需要先访问一下网络,只需要获取到目标文件的总大小即可.目的是为了计算每个线程应该分配的下载任务. 二. 在本地创建一个跟原始

信号-安卓开发 如何将float数组 进行音频编解码 转换成wav音频格式 储存到sd卡内部

问题描述 安卓开发 如何将float数组 进行音频编解码 转换成wav音频格式 储存到sd卡内部 最近正在学习安卓音频相关的一些知识,再SD卡里面读取一个wav音频文件,然后对这个文件进行噪音消除,最后输出的数据都是float格式的,这些数据用matlab中的wavwrite函数转换成wav是没有问题的,但是在安卓上如何将这些数据转换成wav还需要大神们请教,应该需要编解码的过程,如果有做这方面的大神 请知道指导...谢谢了 这段代码是将IFFT处理后的文件储存到res里面,因为是分帧进行的信号

我的Android进阶之旅------&amp;gt;Android中编解码学习笔记

编解码学习笔记(一):基本概念 媒体业务是网络的主要业务之间.尤其移动互联网业务的兴起,在运营商和应用开发商中,媒体业务份量极重,其中媒体的编解码服务涉及需求分析.应用开发.释放license收费等等.最近因为项目的关系,需要理清媒体的codec,比较搞的是,在豆丁网上看运营商的规范 标准,同一运营商同样的业务在不同文档中不同的要求,而且有些要求就我看来应当是历史的延续,也就是现在已经很少采用了.所以豆丁上看不出所以然,从 wiki上查.中文的wiki信息量有限,很短,而wiki的英文内容内多,

视音频编解码基本术语及解释

         整理了一些基本视音频术语,用于入门和查询使用. H264: H264是视频的标准,是MPEG4-10,基于内容的高效编码方式. H.264/MPEG-4第10部分,或称AVC(AdvancedVideo Coding,高级视频编码),是一种视频压缩标准,一种被广泛使用的高精度视频的录制.压缩和发布格式.第一版标准的最终草案于2003年5月完成. H.264/MPEG-4 AVC是一种面向块的基于运动补偿的编解码器标准.由ITU-T视频编码专家组与ISO/IEC联合工作组--即动

[总结]FFMPEG视音频编解码零基础学习方法--转

ffmpeg编解码学习   目录(?)[-] ffmpeg程序的使用ffmpegexeffplayexeffprobeexe 1 ffmpegexe 2 ffplayexe 3 ffprobeexe ffmpeg库的使用视频播放器 1 ffmpeg库的配置 2 最简单的视频播放器 3 相关结构体的研究 ffmpeg库的使用音频播放器 1 最简单的音频播放器 ffmpeg库的使用一个真正的播放器ffplay 1 真正的播放器 ffmpeg库的使用编码 1 编码 2 转码 ffmpeg源代码分析 F