From 31b114f7a486fb21f70e588c619e3a6b845f2ee2 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 29 May 2015 22:54:16 +0400 Subject: [PATCH] (MioInput): perform decoding in separate thread. --- ArcFormats/AudioMIO.cs | 131 +++++++++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 19 deletions(-) diff --git a/ArcFormats/AudioMIO.cs b/ArcFormats/AudioMIO.cs index 6c255dd8..4c55fec0 100644 --- a/ArcFormats/AudioMIO.cs +++ b/ArcFormats/AudioMIO.cs @@ -24,10 +24,13 @@ // using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; +using System.Threading; using GameRes.Utility; namespace GameRes.Formats.Entis @@ -68,10 +71,10 @@ namespace GameRes.Formats.Entis public uint BitsPerSample { get { return m_info.BitsPerSample; } } public override int SourceBitrate { get { return m_bitrate; } } - public override string SourceFormat { get { return "raw"; } } + public override string SourceFormat { get { return "mio"; } } #region Stream Members - public override bool CanSeek { get { return Source.CanSeek; } } + public override bool CanSeek { get { return m_decoded_stream.CanSeek; } } public override long Position { @@ -81,7 +84,7 @@ namespace GameRes.Formats.Entis public override int Read (byte[] buffer, int offset, int count) { - return m_decoded_stream.Read (buffer, offset, count); + return Read_Threaded (buffer, offset, count); } #endregion @@ -131,22 +134,22 @@ namespace GameRes.Formats.Entis if (EriCode.Nemesis != m_info.Architecture) m_pmioc = new HuffmanDecodeContext (0x10000); else - throw new NotImplementedException ("Nemesis encoding not implemented"); + throw new NotImplementedException ("MIO Nemesis encoding not implemented"); - int pcm_bitrate = (int)m_info.SamplesPerSec * 16 * m_info.ChannelCount; + int pcm_bitrate = (int)m_info.SamplesPerSec * 16 * ChannelCount; var format = new GameRes.WaveFormat(); format.FormatTag = 1; - format.Channels = (ushort)m_info.ChannelCount; + format.Channels = (ushort)ChannelCount; format.SamplesPerSecond = m_info.SamplesPerSec; - format.BitsPerSample = (ushort)m_info.BitsPerSample; - format.BlockAlign = (ushort)(m_info.BitsPerSample/8*format.Channels); + format.BitsPerSample = (ushort)BitsPerSample; + format.BlockAlign = (ushort)(BitsPerSample/8*format.Channels); format.AverageBytesPerSecond = (uint)pcm_bitrate/8; this.Format = format; m_decoded_stream = LoadChunks(); if (0 != m_total_samples) m_bitrate = (int)(stream_size * 8 * m_info.SamplesPerSec / m_total_samples); - this.PcmSize = m_decoded_stream.Length; + this.PcmSize = m_total_samples * ChannelCount * BitsPerSample / 8; m_decoded_stream.Position = 0; } catch @@ -259,24 +262,107 @@ namespace GameRes.Formats.Entis catch (EndOfStreamException) { /* ignore EOF errors */ } m_total_samples = current_sample; if (0 == m_total_samples) + { + m_decode_finished = true; return Stream.Null; - + } uint sample_bytes = (uint)ChannelCount * BitsPerSample / 8; var total_bytes = m_total_samples * sample_bytes; - var wave_buf = new byte[total_bytes]; - int current_pos = 0; - foreach (var chunk in chunks) + m_wait_handles = new WaitHandle[2] { m_available_chunk, m_decode_complete }; + m_worker.WorkerSupportsCancellation = true; + m_worker.DoWork += DoWork_Decode; + m_worker.RunWorkerAsync (chunks); + return new MemoryStream ((int)total_bytes); + } + + bool m_decode_finished = false; + AutoResetEvent m_decode_complete = new AutoResetEvent (false); + AutoResetEvent m_available_chunk = new AutoResetEvent (false); + WaitHandle[] m_wait_handles; + + ConcurrentQueue m_chunk_queue = new ConcurrentQueue(); + BackgroundWorker m_worker = new BackgroundWorker(); + Exception m_decode_error = null; + + private void DoWork_Decode (object sender, DoWorkEventArgs e) + { + try { - using (var input = new ChunkStream (Source, chunk)) + var worker = sender as BackgroundWorker; + var chunks = e.Argument as IEnumerable; + uint sample_bytes = (uint)ChannelCount * BitsPerSample / 8; + foreach (var chunk in chunks) { - m_pmioc.AttachInputFile (input); - if (!m_pmiod.DecodeSound (m_pmioc, chunk, wave_buf, current_pos)) - throw new InvalidFormatException(); - current_pos += (int)(chunk.SampleCount * sample_bytes); + if (worker.CancellationPending) + { + e.Cancel = true; + break; + } + using (var input = new ChunkStream (Source, chunk)) + { + var wave_buf = new byte[chunk.SampleCount * sample_bytes]; + m_pmioc.AttachInputFile (input); + if (!m_pmiod.DecodeSound (m_pmioc, chunk, wave_buf, 0)) + throw new InvalidFormatException(); + m_chunk_queue.Enqueue (wave_buf); + m_available_chunk.Set(); + } } } - return new MemoryStream (wave_buf); + catch (Exception X) + { + Trace.WriteLine (X.Message, "[MIO]"); + m_decode_error = X; + } + finally + { + m_decode_complete.Set(); + } + } + + private int Read_Threaded (byte[] buf, int idx, int count) + { + var current_pos = Position; + int total_read = 0; + if (current_pos < m_decoded_stream.Length) + { + int available_bytes = (int)(m_decoded_stream.Length - current_pos); + int read = m_decoded_stream.Read (buf, idx, Math.Min (count, available_bytes)); + idx += read; + count -= read; + total_read += read; + } + if (count > 0 && (!m_decode_finished || m_chunk_queue.Count > 0)) + { + current_pos = Position; + m_decoded_stream.Seek (0, SeekOrigin.End); + for (;;) + { + byte[] wave_buf = null; + while (m_chunk_queue.TryDequeue (out wave_buf)) + { + m_decoded_stream.Write (wave_buf, 0, wave_buf.Length); + if (current_pos + count <= m_decoded_stream.Length) + break; + } + if (m_decode_finished || (current_pos + count <= m_decoded_stream.Length)) + break; + int evt = WaitHandle.WaitAny (m_wait_handles); + if (1 == evt) + { + m_decode_finished = true; + if (m_decode_error != null) + { + m_decoded_stream.Position = current_pos; + throw m_decode_error; + } + } + } + m_decoded_stream.Position = current_pos; + total_read += m_decoded_stream.Read (buf, idx, count); + } + return total_read; } #region IDisposable Members @@ -286,9 +372,16 @@ namespace GameRes.Formats.Entis { if (disposing) { + if (!m_decode_finished) + { + m_worker.CancelAsync(); + m_decode_complete.WaitOne(); + } m_erif.Dispose(); if (m_decoded_stream != null) m_decoded_stream.Dispose(); + m_decode_complete.Dispose(); + m_available_chunk.Dispose(); } m_erif = null; base.Dispose (disposing);