快速转换字节数组到短数组的音频数据
本文关键字:数组 音频 数据 转换 字节 字节数 | 更新日期: 2023-09-27 18:11:20
我需要最快的方式将字节数组转换为音频数据的短数组。
音频数据字节数组包含来自两个音频通道的数据,以这样的方式放置:
C1C1C2C2 C1C1C2C2 C1C1C2C2 ...
where
C1C1 - two bytes of first channel
C2C2 - two bytes of second channel
目前我使用的是这样的算法,但我觉得有更好的方法来完成这个任务。
byte[] rawData = //from audio device
short[] shorts = new short[rawData.Length / 2];
short[] channel1 = new short[rawData.Length / 4];
short[] channel2 = new short[rawData.Length / 4];
System.Buffer.BlockCopy(rawData, 0, shorts, 0, rawData.Length);
for (int i = 0, j = 0; i < shorts.Length; i+=2, ++j)
{
channel1[j] = shorts[i];
channel2[j] = shorts[i+1];
}
可以不复制缓冲区:
byte[] rawData = //from audio device
short[] channel1 = new short[rawData.Length / 4];
short[] channel2 = new short[rawData.Length / 4];
for (int i = 0, j = 0; i < rawData.Length; i+=4, ++j)
{
channel1[j] = (short)(((ushort)rawData[i + 1]) << 8 | (ushort)rawData[i]);
channel2[j] = (short)(((ushort)rawData[i + 3]) << 8 | (ushort)rawData[i + 2]);
}
要使循环更快,您可以查看任务并行库,特别是Parallel。:
[编辑]
System.Threading.Tasks.Parallel.For( 0, shorts.Length/2, ( i ) =>
{
channel1[i] = shorts[i*2];
channel2[i] = shorts[i*2+1];
} );
(/编辑)另一种方法是循环展开,但我认为TPL也会促进这一点。
您可以使用不安全的代码来避免数组寻址或位移位。但正如PVitt所说,如果你的数据大小很重要,你最好使用标准托管代码和TPL。
short[] channel1 = new short[rawData.Length / 4];
short[] channel2 = new short[rawData.Length / 4];
fixed(byte* pRawData = rawData)
fixed(short* pChannel1 = channel1)
fixed(short* pChannel2 = channel2)
{
byte* end = pRawData + rawData.Length;
while(pRawData < end)
{
(*(pChannel1++)) = *((short*)pRawData);
pRawData += sizeof(short);
(*(pChannel2++)) = *((short*)pRawData);
pRawData += sizeof(short);
}
}
与所有优化问题一样,您需要仔细计时,特别注意您的缓冲区分配、channel1和channel2可以是自动增长的静态(大)缓冲区你只能使用第n个字节。您将能够跳过2个大数组分配对于这个函数的每次执行。并且会减少GC的工作量(当时机很重要时总是更好)
正如CodeInChaos所指出的,如果您的数据不在正确的端序你需要做转换,比如在大之间转换小端序假设是8位原子元素代码看起来像:
short[] channel1 = new short[rawData.Length / 4];
short[] channel2 = new short[rawData.Length / 4];
fixed(byte* pRawData = rawData)
fixed(byte* pChannel1 = (byte*)channel1)
fixed(byte* pChannel2 = (byte*)channel2)
{
byte* end = pRawData + rawData.Length;
byte* pChannel1High = pChannel1 + 1;
byte* pChannel2High = pChannel2 + 1;
while(pRawData < end)
{
*pChannel1High = *pRawData;
pChannel1High += 2 * sizeof(short);
*pChannel1 = *pRawData;
pChannel1 += 2 * sizeof(short);
*pChannel2High = *pRawData;
pChannel2High += 2 * sizeof(short);
*pChannel2 = *pRawData;
pChannel2 += 2 * sizeof(short);
}
}
在这篇文章中,我没有使用实际的编译器编译任何代码,所以如果你发现错误,请随意编辑
您可以自己对它进行基准测试!记住使用释放模式,运行时不需要调试(Ctrl+F5)
class Program
{
[StructLayout(LayoutKind.Explicit)]
struct UnionArray
{
[FieldOffset(0)]
public byte[] Bytes;
[FieldOffset(0)]
public short[] Shorts;
}
unsafe static void Main(string[] args)
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
byte[] rawData = new byte[10000000];
new Random().NextBytes(rawData);
Stopwatch sw1 = Stopwatch.StartNew();
short[] shorts = new short[rawData.Length / 2];
short[] channel1 = new short[rawData.Length / 4];
short[] channel2 = new short[rawData.Length / 4];
System.Buffer.BlockCopy(rawData, 0, shorts, 0, rawData.Length);
for (int i = 0, j = 0; i < shorts.Length; i += 2, ++j)
{
channel1[j] = shorts[i];
channel2[j] = shorts[i + 1];
}
sw1.Stop();
Stopwatch sw2 = Stopwatch.StartNew();
short[] channel1b = new short[rawData.Length / 4];
short[] channel2b = new short[rawData.Length / 4];
for (int i = 0, j = 0; i < rawData.Length; i += 4, ++j)
{
channel1b[j] = BitConverter.ToInt16(rawData, i);
channel2b[j] = BitConverter.ToInt16(rawData, i + 2);
}
sw2.Stop();
Stopwatch sw3 = Stopwatch.StartNew();
short[] shortsc = new UnionArray { Bytes = rawData }.Shorts;
short[] channel1c = new short[rawData.Length / 4];
short[] channel2c = new short[rawData.Length / 4];
for (int i = 0, j = 0; i < shorts.Length; i += 2, ++j)
{
channel1c[j] = shortsc[i];
channel2c[j] = shortsc[i + 1];
}
sw3.Stop();
Stopwatch sw4 = Stopwatch.StartNew();
short[] channel1d = new short[rawData.Length / 4];
short[] channel2d = new short[rawData.Length / 4];
for (int i = 0, j = 0; i < rawData.Length; i += 4, ++j)
{
channel1d[j] = (short)((short)(rawData[i + 1]) << 8 | (short)rawData[i]);
channel2d[j] = (short)((short)(rawData[i + 3]) << 8 | (short)rawData[i + 2]);
//Equivalent warning-less version
//channel1d[j] = (short)(((ushort)rawData[i + 1]) << 8 | (ushort)rawData[i]);
//channel2d[j] = (short)(((ushort)rawData[i + 3]) << 8 | (ushort)rawData[i + 2]);
}
sw4.Stop();
Stopwatch sw5 = Stopwatch.StartNew();
short[] channel1e = new short[rawData.Length / 4];
short[] channel2e = new short[rawData.Length / 4];
fixed (byte* pRawData = rawData)
fixed (short* pChannel1 = channel1e)
fixed (short* pChannel2 = channel2e)
{
byte* pRawData2 = pRawData;
short* pChannel1e = pChannel1;
short* pChannel2e = pChannel2;
byte* end = pRawData2 + rawData.Length;
while (pRawData2 < end)
{
(*(pChannel1e++)) = *((short*)pRawData2);
pRawData2 += sizeof(short);
(*(pChannel2e++)) = *((short*)pRawData2);
pRawData2 += sizeof(short);
}
}
sw5.Stop();
Stopwatch sw6 = Stopwatch.StartNew();
short[] shortse = new short[rawData.Length / 2];
short[] channel1f = new short[rawData.Length / 4];
short[] channel2f = new short[rawData.Length / 4];
System.Buffer.BlockCopy(rawData, 0, shortse, 0, rawData.Length);
System.Threading.Tasks.Parallel.For(0, shortse.Length / 2, (i) =>
{
channel1f[i] = shortse[i * 2];
channel2f[i] = shortse[i * 2 + 1];
});
sw6.Stop();
if (!channel1.SequenceEqual(channel1b) || !channel1.SequenceEqual(channel1c) || !channel1.SequenceEqual(channel1d) || !channel1.SequenceEqual(channel1e) || !channel1.SequenceEqual(channel1f))
{
throw new Exception();
}
if (!channel2.SequenceEqual(channel2b) || !channel2.SequenceEqual(channel2c) || !channel2.SequenceEqual(channel2d) || !channel2.SequenceEqual(channel2e) || !channel2.SequenceEqual(channel2f))
{
throw new Exception();
}
Console.WriteLine("Original: {0}ms", sw1.ElapsedMilliseconds);
Console.WriteLine("BitConverter: {0}ms", sw2.ElapsedMilliseconds);
Console.WriteLine("Super-unsafe struct: {0}ms", sw3.ElapsedMilliseconds);
Console.WriteLine("PVitt shifts: {0}ms", sw4.ElapsedMilliseconds);
Console.WriteLine("unsafe VirtualBlackFox: {0}ms", sw5.ElapsedMilliseconds);
Console.WriteLine("TPL: {0}ms", sw6.ElapsedMilliseconds);
Console.ReadKey();
return;
}
}
- 在x86上最快的是VirtualBlackFox的不安全代码,其次是c#不安全值类型数组到字节数组转换的"超级不安全"
struct
"把戏",第三是PVitt。 - 在x64上最快的是VirtualBlackFox的不安全代码,第二PVitt。