简体   繁体   English

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

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

I'm trying to use the XNA microphone to capture audio and pass it to an API I have that analyses the data for display purposes. 我正在尝试使用XNA麦克风捕获音频并将其传递给我分析数据用于显示目的的API。 However, the API requires the audio data in an array of 16 bit integers. 但是,API要求音频数据为16位整数数组。 So my question is fairly straight forward; 所以我的问题很简单。 what's the most efficient way to convert the byte array into a short array? 将字节数组转换为短数组的最有效方法是什么?

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

        short[] shorts;

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

Cheers, Dave 干杯,戴夫

EDIT : This is what I have come up with and seems to work, but could it be done faster? 编辑 :这是我想出的,似乎可以使用,但是可以更快地完成吗?

    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;

    }

After reading your update, I can see you need to actually copy a byte array directly into a buffer of shorts, merging bytes. 阅读您的更新后,我可以看到您实际上需要将字节数组直接直接复制到短裤缓冲区(合并字节)中。 Here's the relevant section from the documentation : 这是文档中的相关部分:

The byte[] buffer format used as a parameter for the SoundEffect constructor, Microphone.GetData method, and DynamicSoundEffectInstance.SubmitBuffer method is PCM wave data. 用作SoundEffect构造函数,Microphone.GetData方法和DynamicSoundEffectInstance.SubmitBuffer方法的参数的byte []缓冲区格式是PCM波形数据。 Additionally, the PCM format is interleaved and in little-endian. 此外,PCM格式是交织的,并且采用低位字节序。

Now, if for some weird reason your system has BitConverter.IsLittleEndian == false , then you will need to loop through your buffer, swapping bytes as you go, to convert from little-endian to big-endian. 现在,如果出于某种奇怪的原因,您的系统具有BitConverter.IsLittleEndian == false ,那么您将需要遍历缓冲区,并随即交换字节,以从little-endian转换为big-endian。 I'll leave the code as an exercise - I am reasonably sure all the XNA systems are little-endian. 我将把代码留作练习-我可以肯定地说,所有XNA系统都是低位优先的。

For your purposes, you can just copy the buffer directly using Marshal.Copy or Buffer.BlockCopy . 为了您的目的,您可以直接使用Marshal.CopyBuffer.BlockCopy复制缓冲区。 Both will give you the performance of the platform's native memory copy operation, which will be extremely fast: 两者都将为您提供平台本机内存复制操作的性能,这将非常快:

// 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);

This is a performance question, so: measure it! 这是一个性能问题,因此: 测量一下!

It is worth pointing out that for measuring performance in .NET you want to do a release build and run without the debugger attached (this allows the JIT to optimise). 值得指出的是,为了评估.NET的性能,您需要在不附加调试器的情况下进行发行版本构建和运行(这可以使JIT得以优化)。

Jodrell's answer is worth commenting on: Using AsParallel is interesting, but it is worth checking if the cost of spinning it up is worth it. Jodrell的答案值得一提:使用AsParallel很有趣,但是值得检查将其AsParallel的成本是否值得。 (Speculation - measure it to confirm: converting byte to short should be extremely fast, so if your buffer data is coming from shared memory and not a per-core cache, most of your cost will probably be in data transfer not processing.) (推测-对其进行测量以确认:将字节转换为short应该非常快,因此,如果缓冲区数据来自共享内存而不是每个内核缓存,则您的大部分成本可能会用于数据传输而不是处理。)

Also I am not sure that ToArray is appropriate. 另外,我不确定ToArray是否合适。 First of all, it may not be able to create the correct-sized array directly, having to resize the array as it builds it will make it very slow. 首先,它可能无法直接创建正确大小的数组,必须在构建数组时调整其大小,这会使它非常慢。 Additionally it will always allocate the array - which is not slow itself, but adds a GC cost that you almost certainly don't want. 此外,它将始终分配数组-数组本身并不慢,但会增加您几乎肯定不希望的GC成本。

Edit: Based on your updated question, the code in the rest of this answer is not directly usable, as the format of the data is different. 编辑:根据您更新的问题,此答案其余部分中的代码不可直接使用,因为数据格式不同。 And the technique itself (a loop, safe or unsafe) is not as fast as what you can use. 而且该技术本身(循环,安全或不安全)并不如您所能使用的那样快。 See my other answer for details. 请参阅我的其他答案以获取详细信息。

So you want to pre-allocate your array. 因此,您想预分配阵列。 Somewhere out in your code you want a buffer like this: 在代码中的某个地方,您需要这样的缓冲区:

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

And then simply copy from one buffer to the other: 然后简单地从一个缓冲区复制到另一个缓冲区:

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

This should be very fast, and the JIT should be clever enough to skip one if not both of the array bounds checks. 这应该非常快,并且如果不是两个数组边界检查都可以,JIT应该足够聪明以跳过一个。

And here's how you can do it with unsafe code: (I haven't tested this code, but it should be about right) 这是使用不安全代码的方法:(我尚未测试此代码,但应该正确)

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

Now the unsafe version has the disadvantage of requiring /unsafe , and also it may actually be slower because it prevents the JIT from doing various optimisations. 现在,不安全的版本具有需要/unsafe的缺点,并且实际上可能会更慢,因为它会阻止JIT进行各种优化。 Once again: measure it . 再次: 测量它

(Also you can probably squeeze more performance if you try some permutations on the above examples. Measure it .) (另外,如果您尝试对上述示例进行一些置换,则可能会降低性能。请对其进行测量 。)

Finally: Are you sure you want the conversion to be (short)sample ? 最后:确定要转换为(short)sample吗? Shouldn't it be something like ((short)sample-128)*256 to take it from unsigned to signed and extend it to the correct bit-width? ((short)sample-128)*256将它从无符号带到带符号并将其扩展到正确的位宽应该不是吗? Update: seems I was wrong on the format here, see my other answer 更新:似乎我的格式不对,请参阅其他答案

The pest PLINQ I could come up with is here. 我可以想到的害虫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();
}

I'm dubios about the performance but with enough data and processors who knows. 我对性能很感兴趣,但有足够的数据和处理器知道。

If the conversion operation is wrong ( (short)((o << 8) | e) ) please change to suit. 如果转换操作错误( (short)((o << 8) | e) ),请更改为适合。

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

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