diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 8250ae94..560ea96c 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -91,6 +91,7 @@
+
diff --git a/ArcFormats/AudioWMA.cs b/ArcFormats/AudioWMA.cs
index 684ea59b..bf452d37 100644
--- a/ArcFormats/AudioWMA.cs
+++ b/ArcFormats/AudioWMA.cs
@@ -26,6 +26,11 @@
using System;
using System.ComponentModel.Composition;
using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using NAudio.CoreAudioApi.Interfaces;
+using NAudio.MediaFoundation;
+using NAudio.Utils;
using NAudio.Wave;
namespace GameRes.Formats
@@ -66,15 +71,20 @@ namespace GameRes.Formats
public WmaInput (Stream file) : base (file)
{
- m_reader = new StreamMediaFoundationReader (file);
- m_bitrate = m_reader.WaveFormat.AverageBytesPerSecond * 8;
- var format = new GameRes.WaveFormat();
- format.FormatTag = (ushort)m_reader.WaveFormat.Encoding;
- format.Channels = (ushort)m_reader.WaveFormat.Channels;
- format.SamplesPerSecond = (uint)m_reader.WaveFormat.SampleRate;
- format.BitsPerSample = (ushort)m_reader.WaveFormat.BitsPerSample;
- format.BlockAlign = (ushort)m_reader.BlockAlign;
- format.AverageBytesPerSecond = (uint)m_reader.WaveFormat.AverageBytesPerSecond;
+ var reader = new CustomMediaFoundationReader (file);
+ if (reader.Duration != 0)
+ m_bitrate = (int)(file.Length * 80000000L / reader.Duration);
+ else
+ m_bitrate = reader.WaveFormat.AverageBytesPerSecond * 8;
+ m_reader = reader;
+ var format = new GameRes.WaveFormat {
+ FormatTag = (ushort)m_reader.WaveFormat.Encoding,
+ Channels = (ushort)m_reader.WaveFormat.Channels,
+ SamplesPerSecond = (uint)m_reader.WaveFormat.SampleRate,
+ BitsPerSample = (ushort)m_reader.WaveFormat.BitsPerSample,
+ BlockAlign = (ushort)m_reader.BlockAlign,
+ AverageBytesPerSecond = (uint)m_reader.WaveFormat.AverageBytesPerSecond,
+ };
this.Format = format;
this.PcmSize = m_reader.Length;
}
@@ -100,4 +110,156 @@ namespace GameRes.Formats
}
#endregion
}
+
+ ///
+ /// Custom implementation of MediaFoundationReader.
+ /// NAudio uses MFCreateMFByteStreamOnStreamEx API call which is not available in Windows 7.
+ ///
+ internal class CustomMediaFoundationReader : MediaFoundationReader
+ {
+ private readonly Stream m_stream;
+
+ ///
+ /// Specifies the duration of a presentation, in 100-nanosecond units.
+ ///
+ public long Duration { get; private set; }
+
+ public CustomMediaFoundationReader (Stream stream, MediaFoundationReaderSettings settings = null)
+ {
+ m_stream = stream;
+ Init (settings);
+ }
+
+ protected override IMFSourceReader CreateReader (MediaFoundationReaderSettings settings)
+ {
+ IMFByteStream byteStream;
+ MFCreateMFByteStreamOnStream (new ComStream (m_stream), out byteStream);
+ var source_reader = MediaFoundationApi.CreateSourceReaderFromByteStream (byteStream);
+
+ source_reader.SetStreamSelection (-2, false);
+ source_reader.SetStreamSelection (-3, true);
+ source_reader.SetCurrentMediaType (-3, IntPtr.Zero, new MediaType
+ {
+ MajorType = MediaTypes.MFMediaType_Audio,
+ SubType = settings.RequestFloatOutput ? AudioSubtypes.MFAudioFormat_Float : AudioSubtypes.MFAudioFormat_PCM
+ }.MediaFoundationObject);
+
+ Duration = GetDuration (source_reader);
+
+ return source_reader;
+ }
+
+ private static long GetDuration (IMFSourceReader reader)
+ {
+ var variantPtr = Marshal.AllocHGlobal (MarshalHelpers.SizeOf());
+ try
+ {
+ int hResult = reader.GetPresentationAttribute (MediaFoundationInterop.MF_SOURCE_READER_MEDIASOURCE,
+ MediaFoundationAttributes.MF_PD_DURATION, variantPtr);
+ if (hResult == MediaFoundationErrors.MF_E_ATTRIBUTENOTFOUND)
+ return 0;
+ if (hResult != 0)
+ Marshal.ThrowExceptionForHR (hResult);
+
+ var variant = MarshalHelpers.PtrToStructure (variantPtr);
+ return (long)variant.Value;
+ }
+ finally
+ {
+ PropVariant.Clear (variantPtr);
+ Marshal.FreeHGlobal (variantPtr);
+ }
+ }
+
+ [DllImport("mfplat.dll", ExactSpelling = true, PreserveSig = false)]
+ static extern void MFCreateMFByteStreamOnStream (IStream pStream, out IMFByteStream ppByteStream);
+ }
+
+ ///
+ /// Implementation of Com IStream.
+ /// NAudio implementation is inaccessible (private).
+ ///
+ internal class ComStream : ProxyStream, IStream
+ {
+ public ComStream (Stream stream) : base (Synchronized (stream))
+ {
+ }
+
+ void IStream.Clone (out IStream ppstm)
+ {
+ ppstm = null;
+ }
+
+ void IStream.Commit (int grfCommitFlags)
+ {
+ Flush();
+ }
+
+ void IStream.CopyTo (IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
+ {
+ }
+
+ void IStream.LockRegion (long libOffset, long cb, int dwLockType)
+ {
+ }
+
+ void IStream.Read (byte[] pv, int cb, IntPtr pcbRead)
+ {
+ if (!CanRead)
+ throw new InvalidOperationException ("Stream is not readable.");
+ int read = Read (pv, 0, cb);
+ if (pcbRead != IntPtr.Zero)
+ Marshal.WriteInt32 (pcbRead, read);
+ }
+
+ void IStream.Revert()
+ {
+ }
+
+ void IStream.Seek (long dlibMove, int dwOrigin, IntPtr plibNewPosition)
+ {
+ SeekOrigin origin = (SeekOrigin) dwOrigin;
+ long val = Seek (dlibMove, origin);
+ if (plibNewPosition != IntPtr.Zero)
+ Marshal.WriteInt64 (plibNewPosition, val);
+ }
+
+ void IStream.SetSize (long libNewSize)
+ {
+ SetLength (libNewSize);
+ }
+
+ void IStream.Stat (out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
+ {
+ const int STGM_READ = 0x00000000;
+ const int STGM_WRITE = 0x00000001;
+ const int STGM_READWRITE = 0x00000002;
+
+ var tmp = new System.Runtime.InteropServices.ComTypes.STATSTG { type = 2, cbSize = Length, grfMode = 0 };
+
+ if (CanWrite && CanRead)
+ tmp.grfMode |= STGM_READWRITE;
+ else if (CanRead)
+ tmp.grfMode |= STGM_READ;
+ else if (CanWrite)
+ tmp.grfMode |= STGM_WRITE;
+ else
+ throw new ObjectDisposedException ("Stream");
+
+ pstatstg = tmp;
+ }
+
+ void IStream.UnlockRegion (long libOffset, long cb, int dwLockType)
+ {
+ }
+
+ void IStream.Write (byte[] pv, int cb, IntPtr pcbWritten)
+ {
+ if (!CanWrite)
+ throw new InvalidOperationException ("Stream is not writeable.");
+ Write (pv, 0, cb);
+ if (pcbWritten != IntPtr.Zero)
+ Marshal.WriteInt32 (pcbWritten, cb);
+ }
+ }
}