OSDN Git Service

XA形式サウンドファイルに対応。
authorくまかみ工房 <kumakamikoubou@gmail.com>
Mon, 16 Oct 2017 11:01:07 +0000 (20:01 +0900)
committerくまかみ工房 <kumakamikoubou@gmail.com>
Mon, 16 Oct 2017 11:01:07 +0000 (20:01 +0900)
xadec.dll をプロジェクトに追加。
「_EncodedWaveData」を「_DecodedWaveData」に訂正。

FDK/FDK.csproj
FDK/xadec.dll [new file with mode: 0644]
FDK/メディア/サウンド/WASAPI/MediaFoundationWaveSource.cs
FDK/メディア/サウンド/WASAPI/NVorbisResampledWaveSource.cs
FDK/メディア/サウンド/WASAPI/NVorbisSampleSource.cs
FDK/メディア/サウンド/WASAPI/SampleSourceFactory.cs
FDK/メディア/サウンド/WASAPI/XAWaveSource.cs [new file with mode: 0644]
FDK/メディア/サウンド/WASAPI/XaResampledWaveSource.cs [new file with mode: 0644]

index 15982e3..3c1161d 100644 (file)
     <Compile Include="メディア\サウンド\WASAPI\Sound.cs" />
     <Compile Include="メディア\サウンド\WASAPI\SoundDevice.cs" />
     <Compile Include="メディア\サウンド\WASAPI\SoundTimer.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\XaResampledWaveSource.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\XAWaveSource.cs" />
     <Compile Include="メディア\テクスチャ.cs" />
     <Compile Include="メディア\テクスチャフォント.cs" />
     <Compile Include="メディア\ビットマップ付きテクスチャ.cs" />
   <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="xadec.dll">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/FDK/xadec.dll b/FDK/xadec.dll
new file mode 100644 (file)
index 0000000..a415cdb
Binary files /dev/null and b/FDK/xadec.dll differ
index 5f249b4..24e5b86 100644 (file)
@@ -33,7 +33,7 @@ namespace FDK.メディア.サウンド.WASAPI
                ///             デコード後のオーディオデータのすべての長さ[byte]。
                /// </summary>
                public long Length
-                       => this._EncodedWaveData.Length;
+                       => this._DecodedWaveData.Length;
 
                /// <summary>
                ///             現在の再生位置[byte]。
@@ -51,8 +51,10 @@ namespace FDK.メディア.サウンド.WASAPI
                ///             コンストラクタ。
                ///             指定されたファイルを指定されたフォーマットでデコードし、内部にオンメモリで保管する。
                /// </summary>
-               public MediaFoundationWaveSource( string path, WaveFormat deviceFormat )
+               public MediaFoundationWaveSource( string ファイルパス, WaveFormat deviceFormat )
                {
+                       var path = Folder.絶対パスに含まれるフォルダ変数を展開して返す( ファイルパス );
+
                        this.WaveFormat = new WaveFormat(
                                deviceFormat.SampleRate,                // 指定されたレート
                                32,                                                     // 32bit 固定
@@ -128,7 +130,7 @@ namespace FDK.メディア.サウンド.WASAPI
                                }
 
                                // (4) ストリームの内容を byte 配列に出力する。
-                               this._EncodedWaveData = waveStream.ToArray();
+                               this._DecodedWaveData = waveStream.ToArray();
                        }
                }
 
@@ -137,7 +139,7 @@ namespace FDK.メディア.サウンド.WASAPI
                /// </summary>
                public void Dispose()
                {
-                       this._EncodedWaveData = null;
+                       this._DecodedWaveData = null;
                        FDKUtilities.解放する( ref this._MediaType );
                }
 
