我正在尝试使用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;
}
答案 0 :(得分:5)
在阅读完更新后,我发现你需要实际将一个字节数组直接复制到short的缓冲区中,合并字节。以下是documentation的相关部分:
用作SoundEffect构造函数的参数的byte []缓冲区格式,Microphone.GetData方法和DynamicSoundEffectInstance.SubmitBuffer方法是PCM波形数据。另外,PCM格式是交错的并且是小端的。
现在,如果由于一些奇怪的原因你的系统有BitConverter.IsLittleEndian == false
,那么你将需要循环缓冲区,交换字节,从little-endian转换为big-endian。我将把代码留作练习 - 我有理由相信所有的XNA系统都是小端的。
出于您的目的,您只需使用Marshal.Copy
或Buffer.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)
),请更改为适合。