有效地转换音频字节-字节[]短[]

本文关键字:-字节 字节 转换 音频 有效地 | 更新日期: 2023-09-27 17:52:17

我试图使用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);
    }

欢呼,戴夫

EDIT:这是我想到的,似乎工作,但它可以做得更快吗?

    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;
    }

有效地转换音频字节-字节[]短[]

阅读你的更新后,我可以看到你需要实际复制一个字节数组直接到一个缓冲区的短裤,合并字节。以下是文档中的相关部分:

作为SoundEffect构造函数麦克风参数的byte[]缓冲区格式。GetData方法,以及DynamicSoundEffectInstance。SubmitBuffer方法是PCM波数据。此外,PCM格式是交错的,并且采用小端序。

现在,如果由于某些奇怪的原因,您的系统有BitConverter.IsLittleEndian == false,那么您将需要循环遍历缓冲区,交换字节,以从小端字节转换为大端字节。我将把代码留作练习——我相当肯定所有的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);

这是一个性能问题,所以:测量它!

值得指出的是,为了在。net中测量性能,您需要在没有附加调试器的情况下进行发布构建并运行(这允许JIT优化)。

Jodrell的回答值得评论:使用AsParallel很有趣,但值得检查旋转它的成本是否值得。(推测-测量它以确认:将字节转换为短应该非常快,所以如果你的缓冲区数据来自共享内存而不是每核缓存,你的大部分成本可能会在数据传输而不是处理。)

我也不确定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一样把它从无符号到有符号并扩展到正确的位宽度吗?更新:似乎我在这里的格式是错误的,见我的其他答案

我能想到的最讨厌的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();
}

我怀疑性能,但有足够的数据和处理器谁知道呢。

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