@@ -151,7 +153,7 @@ namespace FDK.メディア.サウンド.WASAPI
                public int Read( byte[] buffer, int offset, int count )
                {
                        // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
-                       if( ( null == this._EncodedWaveData ) || ( null == buffer ) )
+                       if( ( null == this._DecodedWaveData ) || ( null == buffer ) )
                                return 0;
 
                        long 読み込み可能な最大count = ( this.Length - this._Position );
@@ -161,7 +163,7 @@ namespace FDK.メディア.サウンド.WASAPI
                        if( 0 < count )
                        {
                                Buffer.BlockCopy(
-                                       src: this._EncodedWaveData,
+                                       src: this._DecodedWaveData,
                                        srcOffset: (int) this._Position,
                                        dst: buffer,
                                        dstOffset: offset,
@@ -174,7 +176,7 @@ namespace FDK.メディア.サウンド.WASAPI
                }
 
                private MFMediaType _MediaType = null;
-               private byte[] _EncodedWaveData = null;
+               private byte[] _DecodedWaveData = null;
                private long _Position = 0;
        }
 }
index 902c687..2294fe1 100644 (file)
@@ -30,7 +30,7 @@ namespace FDK.メディア.サウンド.WASAPI
                ///             デコード後のオーディオデータのすべての長さ[byte]。
                /// </summary>
                public long Length
-                       => this._EncodedWaveData.Length;
+                       => this._DecodedWaveData.Length;
 
                /// <summary>
                ///             現在の再生位置[byte]。
@@ -63,8 +63,8 @@ namespace FDK.メディア.サウンド.WASAPI
                                // resampler.Length はサンプル単位ではなくフレーム単位。
                                var サイズbyte = resampler.Length * resampler.WaveFormat.Channels; // 実際のサイズはチャンネル倍ある。
 
-                               this._EncodedWaveData = new byte[ サイズbyte ];
-                               resampler.Read( this._EncodedWaveData, 0, (int) サイズbyte );        // でもこっちはバイト単位。
+                               this._DecodedWaveData = new byte[ サイズbyte ];
+                               resampler.Read( this._DecodedWaveData, 0, (int) サイズbyte );        // でもこっちはバイト単位。
                        }
                }
 
