65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的 Speex

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.83/5 (11投票s)

2007年8月18日

3分钟阅读

viewsIcon

118295

downloadIcon

3889

在 .NET 框架中使用 Speex 语音编解码器

Screenshot - Hierarchy.png

引言

我一直在用 C# 开发一个语音聊天程序,并遇到了一个令人烦恼的问题。 未压缩的音频数据根本不适用于聊天程序,而且我能找到的所有 .NET 语音压缩解决方案都非常昂贵。 Speex 是一种免授权的开源语音编解码器,似乎是显而易见的选择,但详尽的搜索并没有找到该库的 C# 实现。 在 SourceForge 上快速搜索到了一些此类项目,但它们都不完整且早已被放弃。 对于考虑尝试同样壮举的人来说,这不是一个令人欣慰的事实。

当然,如果我能够看到它,一个非常简单的解决方案一直可用。 Speex 网站提供了两个命令行实用程序,speexencspeexdec。 因为我正在寻找一个编程解决方案,所以我甚至从未想过要查看这些实用程序的语法。 如果我这样做了,我就会看到从 .NET 代码中使用这些实用程序是多么容易。

什么是 Speex?

Speex 是一种免授权的开源语音编解码器。 它用于将音频数据压缩成更小的格式,这对于通过互联网传输语音非常有利。 请记住,它通常对于非语音数据效率不高。 这是因为(根据维基百科)语音编解码器通过消除人类声音无法发出的频率以及人类听不到的频率来工作。 由于可用频率的数量减少,音频数据可以以更紧凑的形式存储。 Speex 通常用于 VoIP 程序和其他类似的互联网语音应用程序中,但它也可以简单地用于减少计算机上文件的大小。

更多信息请访问 speex.org

Using the Code

这段代码使用起来非常简单。 它包含一个异常类、两个用于存储各种数据的结构,以及一个包含两个方法的类:encodedecode。 它还使用了 Sujoy G. 的 clsWaveProcessor 类的一个经过大量修改的版本,该类来自他的文章 C# 中的 Wave 文件处理器。 我添加了一个包含原始 PCM 数据的新字段,删除了除 WaveHeaderIN 之外的所有方法,并编辑了该方法以处理流而不是文件名。 这是程序的核心,Codec 类。

public class Codec
{
    public EncodeReturn Encode(byte[] raw, int bytespersecond,
        int samplespersecond, bool stereo,
        short bitspersample, bool denoise, bool agc)
    {
        //Start speexenc process
        Process encProc = Process.Start("speexenc",
            "-u " + //Ultra wide-band
            (denoise ? "--denoise " : "") + //Denoise before encode
            "--agc " + //Addaptive gain control before encode
            "--bitrate " + bytespersecond * 8 + " " + //Set the bitrate
            "--rate " + samplespersecond + " " + //Set the sample rate
            (stereo ? "--stereo " : "") + //Set the channel count
            (bitspersample != 16 ? "--8bit " : "") + //
            "con con"); //Set console input and output

        //Writes the raw audio data to encproc's StdIn one byte at a time
        foreach (byte b in raw)
        {
            encProc.StandardInput.BaseStream.WriteByte(b);
        }

        //Wait, to ensure that all output has been written
        encProc.WaitForExit();

        //Check for success
        if (encProc.ExitCode != 0)
            throw new EncodeDecodeFailureException(encProc.ExitCode);

        //Skip the first line
        encProc.StandardOutput.ReadLine();

        //Remove output
        BinaryReader br = new BinaryReader(encProc.StandardOutput.BaseStream);

        byte[] retB = new byte[encProc.StandardOutput.BaseStream.Length];

        //In non-verbose mode, the first line of output is the only line on
        //non-audio data
        encProc.StandardOutput.ReadLine();

        //Read the output
        int k = 0;
        while (!encProc.StandardOutput.EndOfStream)
        {
            retB[k++] = br.ReadByte();
        }

        //Clean up
        br.Close();

        //Create the return object
        EncodeReturn retVal = new EncodeReturn(retB);

        //And return it
        return retVal;

    }

    public DecodeReturn Decode(byte[] raw)
    {
        //Create and start the decoding process
        Process decProc = Process.Start("speexdec", "--force-uwb con con");

        //Writes the raw audio data to encproc's StdIn one byte at a time
        foreach (byte b in raw)
        {
            decProc.StandardInput.BaseStream.WriteByte(b);
        }

        //Wait, to ensure that all output has been written
        decProc.WaitForExit();

        //Check for success
        if (decProc.ExitCode != 0)
            throw new EncodeDecodeFailureException(decProc.ExitCode);

        //Skip the first line
        encProc.StandardOutput.ReadLine();

        //Pass the output to clsWaveProcessor
        clsWaveProcessor cwp = new clsWaveProcessor();

        //Process the header and the data
        cwp.WaveHeaderIN(decProc.StandardOutput.BaseStream);

        //Create the output
        DecodeReturn dr = new DecodeReturn(cwp.RawPcmWaveData,
            ((cwp.BitsPerSample / 8) * cwp.SampleRate),
            cwp.SampleRate, cwp.Channels != 1, cwp.BitsPerSample);

        //Return the output
        return dr;
    }
} 

代码相当简单。 在每个方法中,我都会根据用户的需要启动一个进程。 请注意,我为满足我的需求简化了 encode。 您可能希望添加更多参数。 这里最需要注意的是,speexencspeexdec 的输入和输出文件都设置为 con。 这允许我写入输入 PCM 数据,以及读取输出,而无需使用临时文件。

然后我只需读取和写入数据。 请注意,我忽略了每个方法中的一行输出数据。 这是因为,无论输出是否重定向到控制台,这两个实用程序都会在开始写入输出文件之前写入一行额外信息。 这就是为什么关闭详细模式非常重要的原因。

几乎所有其余相关信息都可以在注释中找到。

注意

我只想提一下,我计划尽快更新它。 该代码尚未经过测试,我计划做一个演示应用程序。

历史

更新:2007 年 9 月 1 日

  • EncodeReturnDecodeReturn 成员设置为 public。
  • 添加了有关 Speex 本身的部分。
© . All rights reserved.