有效地转换音频字节 - byte []到short []

时间:2011-04-20 10:29:12

标签: c# silverlight windows-phone-7 xna bytearray

我正在尝试使用XNA麦克风捕获音频并将其传递给我分析数据以供显示的API。但是,API需要16位整数数组中的音频数据。所以我的问题相当直截了当;什么是将字节数组转换为短数组的最有效方法?

    private void _microphone_BufferReady(object sender, System.EventArgs e)
    {
        _microphone.GetData(_buffer);

        short[] shorts;

        //Convert and pass the 16 bit samples
        ProcessData(shorts);
    }

干杯, 戴夫

编辑:这就是我提出的并且似乎有效,但是可以更快地完成吗?

    private short[] ConvertBytesToShorts(byte[] bytesBuffer)
    {
        //Shorts array should be half the size of the bytes buffer, as each short represents 2 bytes (16bits)
        short[] shorts = new short[bytesBuffer.Length / 2];

        int currentStartIndex = 0;

        for (int i = 0; i < shorts.Length - 1; i++)
        {
            //Convert the 2 bytes at the currentStartIndex to a short
            shorts[i] = BitConverter.ToInt16(bytesBuffer, currentStartIndex);

            //increment by 2, ready to combine the next 2 bytes in the buffer
            currentStartIndex += 2;
        }

        return shorts;

    }

3 个答案:

答案 0 :(得分:5)

在阅读完更新后,我发现你需要实际将一个字节数组直接复制到short的缓冲区中,合并字节。以下是documentation的相关部分:

  

用作SoundEffect构造函数的参数的byte []缓冲区格式,Microphone.GetData方法和DynamicSoundEffectInstance.SubmitBuffer方法是PCM波形数据。另外,PCM格式是交错的并且是小端的。

现在,如果由于一些奇怪的原因你的系统有BitConverter.IsLittleEndian == false,那么你将需要循环缓冲区,交换字节,从little-endian转换为big-endian。我将把代码留作练习 - 我有理由相信所有的XNA系统都是小端的。

出于您的目的,您只需使用Marshal.CopyBuffer.BlockCopy直接复制缓冲区即可。两者都将为您提供平台本机内存复制操作的性能,这将非常快:

// Create this buffer once and reuse it! Don't recreate it each time!
short[] shorts = new short[_buffer.Length/2];

// Option one:
unsafe
{
    fixed(short* pShorts = shorts)
        Marshal.Copy(_buffer, 0, (IntPtr)pShorts, _buffer.Length);
}

// Option two:
Buffer.BlockCopy(_buffer, 0, shorts, 0, _buffer.Length);

答案 1 :(得分:1)

这是一个性能问题,因此:测量它!

值得指出的是,为了测量.NET中的性能,您希望进行发布构建并运行而不使用附加的调试器(这允许JIT进行优化)。

Jodrell的回答值得评论:使用AsParallel很有意思,但是值得检查旋转它的成本是否值得。 (推测 - 测量它以确认:将字节转换为short应该非常快,因此如果您的缓冲区数据来自共享内存而不是来自每个内核缓存,那么您的大部分成本可能是数据传输而不是处理。)< / p>

此外,我不确定ToArray是否合适。首先,它可能无法直接创建正确大小的数组,因为必须在构建时调整数组大小会使其非常慢。此外,它总是分配数组 - 这本身并不慢,但增加了你几乎肯定不想要的GC成本。

编辑:根据您更新的问题,本答案其余部分的代码无法直接使用,因为数据格式不同。技术本身(循环,安全或不安全)并不像你可以使用的那么快。有关详细信息,请参阅我的其他答案。

所以你想预先分配你的数组。你的代码中的某个地方需要一个像这样的缓冲区:

short[] shorts = new short[_buffer.Length];

然后只需从一个缓冲区复制到另一个缓冲区:

for(int i = 0; i < _buffer.Length; ++i)
    result[i] = ((short)buffer[i]);

这应该非常快,如果不是两个数组边界检查,JIT应该足够聪明,可以跳过一个。

以下是使用不安全代码的方法:(我没有测试过这段代码,但它应该是正确的)

unsafe
{
    int length = _buffer.Length;
    fixed(byte* pSrc = _buffer) fixed(short* pDst = shorts)
    {
        byte* ps = pSrc;
        short* pd = pDst;

        while(pd < pd + length)
            *(pd++) = (short)(*(ps++));
    }
}

现在,不安全版本的缺点是需要/unsafe,而且实际上可能更慢因为它阻止了JIT进行各种优化。再一次:测量它

(如果你在上面的例子中尝试一些排列,你也可以挤出更多的表现。测量它。)

最后:您确定要转换为(short)sample吗?不应该像((short)sample-128)*256这样从无符号到签名并将其扩展到正确的位宽吗? 更新:我的格式似乎错了,请参阅我的其他答案

答案 2 :(得分:0)

我想出的有害生物PLINQ就在这里。

private short[] ConvertBytesToShorts(byte[] bytesBuffer)
{         
    //Shorts array should be half the size of the bytes buffer, as each short represents 2 bytes (16bits)
    var odd = buffer.AsParallel().Where((b, i) => i % 2 != 0);
    var even = buffer.AsParallell().Where((b, i) => i % 2 == 0);

    return odd.Zip(even, (o, e) => {
        return (short)((o << 8) | e);
    }.ToArray();
}

我是关于性能的dubios,但有足够的数据和处理器知道。

如果转换操作错误((short)((o << 8) | e)),请更改为适合。