@@ -78,7 +78,7 @@ namespace FDK.メディア.サウンド.WASAPI
                public int Read( byte[] buffer, int offset, int count )
                {
                        // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
-                       if( ( null == this._EncodedWaveData ) || ( null == buffer ) )
+                       if( ( null == this._DecodedWaveData ) || ( null == buffer ) )
                                return 0;
 
                        long 読み込み可能な最大count = ( this.Length - this._Position );
@@ -88,7 +88,7 @@ namespace FDK.メディア.サウンド.WASAPI
                        if( 0 < count )
                        {
                                Buffer.BlockCopy(
-                                       src: this._EncodedWaveData,
+                                       src: this._DecodedWaveData,
                                        srcOffset: (int) this._Position,
                                        dst: buffer,
                                        dstOffset: offset,
@@ -105,10 +105,10 @@ namespace FDK.メディア.サウンド.WASAPI
                /// </summary>
                public void Dispose()
                {
-                       this._EncodedWaveData = null;
+                       this._DecodedWaveData = null;
                }
 
-               private byte[] _EncodedWaveData = null;
+               private byte[] _DecodedWaveData = null;
                private long _Position = 0;
        }
 }
index cd9f461..ba77e85 100644 (file)
@@ -9,7 +9,7 @@ using NVorbis;
 namespace FDK.メディア.サウンド.WASAPI
 {
        /// <summary>
-       ///             指定されたメディアファイル(動画, 音楽)を Vorbis としてデコードして、CSCore.IWaveSource オブジェクトを生成する。
+       ///             指定されたメディアファイル(動画, 音楽)を Vorbis としてデコードして、CSCore.ISampleSource オブジェクトを生成する。
        ///             リサンプラーなし版。
        ///             参照:<seealso cref="https://cscore.codeplex.com/SourceControl/latest#Samples/NVorbisIntegration/Program.cs"/>
        /// </summary>
index f8b96de..7d6e640 100644 (file)
@@ -17,34 +17,41 @@ namespace FDK.メディア.サウンド.WASAPI
                public static ISampleSource Create( SoundDevice device, string ファイルパス )
                {
                        var path = Folder.絶対パスに含まれるフォルダ変数を展開して返す( ファイルパス );
+                       var ext = Path.GetExtension( path ).ToLower();
 
                        #region " NVorbis を試みる "
                        //----------------
-                       try
+                       if( ".ogg" == ext )
                        {
-                               using( var audioStream = new FileStream( path, FileMode.Open ) )
+                               try
                                {
-                                       return new NVorbisResampledWaveSource( audioStream, device.WaveFormat )
-                                               .ToSampleSource();
+                                       using( var audioStream = new FileStream( path, FileMode.Open ) )
+                                       {
+                                               return new NVorbisResampledWaveSource( audioStream, device.WaveFormat )
+                                                       .ToSampleSource();
+                                       }
+                               }
+                               catch
+                               {
+                                       // ダメだったので次へ。
                                }
-                       }
-                       catch
-                       {
-                               // ダメだったので次へ。
                        }
                        //----------------
                        #endregion
 
-                       #region " CSCore を試みる "
+                       #region " XA を試みる "
                        //----------------
-                       try
+                       if( ".xa" == ext )
                        {
-                               // 対応できるフォーマットは MediaFoundation とダブるので、こちらの実装は不要かも。
-                               //return new CSCoreSampleSource( path );
-                       }
-                       catch
-                       {
-                               // ダメだったので次へ。
+                               try
+                               {
+                                       return new XaResampledWaveSource( path, device.WaveFormat )
+                                               .ToSampleSource();
+                               }
+                               catch
+                               {
+                                       // ダメだったので次へ。
+                               }
                        }
                        //----------------
                        #endregion
diff --git a/FDK/メディア/サウンド/WASAPI/XAWaveSource.cs b/FDK/メディア/サウンド/WASAPI/XAWaveSource.cs
new file mode 100644 (file)
index 0000000..4b80c47
--- /dev/null
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using CSCore;
+using CSCore.DSP;
+
+namespace FDK.メディア.サウンド.WASAPI
+{
+       /// <summary>
+       ///             指定されたメディアファイルを XA としてデコードして、CSCore.IWaveSource オブジェクトを生成する。
+       ///             リサンプラーなし版。
+       /// </summary>
+       unsafe class XAWaveSource : IWaveSource
+       {
+               public bool CanSeek => true; // オンメモリなので常にサポートする。
+
+               public WaveFormat WaveFormat
+               {
+                       get;
+                       protected set;
+               } = null;
+
+               /// <summary>
+               ///             デコード後のオーディオデータのすべての長さ[byte]。
+               /// </summary>
+               public long Length
+                       => this._DecodedWaveData.Length;
+
+               /// <summary>
+               ///             現在の再生位置[byte]。
+               /// </summary>
+               public long Position
+               {
+                       get
+                               => this._Position;
+                       set
+                               => this._Position = FDKUtilities.位置をブロック境界単位にそろえて返す( value, this.WaveFormat.BlockAlign );
+               }
+
+               /// <summary>
+               ///             コンストラクタ。
+               ///             指定されたファイルを指定されたフォーマットでデコードし、内部にオンメモリで保管する。
+               /// </summary>
+               public XAWaveSource( string ファイルパス, WaveFormat deviceFormat )
+               {
+                       var path = Folder.絶対パスに含まれるフォルダ変数を展開して返す( ファイルパス );
+
+                       var xaheader = new XAHEADER();
+                       var srcBuf = (byte[]) null;
+
+                       #region " XAHEADER と XAデータ を読み込みむ。"
+                       //----------------
+                       using( var br = new BinaryReader( new FileStream( path, FileMode.Open ) ) )
+                       {
+                               xaheader.id = br.ReadUInt32();
+                               xaheader.nDataLen = br.ReadUInt32();
+                               xaheader.nSamples = br.ReadUInt32();
+                               xaheader.nSamplesPerSec = br.ReadUInt16();
+                               xaheader.nBits = br.ReadByte();
+                               xaheader.nChannels = br.ReadByte();
+                               xaheader.nLoopPtr = br.ReadUInt32();
+                               xaheader.befL = new short[ 2 ];
+                               xaheader.befL[ 0 ] = br.ReadInt16();
+                               xaheader.befL[ 1 ] = br.ReadInt16();
+                               xaheader.befR = new short[ 2 ];
+                               xaheader.befR[ 0 ] = br.ReadInt16();
+                               xaheader.befR[ 1 ] = br.ReadInt16();
+                               xaheader.pad = new byte[ 4 ];
+                               xaheader.pad = br.ReadBytes( 4 );
+
+                               srcBuf = br.ReadBytes( (int) xaheader.nDataLen );
+                       }
+                       //----------------
+                       #endregion
+
+                       var waveformatex = new WAVEFORMATEX();
+                       var handlePtr = IntPtr.Zero;
+
+                       #region " XAファイルをオープンし、Waveフォーマットとハンドルを取得。"
+                       //----------------
+                       handlePtr = xaDecodeOpen( ref xaheader, out waveformatex );
+
+                       if( null == handlePtr || IntPtr.Zero == handlePtr )
+                               throw new Exception( $"xaDecodeOpen に失敗しました。[{ファイルパス}]" );
+                       //----------------
+                       #endregion
+
+                       #region " Waveフォーマットを WaveFormat プロパティに設定。"
+                       //----------------
+                       if( 0 == waveformatex.cbSize )
+                       {
+                               this.WaveFormat = new WaveFormat(
+                                       (int) waveformatex.nSamplesPerSec,
+                                       (int) waveformatex.wBitsPerSample,
+                                       (int) waveformatex.nChannels,
+                                       (AudioEncoding) waveformatex.wFormatTag );
+                       }
+                       else
+                       {
+                               var msg = $"デコード後のフォーマットが WAVEFORMATEX 型になる XA には未対応です。[{ファイルパス}]";
+                               Log.ERROR( msg );
+                               throw new Exception( msg );
+                       }
+                       //----------------
+                       #endregion
+
+                       #region " デコード後のPCMサイズ[byte]を取得し、バッファを確保する。"
+                       //----------------
+                       if( !( xaDecodeSize( handlePtr, xaheader.nDataLen, out uint decodedWaveDataLength ) ) )
+                       {
+                               var msg = $"xaDecodeSize に失敗しました。[{ファイルパス}]";
+                               Log.ERROR( msg );
+                               throw new Exception( msg );
+                       }
+                       this._DecodedWaveData = new byte[ decodedWaveDataLength ];
+                       //----------------
+                       #endregion
+
+                       #region " デコードする。"
+                       //----------------
+                       unsafe
+                       {
+                               fixed ( byte* pXaBuf = srcBuf )
+                               fixed ( byte* pPcmBuf = this._DecodedWaveData )
+                               {
+                                       var xastreamheader = new XASTREAMHEADER() {
+                                               pSrc = pXaBuf,
+                                               nSrcLen = xaheader.nDataLen,
+                                               nSrcUsed = 0,
+                                               pDst = pPcmBuf,
+                                               nDstLen = decodedWaveDataLength,
+                                               nDstUsed = 0,
+                                       };
+                                       if( !( xaDecodeConvert( handlePtr, ref xastreamheader ) ) )
+                                       {
+                                               var msg = $"xaDecodeConvert に失敗しました。[{ファイルパス}]";
+                                               Log.ERROR( msg );
+                                               throw new Exception( msg );
+                                       }
+                               }
+                       }
+                       //----------------
+                       #endregion
+
+                       #region " XAファイルを閉じる。"
+                       //----------------
+                       if( !( xaDecodeClose( handlePtr ) ) )
+                       {
+                               var msg = $"xaDecodeClose に失敗しました。[{ファイルパス}]";
+                               Log.ERROR( msg );
+                               throw new Exception( msg );
+                       }
+                       //----------------
+                       #endregion
+               }
+
+               /// <summary>
+               ///             解放する。
+               /// </summary>
+               public void Dispose()
+               {
+                       this._DecodedWaveData = null;
+               }
+
+               /// <summary>
+               ///             連続したデータを読み込み、<see cref="Position"/> を読み込んだ数だけ進める。
+               /// </summary>
+               /// <param name="buffer">読み込んだデータを格納するための配列。</param>
+               /// <param name="offset"><paramref name="buffer"/> に格納を始める位置。</param>
+               /// <param name="count">読み込む最大のデータ数。</param>
+               /// <returns><paramref name="buffer"/> に読み込んだデータの総数。</returns>
+               public int Read( byte[] buffer, int offset, int count )
+               {
+                       // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
+                       if( ( null == this._DecodedWaveData ) || ( null == buffer ) )
+                               return 0;
+
+                       long 読み込み可能な最大count = ( this.Length - this._Position );
+                       if( count > 読み込み可能な最大count )
+                               count = (int) 読み込み可能な最大count;
+
+                       if( 0 < count )
+                       {
+                               Buffer.BlockCopy(
+                                       src: this._DecodedWaveData,
+                                       srcOffset: (int) this._Position,
+                                       dst: buffer,
+                                       dstOffset: offset,
+                                       count: count );
+
+                               this._Position += count;
+                       }
+
+                       return count;
+               }
+
+               private byte[] _DecodedWaveData = null;
+               private long _Position = 0;
+
+               #region " Win32(xsdec.dll) "
+               //----------------
+               [StructLayout( LayoutKind.Sequential )]
+               public struct WAVEFORMATEX
+               {
+                       public ushort wFormatTag;
+                       public ushort nChannels;
+                       public uint nSamplesPerSec;
+                       public uint nAvgBytesPerSec;
+                       public ushort nBlockAlign;
+                       public ushort wBitsPerSample;
+                       public ushort cbSize;
+               }
+
+               [StructLayout( LayoutKind.Sequential )]
+               public struct XASTREAMHEADER
+               {
+                       public byte* pSrc;
+                       public uint nSrcLen;
+                       public uint nSrcUsed;
+                       public byte* pDst;
+                       public uint nDstLen;
+                       public uint nDstUsed;
+               }
+
+               [StructLayout( LayoutKind.Sequential )]
+               public struct XAHEADER
+               {
+                       public uint id;
+                       public uint nDataLen;
+                       public uint nSamples;
+                       public ushort nSamplesPerSec;
+                       public byte nBits;
+                       public byte nChannels;
+                       public uint nLoopPtr;
+                       [MarshalAs( UnmanagedType.ByValArray, SizeConst = 2 )]
+                       public short[] befL;
+                       [MarshalAs( UnmanagedType.ByValArray, SizeConst = 2 )]
+                       public short[] befR;
+                       [MarshalAs( UnmanagedType.ByValArray, SizeConst = 4 )]
+                       public byte[] pad;
+               }
+
+               [DllImport( "xadec.dll", EntryPoint = "xaDecodeOpen", CallingConvention = CallingConvention.Cdecl )]
+               public extern static IntPtr xaDecodeOpen( ref XAHEADER pxah, out WAVEFORMATEX pwfx );
+
+               [DllImport( "xadec.dll", EntryPoint = "xaDecodeClose", CallingConvention = CallingConvention.Cdecl )]
+               public extern static bool xaDecodeClose( IntPtr hxas );
+
+               [DllImport( "xadec.dll", EntryPoint = "xaDecodeSize", CallingConvention = CallingConvention.Cdecl )]
+               public extern static bool xaDecodeSize( IntPtr hxas, uint slen, out uint pdlen );
+
+               [DllImport( "xadec.dll", EntryPoint = "xaDecodeConvert", CallingConvention = CallingConvention.Cdecl )]
+               public extern static bool xaDecodeConvert( IntPtr hxas, ref XASTREAMHEADER psh );
+               //----------------
+               #endregion
+       }
+}
diff --git a/FDK/メディア/サウンド/WASAPI/XaResampledWaveSource.cs b/FDK/メディア/サウンド/WASAPI/XaResampledWaveSource.cs
new file mode 100644 (file)
index 0000000..942dd30
--- /dev/null
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using CSCore;
+using CSCore.DSP;
+
+namespace FDK.メディア.サウンド.WASAPI
+{
+       /// <summary>
+       ///             指定されたメディアファイルを XA としてデコードして、CSCore.IWaveSource オブジェクトを生成する。
+       ///             リサンプラーあり版。
+       /// </summary>
+       class XaResampledWaveSource : IWaveSource
+       {
+               public bool CanSeek => true;    // オンメモリなので常にサポートできる。
+
+               /// <summary>
+               ///     デコード&リサンプル後のオーディオデータのフォーマット。
+               /// </summary>
+               public WaveFormat WaveFormat
+               {
+                       get;
+                       protected set;
+               } = null;
+
+               /// <summary>
+               ///             デコード後のオーディオデータのすべての長さ[byte]。
+               /// </summary>
+               public long Length
+                       => this._DecodedWaveData.Length;
+
+               /// <summary>
+               ///             現在の再生位置[byte]。
+               /// </summary>
+               public long Position
+               {
+                       get
+                               => this._Position;
+                       set
+                               => this._Position = FDKUtilities.位置をブロック境界単位にそろえて返す( value, this.WaveFormat.BlockAlign );
+               }
+
+               /// <summary>
+               ///             コンストラクタ。
+               ///             指定されたXAファイルを指定されたフォーマットでデコードし、内部にオンメモリで保管する。
+               /// </summary>
+               public XaResampledWaveSource( string ファイルパス, WaveFormat deviceFormat )
+               {
+                       this.WaveFormat = new WaveFormat(
+                               deviceFormat.SampleRate,
+                               32,
+                               deviceFormat.Channels,
+                               AudioEncoding.IeeeFloat );
+
+                       // リサンプルなし版で生成して、それを this.WaveFormat に合わせてリサンプルしたデータ(byte[])を保管する。
+                       using( var xaSource = new XAWaveSource( ファイルパス, deviceFormat ) )
+                       using( var resampler = new DmoResampler( xaSource, this.WaveFormat ) )
+                       {
+                               // resampler.Length はサンプル単位ではなくフレーム単位。
+                               var サイズbyte = resampler.Length * resampler.WaveFormat.Channels; // 実際のサイズはチャンネル倍ある。
+
+                               this._DecodedWaveData = new byte[ サイズbyte ];
+                               resampler.Read( this._DecodedWaveData, 0, (int) サイズbyte );  // でもこっちはバイト単位。
+                       }
+               }
+
+               /// <summary>
+               ///             連続したデータを読み込み、<see cref="Position"/> を読み込んだ数だけ進める。
+               /// </summary>
+               /// <param name="buffer">読み込んだデータを格納するための配列。</param>
+               /// <param name="offset"><paramref name="buffer"/> に格納を始める位置。</param>
+               /// <param name="count">読み込む最大のデータ数。</param>
+               /// <returns><paramref name="buffer"/> に読み込んだデータの総数。</returns>
+               public int Read( byte[] buffer, int offset, int count )
+               {
+                       // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
+                       if( ( null == this._DecodedWaveData ) || ( null == buffer ) )
+                               return 0;
+
+                       long 読み込み可能な最大count = ( this.Length - this._Position );
+                       if( count > 読み込み可能な最大count )
+                               count = (int) 読み込み可能な最大count;
+
+                       if( 0 < count )
+                       {
+                               Buffer.BlockCopy(
+                                       src: this._DecodedWaveData,
+                                       srcOffset: (int) this._Position,
+                                       dst: buffer,
+                                       dstOffset: offset,
+                                       count: count );
+
+                               this._Position += count;
+                       }
+
+                       return count;
+               }
+
+               /// <summary>
+               ///             解放する。
+               /// </summary>
+               public void Dispose()
+               {
+                       this._DecodedWaveData = null;
+               }
+
+               private byte[] _DecodedWaveData = null;
+               private long _Position = 0;
+       }
+}