繁体   English   中英

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

[英]Efficiently convert audio bytes - byte[] to short[]

我正在尝试使用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;

    }

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

用作SoundEffect构造函数,Microphone.GetData方法和DynamicSoundEffectInstance.SubmitBuffer方法的参数的byte []缓冲区格式是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);

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

值得指出的是,为了评估.NET的性能,您需要在不附加调试器的情况下进行发行版本构建和运行(这可以使JIT得以优化)。

Jodrell的答案值得一提:使用AsParallel很有趣,但是值得检查将其AsParallel的成本是否值得。 (推测-对其进行测量以确认:将字节转换为short应该非常快,因此,如果缓冲区数据来自共享内存而不是每个内核缓存,则您的大部分成本可能会用于数据传输而不是处理。)

另外,我不确定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) ),请更改为适合。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM