Improve audio parsing performance.
- Improve the AudReader and WavReader by performing fewer, larger stream reads to consume bytes more efficiently. - Improve ImaAdpcmReader.LoadImaAdpcmSound by accepting an output buffer rather than allocating a new array. - Improve StreamExts.ReadAllBytes by using MemoryStream.CopyTo. This internally rents a much larger buffer from the array pool, allowing for fewer, larger stream reads.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
b429ca7879
commit
8b8651dcf7
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
@@ -94,6 +95,9 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
if (audioType != WaveType.Pcm)
|
||||
sampleBits = 16;
|
||||
|
||||
if (channels != 1 && channels != 2)
|
||||
throw new NotSupportedException($"Expected 1 or 2 channels only for WAV file, received: {channels}");
|
||||
|
||||
var chan = channels;
|
||||
result = () =>
|
||||
{
|
||||
@@ -115,10 +119,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
readonly int numBlocks;
|
||||
readonly int blockDataSize;
|
||||
readonly int outputSize;
|
||||
readonly int[] predictor;
|
||||
readonly int[] index;
|
||||
readonly byte[] blockData;
|
||||
|
||||
readonly byte[] interleaveBuffer;
|
||||
int outOffset;
|
||||
int currentBlock;
|
||||
|
||||
@@ -129,21 +131,25 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
numBlocks = dataSize / blockAlign;
|
||||
blockDataSize = blockAlign - channels * 4;
|
||||
outputSize = uncompressedSize * channels * 2;
|
||||
predictor = new int[channels];
|
||||
index = new int[channels];
|
||||
|
||||
interleaveBuffer = new byte[channels * 16];
|
||||
blockData = new byte[blockDataSize];
|
||||
}
|
||||
|
||||
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||
{
|
||||
// Decode each block of IMA ADPCM data
|
||||
// Each block starts with a initial state per-channel
|
||||
Span<int> predictor = stackalloc int[channels];
|
||||
Span<int> index = stackalloc int[channels];
|
||||
|
||||
Span<byte> channelData = stackalloc byte[channels * 4];
|
||||
baseStream.ReadBytes(channelData);
|
||||
var cd = 0;
|
||||
for (var c = 0; c < channels; c++)
|
||||
{
|
||||
predictor[c] = baseStream.ReadInt16();
|
||||
index[c] = baseStream.ReadUInt8();
|
||||
baseStream.ReadUInt8(); // Unknown/Reserved
|
||||
predictor[c] = (short)(channelData[cd++] | channelData[cd++] << 8);
|
||||
index[c] = channelData[cd++];
|
||||
cd++; // Unknown/Reserved
|
||||
|
||||
// Output first sample from input
|
||||
data.Enqueue((byte)predictor[c]);
|
||||
@@ -155,15 +161,17 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
}
|
||||
|
||||
// Decode and output remaining data in this block
|
||||
Span<byte> decoded = stackalloc byte[16];
|
||||
Span<byte> interleaveBuffer = stackalloc byte[channels * 16];
|
||||
var blockDataSpan = blockData.AsSpan();
|
||||
baseStream.ReadBytes(blockDataSpan);
|
||||
var blockOffset = 0;
|
||||
Span<byte> chunk = stackalloc byte[4];
|
||||
while (blockOffset < blockDataSize)
|
||||
{
|
||||
for (var c = 0; c < channels; c++)
|
||||
{
|
||||
// Decode 4 bytes (to 16 bytes of output) per channel
|
||||
baseStream.ReadBytes(chunk);
|
||||
var decoded = ImaAdpcmReader.LoadImaAdpcmSound(chunk, ref index[c], ref predictor[c]);
|
||||
ImaAdpcmReader.LoadImaAdpcmSound(blockDataSpan.Slice(blockOffset, 4), ref index[c], ref predictor[c], decoded);
|
||||
|
||||
// Interleave output, one sample per channel
|
||||
var interleaveChannelOffset = 2 * c;
|
||||
@@ -209,6 +217,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
readonly short channels;
|
||||
readonly int blockDataSize;
|
||||
readonly int numBlocks;
|
||||
readonly byte[] blockData;
|
||||
|
||||
int currentBlock;
|
||||
|
||||
@@ -218,36 +227,35 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
this.channels = channels;
|
||||
blockDataSize = blockAlign - channels * 7;
|
||||
numBlocks = dataSize / blockAlign;
|
||||
|
||||
blockData = new byte[blockDataSize];
|
||||
}
|
||||
|
||||
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||
{
|
||||
var bpred = new byte[channels];
|
||||
var chanIdelta = new short[channels];
|
||||
Span<byte> bpred = stackalloc byte[channels];
|
||||
Span<short> chanIdelta = stackalloc short[channels];
|
||||
|
||||
var s1 = new short[channels];
|
||||
var s2 = new short[channels];
|
||||
Span<short> s1 = stackalloc short[channels];
|
||||
Span<short> s2 = stackalloc short[channels];
|
||||
|
||||
baseStream.ReadBytes(bpred);
|
||||
baseStream.ReadBytes(MemoryMarshal.Cast<short, byte>(chanIdelta));
|
||||
baseStream.ReadBytes(MemoryMarshal.Cast<short, byte>(s1));
|
||||
baseStream.ReadBytes(MemoryMarshal.Cast<short, byte>(s2));
|
||||
|
||||
for (var c = 0; c < channels; c++)
|
||||
bpred[c] = baseStream.ReadUInt8();
|
||||
|
||||
for (var c = 0; c < channels; c++)
|
||||
chanIdelta[c] = baseStream.ReadInt16();
|
||||
|
||||
for (var c = 0; c < channels; c++)
|
||||
s1[c] = baseStream.ReadInt16();
|
||||
|
||||
for (var c = 0; c < channels; c++)
|
||||
s2[c] = WriteSample(baseStream.ReadInt16(), data);
|
||||
s2[c] = WriteSample(s2[c], data);
|
||||
|
||||
for (var c = 0; c < channels; c++)
|
||||
WriteSample(s1[c], data);
|
||||
|
||||
var channelNumber = channels > 1 ? 1 : 0;
|
||||
|
||||
baseStream.ReadBytes(blockData);
|
||||
for (var blockindx = 0; blockindx < blockDataSize; blockindx++)
|
||||
{
|
||||
var bytecode = baseStream.ReadUInt8();
|
||||
var bytecode = blockData[blockindx];
|
||||
|
||||
// Decode the first nibble, this is always left channel
|
||||
WriteSample(DecodeNibble((short)((bytecode >> 4) & 0x0F), bpred[0], ref chanIdelta[0], ref s1[0], ref s2[0]), data);
|
||||
|
||||
Reference in New Issue
Block a user