mirror of
https://github.com/crskycode/GARbro.git
synced 2024-10-23 15:48:16 +08:00
decode HCA audio stream in background task.
parallel decoding disabled for now (see HcaReader.ConvertParallel).
This commit is contained in:
parent
dbb3384e02
commit
48b6747c4c
@ -24,11 +24,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using GameRes.Utility;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GameRes.Formats.Cri
|
namespace GameRes.Formats.Cri
|
||||||
{
|
{
|
||||||
@ -48,14 +51,7 @@ namespace GameRes.Formats.Cri
|
|||||||
|
|
||||||
public override SoundInput TryOpen (Stream file)
|
public override SoundInput TryOpen (Stream file)
|
||||||
{
|
{
|
||||||
using (var reader = new HcaReader (file, DefaultKey.Item1, DefaultKey.Item2))
|
return new HcaInput (file, ConversionFormat.IeeeFloat, DefaultKey);
|
||||||
{
|
|
||||||
var data = reader.Unpack (ConversionFormat.IeeeFloat);
|
|
||||||
var input = new MemoryStream (data);
|
|
||||||
var sound = new RawPcmInput (input, reader.Format);
|
|
||||||
file.Dispose();
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +61,136 @@ namespace GameRes.Formats.Cri
|
|||||||
IeeeFloat = 3,
|
IeeeFloat = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
internal class HcaInput : SoundInput
|
||||||
|
{
|
||||||
|
HcaReader m_reader;
|
||||||
|
long m_position;
|
||||||
|
int m_bitrate;
|
||||||
|
Array[] m_decoded_blocks;
|
||||||
|
int m_decoded_block_size;
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { return m_position; }
|
||||||
|
set { m_position = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanSeek { get { return true; } }
|
||||||
|
public override string SourceFormat { get { return "hca"; } }
|
||||||
|
public override int SourceBitrate { get { return m_bitrate; } }
|
||||||
|
|
||||||
|
public HcaInput (Stream file, ConversionFormat target, Tuple<uint, uint> key) : base (file)
|
||||||
|
{
|
||||||
|
m_reader = new HcaReader (file, key.Item1, key.Item2);
|
||||||
|
m_reader.InitConversion (target);
|
||||||
|
Format = m_reader.Format;
|
||||||
|
m_bitrate = (int)(Format.SamplesPerSecond * m_reader.BlockSize / (0x80 * Format.Channels));
|
||||||
|
m_decoded_block_size = (int)(0x80 * Format.Channels * Format.BitsPerSample);
|
||||||
|
PcmSize = m_reader.BlockCount * m_decoded_block_size;
|
||||||
|
m_decoded_blocks = new Array[m_reader.BlockCount];
|
||||||
|
|
||||||
|
InitBackgroundReader (target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read (byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
long block_pos;
|
||||||
|
int block_index = (int)Math.DivRem (m_position, m_decoded_block_size, out block_pos);
|
||||||
|
if (block_index < 0 || block_index > m_decoded_blocks.Length)
|
||||||
|
return 0;
|
||||||
|
int total_read = 0;
|
||||||
|
while (block_index < m_decoded_blocks.Length && count > 0)
|
||||||
|
{
|
||||||
|
if (null == m_decoded_blocks[block_index])
|
||||||
|
FillBlock (block_index);
|
||||||
|
if (null == m_decoded_blocks[block_index])
|
||||||
|
break;
|
||||||
|
int available = Math.Min (count, m_decoded_block_size - (int)block_pos);
|
||||||
|
Buffer.BlockCopy (m_decoded_blocks[block_index], (int)block_pos, buffer, offset, available);
|
||||||
|
m_position += available;
|
||||||
|
total_read += available;
|
||||||
|
count -= available;
|
||||||
|
if (0 == count)
|
||||||
|
break;
|
||||||
|
offset += available;
|
||||||
|
block_pos = 0;
|
||||||
|
++block_index;
|
||||||
|
}
|
||||||
|
return total_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitBackgroundReader (ConversionFormat target)
|
||||||
|
{
|
||||||
|
m_block_queue = new BlockingCollection<Tuple<int, Array>>();
|
||||||
|
m_cancel_source = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var token = m_cancel_source.Token;
|
||||||
|
if (ConversionFormat.IeeeFloat == target)
|
||||||
|
m_conversion_task = Task.Factory.StartNew (() => m_reader.ConvertParallel (m_block_queue, HcaReader.PackSampleFloat, token), token);
|
||||||
|
else
|
||||||
|
m_conversion_task = Task.Factory.StartNew (() => m_reader.ConvertParallel (m_block_queue, HcaReader.PackSample16, token), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockingCollection<Tuple<int, Array>> m_block_queue;
|
||||||
|
Task m_conversion_task;
|
||||||
|
CancellationTokenSource m_cancel_source;
|
||||||
|
|
||||||
|
void FillBlock (int block_index)
|
||||||
|
{
|
||||||
|
var token = m_cancel_source.Token;
|
||||||
|
while (!m_block_queue.IsCompleted && null == m_decoded_blocks[block_index])
|
||||||
|
{
|
||||||
|
var block = m_block_queue.Take (token);
|
||||||
|
if (block.Item1 >= m_decoded_blocks.Length)
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
m_decoded_blocks[block.Item1] = block.Item2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
bool _hca_disposed = false;
|
||||||
|
protected override void Dispose (bool disposing)
|
||||||
|
{
|
||||||
|
if (!_hca_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (m_cancel_source != null)
|
||||||
|
{
|
||||||
|
if (!m_block_queue.IsAddingCompleted)
|
||||||
|
m_cancel_source.Cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_conversion_task.Wait();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore exceptions
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_conversion_task.Dispose();
|
||||||
|
m_block_queue.Dispose();
|
||||||
|
m_cancel_source.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_reader.Dispose();
|
||||||
|
}
|
||||||
|
_hca_disposed = true;
|
||||||
|
base.Dispose (disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class HcaReader : IDisposable
|
internal sealed class HcaReader : IDisposable
|
||||||
{
|
{
|
||||||
WaveFormat m_format;
|
WaveFormat m_format;
|
||||||
byte[] m_input;
|
byte[] m_input;
|
||||||
|
|
||||||
public WaveFormat Format { get { return m_format; } }
|
public WaveFormat Format { get { return m_format; } }
|
||||||
|
public uint BlockCount { get { return m_fmt_block_count.Value; } }
|
||||||
|
public int BlockSize { get { return m_comp.BlockSize; } }
|
||||||
|
|
||||||
public HcaReader (Stream input, uint key1, uint key2)
|
public HcaReader (Stream input, uint key1, uint key2)
|
||||||
{
|
{
|
||||||
@ -78,19 +198,20 @@ namespace GameRes.Formats.Cri
|
|||||||
ParseHeader (file);
|
ParseHeader (file);
|
||||||
m_ath = new AthTable (m_ath_type.Value, m_format.SamplesPerSecond);
|
m_ath = new AthTable (m_ath_type.Value, m_format.SamplesPerSecond);
|
||||||
m_cipher = new Cipher (m_ciph_type.Value, key1, key2);
|
m_cipher = new Cipher (m_ciph_type.Value, key1, key2);
|
||||||
|
m_block = new ThreadLocal<byte[]> (() => new byte[m_comp.BlockSize]);
|
||||||
|
|
||||||
InitBuffer (input);
|
InitBuffer (input);
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate void SampleConverter (float f, BinaryWriter output);
|
delegate void SampleWriter (float f, BinaryWriter output);
|
||||||
|
|
||||||
static readonly Dictionary<ConversionFormat, ushort> FormatBpsMap = new Dictionary<ConversionFormat, ushort> {
|
static readonly Dictionary<ConversionFormat, ushort> FormatBpsMap = new Dictionary<ConversionFormat, ushort> {
|
||||||
{ ConversionFormat.Pcm, 16 },
|
{ ConversionFormat.Pcm, 16 },
|
||||||
{ ConversionFormat.IeeeFloat, 32 },
|
{ ConversionFormat.IeeeFloat, 32 },
|
||||||
};
|
};
|
||||||
static readonly Dictionary<ConversionFormat, SampleConverter> ConversionMap = new Dictionary<ConversionFormat, SampleConverter> {
|
static readonly Dictionary<ConversionFormat, SampleWriter> ConversionMap = new Dictionary<ConversionFormat, SampleWriter> {
|
||||||
{ ConversionFormat.Pcm, PackSample16 },
|
{ ConversionFormat.Pcm, (f, output) => output.Write (PackSample16 (f)) },
|
||||||
{ ConversionFormat.IeeeFloat, PackSampleFloat },
|
{ ConversionFormat.IeeeFloat, (f, output) => output.Write (PackSampleFloat (f)) },
|
||||||
};
|
};
|
||||||
|
|
||||||
int m_version;
|
int m_version;
|
||||||
@ -102,20 +223,26 @@ namespace GameRes.Formats.Cri
|
|||||||
float m_rva_volume = 1.0f;
|
float m_rva_volume = 1.0f;
|
||||||
AthTable m_ath;
|
AthTable m_ath;
|
||||||
Cipher m_cipher;
|
Cipher m_cipher;
|
||||||
Channel[] m_channel;
|
|
||||||
byte[] m_block;
|
ThreadLocal<Channel[]> m_channel;
|
||||||
|
ThreadLocal<byte[]> m_block;
|
||||||
|
|
||||||
public byte[] Unpack (ConversionFormat target)
|
public byte[] Unpack (ConversionFormat target)
|
||||||
{
|
{
|
||||||
InitWaveFormat (target);
|
InitWaveFormat (target);
|
||||||
m_block = new byte[m_comp.BlockSize];
|
var output = new byte[BlockCount * 0x400 * m_format.BlockAlign];
|
||||||
var output = new byte[m_fmt_block_count.Value * 0x400 * m_format.BlockAlign];
|
var convert_sample = ConversionMap[target];
|
||||||
using (var mem = new MemoryStream (output))
|
using (var mem = new MemoryStream (output))
|
||||||
using (var writer = new BinaryWriter (mem))
|
using (var writer = new BinaryWriter (mem))
|
||||||
Decode (writer, ConversionMap[target]);
|
ConvertSequential (f => convert_sample (f, writer));
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InitConversion (ConversionFormat target)
|
||||||
|
{
|
||||||
|
InitWaveFormat (target);
|
||||||
|
}
|
||||||
|
|
||||||
void InitWaveFormat (ConversionFormat target)
|
void InitWaveFormat (ConversionFormat target)
|
||||||
{
|
{
|
||||||
m_format.FormatTag = (ushort)target;
|
m_format.FormatTag = (ushort)target;
|
||||||
@ -259,80 +386,127 @@ namespace GameRes.Formats.Cri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_channel = new Channel[m_format.Channels];
|
m_channel = new ThreadLocal<Channel[]> (() => {
|
||||||
for (int i = 0; i < m_channel.Length; ++i)
|
var channels = new Channel[m_format.Channels];
|
||||||
{
|
for (int i = 0; i < channels.Length; ++i)
|
||||||
m_channel[i] = new Channel (r[i], m_comp.R[5], m_comp.R[6]);
|
{
|
||||||
}
|
channels[i] = new Channel (r[i], m_comp.R[5], m_comp.R[6]);
|
||||||
|
}
|
||||||
|
return channels;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Decode (BinaryWriter output, SampleConverter pack_sample)
|
void ConvertSequential (Action<float> pack_sample)
|
||||||
{
|
{
|
||||||
int block_offset = m_data_offset;
|
foreach (var block_offset in GetBlockOffsets())
|
||||||
for (uint i = 0; i < m_fmt_block_count; ++i)
|
|
||||||
{
|
{
|
||||||
if (block_offset + m_comp.BlockSize > m_input.Length)
|
if (block_offset + m_comp.BlockSize > m_input.Length)
|
||||||
throw new EndOfStreamException();
|
throw new EndOfStreamException();
|
||||||
Buffer.BlockCopy (m_input, block_offset, m_block, 0, m_comp.BlockSize);
|
Buffer.BlockCopy (m_input, block_offset, m_block.Value, 0, m_comp.BlockSize);
|
||||||
block_offset += m_comp.BlockSize;
|
|
||||||
DecodeBlock();
|
DecodeBlock();
|
||||||
for (int j = 0; j < 8; ++j)
|
for (int j = 0; j < 8; ++j)
|
||||||
for (int k = 0; k < 0x80; ++k)
|
for (int k = 0; k < 0x80; ++k)
|
||||||
for (int c = 0; c < m_channel.Length; ++c)
|
for (int c = 0; c < m_format.Channels; ++c)
|
||||||
{
|
{
|
||||||
float f = m_channel[c].Samples[j,k] * m_rva_volume;
|
float f = m_channel.Value[c].Samples[j,k] * m_rva_volume;
|
||||||
pack_sample (f, output);
|
pack_sample (f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ConvertParallel<SampleType> (BlockingCollection<Tuple<int, Array>> output, Func<float, SampleType> convert_sample, CancellationToken token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// despite the fact that parallel decoding is considerably faster (roughly x[number of cores])
|
||||||
|
// it hurts playback badly due to locks inside BlockingCollection.Take()
|
||||||
|
// Parallel.ForEach (Enumerable.Range (0, (int)BlockCount), block_num =>
|
||||||
|
foreach (int block_num in Enumerable.Range (0, (int)BlockCount))
|
||||||
|
{
|
||||||
|
int block_offset = m_data_offset + block_num * m_comp.BlockSize;
|
||||||
|
if (block_offset + m_comp.BlockSize > m_input.Length)
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
Buffer.BlockCopy (m_input, block_offset, m_block.Value, 0, m_comp.BlockSize);
|
||||||
|
DecodeBlock();
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
var decoded = new SampleType[0x400 * m_format.Channels];
|
||||||
|
int i = 0;
|
||||||
|
for (int j = 0; j < 8; ++j)
|
||||||
|
for (int k = 0; k < 0x80; ++k)
|
||||||
|
for (int c = 0; c < m_format.Channels; ++c)
|
||||||
|
{
|
||||||
|
float f = m_channel.Value[c].Samples[j,k] * m_rva_volume;
|
||||||
|
decoded[i++] = convert_sample (f);
|
||||||
|
}
|
||||||
|
output.Add (new Tuple<int, Array> (block_num, decoded), token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
output.CompleteAdding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<int> GetBlockOffsets ()
|
||||||
|
{
|
||||||
|
int block_offset = m_data_offset;
|
||||||
|
for (int i = 0; i < m_fmt_block_count; ++i)
|
||||||
|
{
|
||||||
|
yield return block_offset;
|
||||||
|
block_offset += m_comp.BlockSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DecodeBlock ()
|
void DecodeBlock ()
|
||||||
{
|
{
|
||||||
if (CheckSum (m_block) != 0)
|
var block = m_block.Value;
|
||||||
|
if (CheckSum (block) != 0)
|
||||||
throw new InvalidFormatException ("Data checksum mismatch");
|
throw new InvalidFormatException ("Data checksum mismatch");
|
||||||
|
|
||||||
m_cipher.Decipher (m_block);
|
m_cipher.Decipher (block);
|
||||||
using (var input = new MemoryStream (m_block, 0, m_block.Length-2))
|
using (var input = new MemoryStream (block, 0, block.Length-2))
|
||||||
using (var bits = new HsaBitStream (input))
|
using (var bits = new HsaBitStream (input))
|
||||||
{
|
{
|
||||||
if(0xFFFF == bits.GetBits (16))
|
if (0xFFFF != bits.GetBits (16))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var decoder = m_channel.Value;
|
||||||
|
int t = bits.GetBits (9) << 8;
|
||||||
|
t -= bits.GetBits (7);
|
||||||
|
for (int i = 0; i < decoder.Length; ++i)
|
||||||
|
decoder[i].Decode1 (bits, m_comp.R9, t, m_ath.Table);
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
{
|
{
|
||||||
int t = bits.GetBits (9) << 8;
|
for (int j = 0; j < decoder.Length; ++j)
|
||||||
t -= bits.GetBits (7);
|
decoder[j].Decode2 (bits);
|
||||||
for (int i = 0; i < m_format.Channels; ++i)
|
for (int j = 0; j < decoder.Length; ++j)
|
||||||
m_channel[i].Decode1 (bits, m_comp.R9, t, m_ath.Table);
|
decoder[j].Decode3 (m_comp.R9, m_comp.R[7], m_comp.R[6] + m_comp.R[5], m_comp.R[4]);
|
||||||
for (int i = 0; i < 8; ++i)
|
for (int j = 0; j < decoder.Length-1; ++j)
|
||||||
{
|
decoder[j].Decode4 (i, m_comp.R[4]-m_comp.R[5], m_comp.R[5], m_comp.R[6], decoder[j+1]);
|
||||||
for (int j = 0; j < m_channel.Length; ++j)
|
for (int j = 0; j < decoder.Length; ++j)
|
||||||
m_channel[j].Decode2 (bits);
|
decoder[j].Decode5 (i);
|
||||||
for (int j = 0; j < m_channel.Length; ++j)
|
|
||||||
m_channel[j].Decode3 (m_comp.R9, m_comp.R[7], m_comp.R[6] + m_comp.R[5], m_comp.R[4]);
|
|
||||||
for (int j = 0; j < m_channel.Length-1; ++j)
|
|
||||||
m_channel[j].Decode4 (i, m_comp.R[4]-m_comp.R[5], m_comp.R[5], m_comp.R[6], m_channel[j+1]);
|
|
||||||
for (int j = 0; j < m_channel.Length; ++j)
|
|
||||||
m_channel[j].Decode5 (i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PackSampleFloat (float f, BinaryWriter output)
|
public static float PackSampleFloat (float f)
|
||||||
{
|
{
|
||||||
if (f > 1)
|
if (f > 1)
|
||||||
f = 1;
|
f = 1;
|
||||||
else if (f < -1)
|
else if (f < -1)
|
||||||
f = -1;
|
f = -1;
|
||||||
output.Write (f);
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PackSample16 (float f, BinaryWriter output)
|
public static short PackSample16 (float f)
|
||||||
{
|
{
|
||||||
int v = (int)(f * 0x7FFF);
|
int s = (int)(f * 0x7FFF);
|
||||||
if (v > 0x7FFF)
|
if (s > 0x7FFF)
|
||||||
v = 0x7FFF;
|
s = 0x7FFF;
|
||||||
else if (v < -0x7FFF)
|
else if (s < -0x7FFF)
|
||||||
v = -0x7FFF;
|
s = -0x7FFF;
|
||||||
output.Write ((short)v);
|
return (short)s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint ReadSignature (BigEndianReader input)
|
static uint ReadSignature (BigEndianReader input)
|
||||||
@ -982,6 +1156,8 @@ namespace GameRes.Formats.Cri
|
|||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
{
|
{
|
||||||
|
m_channel.Dispose();
|
||||||
|
m_block.Dispose();
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user