有效地转换音频字节 – byte 到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; } 

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

用作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); 

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

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

Jodrell的回答值得评论:使用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将它从unsigned转为signed并将其扩展到正确的位宽? 更新:我的格式似乎有问题,请参阅我的其他答案

我想出的有害生物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) )请改为适合。