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
@@ -13,7 +13,6 @@ using System;
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -178,13 +177,9 @@ namespace OpenRA
|
|||||||
if (s.CanSeek)
|
if (s.CanSeek)
|
||||||
return s.ReadBytes((int)(s.Length - s.Position));
|
return s.ReadBytes((int)(s.Length - s.Position));
|
||||||
|
|
||||||
var bytes = new List<byte>();
|
using var ms = new MemoryStream();
|
||||||
var buffer = new byte[1024];
|
s.CopyTo(ms);
|
||||||
int count;
|
return ms.Capacity == ms.Length ? ms.GetBuffer() : ms.ToArray();
|
||||||
while ((count = s.Read(buffer, 0, buffer.Length)) > 0)
|
|
||||||
bytes.AddRange(buffer.Take(count));
|
|
||||||
|
|
||||||
return bytes.ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
{
|
{
|
||||||
readonly int outputSize;
|
readonly int outputSize;
|
||||||
int dataSize;
|
int dataSize;
|
||||||
|
byte[] inputBuffer;
|
||||||
|
|
||||||
int currentSample;
|
int currentSample;
|
||||||
int baseOffset;
|
int baseOffset;
|
||||||
@@ -121,9 +122,12 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var chunk = AudChunk.Read(baseStream);
|
var chunk = AudChunk.Read(baseStream);
|
||||||
|
var input = EnsureArraySize(ref inputBuffer, chunk.CompressedSize);
|
||||||
|
baseStream.ReadBytes(input);
|
||||||
|
|
||||||
for (var n = 0; n < chunk.CompressedSize; n++)
|
for (var n = 0; n < chunk.CompressedSize; n++)
|
||||||
{
|
{
|
||||||
var b = baseStream.ReadUInt8();
|
var b = input[n];
|
||||||
|
|
||||||
var t = ImaAdpcmReader.DecodeImaAdpcmSample(b, ref index, ref currentSample);
|
var t = ImaAdpcmReader.DecodeImaAdpcmSample(b, ref index, ref currentSample);
|
||||||
data.Enqueue((byte)t);
|
data.Enqueue((byte)t);
|
||||||
@@ -144,6 +148,13 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
|
|
||||||
return dataSize <= 0;
|
return dataSize <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Span<byte> EnsureArraySize(ref byte[] array, int desiredSize)
|
||||||
|
{
|
||||||
|
if (array == null || array.Length < desiredSize)
|
||||||
|
array = new byte[desiredSize];
|
||||||
|
return array.AsSpan(..desiredSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class WestwoodCompressedAudStream : ReadOnlyAdapterStream
|
sealed class WestwoodCompressedAudStream : ReadOnlyAdapterStream
|
||||||
|
|||||||
@@ -225,23 +225,25 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static byte[] GetAudioData(bool compressed, MemoryStream audio, ref int adpcmIndex)
|
||||||
|
{
|
||||||
|
var audioArray = audio.ToArray();
|
||||||
|
if (!compressed)
|
||||||
|
return audioArray;
|
||||||
|
|
||||||
|
var result = new byte[audio.Length * 4];
|
||||||
|
ImaAdpcmReader.LoadImaAdpcmSound(audioArray, ref adpcmIndex, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (AudioChannels == 1)
|
if (AudioChannels == 1)
|
||||||
AudioData = compressed ? ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
|
AudioData = GetAudioData(compressed, audio1, ref adpcmIndex);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
byte[] leftData, rightData;
|
adpcmIndex = 0;
|
||||||
if (!compressed)
|
var leftData = GetAudioData(compressed, audio1, ref adpcmIndex);
|
||||||
{
|
adpcmIndex = 0;
|
||||||
leftData = audio1.ToArray();
|
var rightData = GetAudioData(compressed, audio2, ref adpcmIndex);
|
||||||
rightData = audio2.ToArray();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
adpcmIndex = 0;
|
|
||||||
leftData = ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex);
|
|
||||||
adpcmIndex = 0;
|
|
||||||
rightData = ImaAdpcmReader.LoadImaAdpcmSound(audio2.ToArray(), ref adpcmIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioData = new byte[rightData.Length + leftData.Length];
|
AudioData = new byte[rightData.Length + leftData.Length];
|
||||||
var rightIndex = 0;
|
var rightIndex = 0;
|
||||||
|
|||||||
@@ -56,18 +56,18 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
return (short)current;
|
return (short)current;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] LoadImaAdpcmSound(ReadOnlySpan<byte> raw, ref int index)
|
public static void LoadImaAdpcmSound(ReadOnlySpan<byte> raw, ref int index, Span<byte> output)
|
||||||
{
|
{
|
||||||
var currentSample = 0;
|
var currentSample = 0;
|
||||||
return LoadImaAdpcmSound(raw, ref index, ref currentSample);
|
LoadImaAdpcmSound(raw, ref index, ref currentSample, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] LoadImaAdpcmSound(ReadOnlySpan<byte> raw, ref int index, ref int currentSample)
|
public static void LoadImaAdpcmSound(ReadOnlySpan<byte> raw, ref int index, ref int currentSample, Span<byte> output)
|
||||||
{
|
{
|
||||||
var dataSize = raw.Length;
|
var dataSize = raw.Length;
|
||||||
var outputSize = raw.Length * 4;
|
if (output.Length != raw.Length * 4)
|
||||||
|
throw new ArgumentException($"{nameof(output)} must be 4 times the length of {nameof(raw)}.", nameof(output));
|
||||||
|
|
||||||
var output = new byte[outputSize];
|
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
|
|
||||||
while (dataSize-- > 0)
|
while (dataSize-- > 0)
|
||||||
@@ -82,8 +82,6 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
output[offset++] = (byte)t;
|
output[offset++] = (byte)t;
|
||||||
output[offset++] = (byte)(t >> 8);
|
output[offset++] = (byte)(t >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.FileFormats
|
namespace OpenRA.Mods.Common.FileFormats
|
||||||
@@ -94,6 +95,9 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
if (audioType != WaveType.Pcm)
|
if (audioType != WaveType.Pcm)
|
||||||
sampleBits = 16;
|
sampleBits = 16;
|
||||||
|
|
||||||
|
if (channels != 1 && channels != 2)
|
||||||
|
throw new NotSupportedException($"Expected 1 or 2 channels only for WAV file, received: {channels}");
|
||||||
|
|
||||||
var chan = channels;
|
var chan = channels;
|
||||||
result = () =>
|
result = () =>
|
||||||
{
|
{
|
||||||
@@ -115,10 +119,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
readonly int numBlocks;
|
readonly int numBlocks;
|
||||||
readonly int blockDataSize;
|
readonly int blockDataSize;
|
||||||
readonly int outputSize;
|
readonly int outputSize;
|
||||||
readonly int[] predictor;
|
readonly byte[] blockData;
|
||||||
readonly int[] index;
|
|
||||||
|
|
||||||
readonly byte[] interleaveBuffer;
|
|
||||||
int outOffset;
|
int outOffset;
|
||||||
int currentBlock;
|
int currentBlock;
|
||||||
|
|
||||||
@@ -129,21 +131,25 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
numBlocks = dataSize / blockAlign;
|
numBlocks = dataSize / blockAlign;
|
||||||
blockDataSize = blockAlign - channels * 4;
|
blockDataSize = blockAlign - channels * 4;
|
||||||
outputSize = uncompressedSize * channels * 2;
|
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)
|
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||||
{
|
{
|
||||||
// Decode each block of IMA ADPCM data
|
// Decode each block of IMA ADPCM data
|
||||||
// Each block starts with a initial state per-channel
|
// 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++)
|
for (var c = 0; c < channels; c++)
|
||||||
{
|
{
|
||||||
predictor[c] = baseStream.ReadInt16();
|
predictor[c] = (short)(channelData[cd++] | channelData[cd++] << 8);
|
||||||
index[c] = baseStream.ReadUInt8();
|
index[c] = channelData[cd++];
|
||||||
baseStream.ReadUInt8(); // Unknown/Reserved
|
cd++; // Unknown/Reserved
|
||||||
|
|
||||||
// Output first sample from input
|
// Output first sample from input
|
||||||
data.Enqueue((byte)predictor[c]);
|
data.Enqueue((byte)predictor[c]);
|
||||||
@@ -155,15 +161,17 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode and output remaining data in this block
|
// 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;
|
var blockOffset = 0;
|
||||||
Span<byte> chunk = stackalloc byte[4];
|
|
||||||
while (blockOffset < blockDataSize)
|
while (blockOffset < blockDataSize)
|
||||||
{
|
{
|
||||||
for (var c = 0; c < channels; c++)
|
for (var c = 0; c < channels; c++)
|
||||||
{
|
{
|
||||||
// Decode 4 bytes (to 16 bytes of output) per channel
|
// Decode 4 bytes (to 16 bytes of output) per channel
|
||||||
baseStream.ReadBytes(chunk);
|
ImaAdpcmReader.LoadImaAdpcmSound(blockDataSpan.Slice(blockOffset, 4), ref index[c], ref predictor[c], decoded);
|
||||||
var decoded = ImaAdpcmReader.LoadImaAdpcmSound(chunk, ref index[c], ref predictor[c]);
|
|
||||||
|
|
||||||
// Interleave output, one sample per channel
|
// Interleave output, one sample per channel
|
||||||
var interleaveChannelOffset = 2 * c;
|
var interleaveChannelOffset = 2 * c;
|
||||||
@@ -209,6 +217,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
readonly short channels;
|
readonly short channels;
|
||||||
readonly int blockDataSize;
|
readonly int blockDataSize;
|
||||||
readonly int numBlocks;
|
readonly int numBlocks;
|
||||||
|
readonly byte[] blockData;
|
||||||
|
|
||||||
int currentBlock;
|
int currentBlock;
|
||||||
|
|
||||||
@@ -218,36 +227,35 @@ namespace OpenRA.Mods.Common.FileFormats
|
|||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
blockDataSize = blockAlign - channels * 7;
|
blockDataSize = blockAlign - channels * 7;
|
||||||
numBlocks = dataSize / blockAlign;
|
numBlocks = dataSize / blockAlign;
|
||||||
|
|
||||||
|
blockData = new byte[blockDataSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||||
{
|
{
|
||||||
var bpred = new byte[channels];
|
Span<byte> bpred = stackalloc byte[channels];
|
||||||
var chanIdelta = new short[channels];
|
Span<short> chanIdelta = stackalloc short[channels];
|
||||||
|
|
||||||
var s1 = new short[channels];
|
Span<short> s1 = stackalloc short[channels];
|
||||||
var s2 = new 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++)
|
for (var c = 0; c < channels; c++)
|
||||||
bpred[c] = baseStream.ReadUInt8();
|
s2[c] = WriteSample(s2[c], data);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
for (var c = 0; c < channels; c++)
|
for (var c = 0; c < channels; c++)
|
||||||
WriteSample(s1[c], data);
|
WriteSample(s1[c], data);
|
||||||
|
|
||||||
var channelNumber = channels > 1 ? 1 : 0;
|
var channelNumber = channels > 1 ? 1 : 0;
|
||||||
|
|
||||||
|
baseStream.ReadBytes(blockData);
|
||||||
for (var blockindx = 0; blockindx < blockDataSize; blockindx++)
|
for (var blockindx = 0; blockindx < blockDataSize; blockindx++)
|
||||||
{
|
{
|
||||||
var bytecode = baseStream.ReadUInt8();
|
var bytecode = blockData[blockindx];
|
||||||
|
|
||||||
// Decode the first nibble, this is always left channel
|
// 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);
|
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