using System; namespace FDK.メディア.サウンド.WASAPI排他 { public unsafe class Sound : IDisposable { public enum E再生状態 { 停止中, // 初期状態 再生中, 一時停止中, 再生終了, }; public E再生状態 再生状態 = E再生状態.停止中; /// /// 0.0(最小)~1.0(原音) の範囲で指定する。再生中でも反映される。 /// public float 音量 { set { float 設定値 = Math.Min( Math.Max( value, 0.0f ), 1.0f ); // 0.0未満は0.0へ、1.0超は1.0へ。 lock( this.排他利用 ) { this.bs_音量 = 設定値; } } get { lock( this.排他利用 ) { return this.bs_音量; } } } public double 長さsec { get { lock( this.排他利用 ) { return ( this.サウンドデータサイズsample / this.WAVEフォーマット.SampleRate ); } } } public Sound() { } public Sound( string サウンドファイルuri ) : this() { this.ファイルから作成する( サウンドファイルuri ); } public void ファイルから作成する( string サウンドファイルuri ) { lock( this.排他利用 ) { #region " 作成済みなら先にDisposeする。" //----------------- if( this.作成済み ) this.Dispose(); this.作成済み = false; //----------------- #endregion byte[] encodedPcm = null; using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( サウンドファイルuri ) ) using( var pcmStream = new System.IO.MemoryStream() ) { #region " サウンドファイル名から SourceReader を作成する。" //----------------- // 先述の using で作成済み。 // 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。 sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false ); sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true ); // メディアタイプを作成し、オーディオフォーマットを設定する。(固定フォーマットとする。) using( var mediaType = new SharpDX.MediaFoundation.MediaType() ) { mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Pcm ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WAVEフォーマット.Channels ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WAVEフォーマット.SampleRate ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WAVEフォーマット.BlockAlign ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WAVEフォーマット.AverageBytesPerSecond ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, this.WAVEフォーマット.BitsPerSample ); mediaType.Set( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE // 作成したメディアタイプを sourceReader にセットする。sourceReader は、必要なデコーダをロードするだろう。 sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, mediaType ); } // 最初のオーディオストリームが選択されていることを保証する。 sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true ); //----------------- #endregion #region " sourceReader からサンプルを取得してデコードし、メモリストリーム pcmStream へ書き込んだのち、encodedPcm へ変換する。" //----------------- using( var pcmWriter = new System.IO.BinaryWriter( pcmStream ) ) { while( true ) { // 次のサンプルを読み込む。 int dwActualStreamIndexRef = 0; var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None; Int64 llTimestampRef = 0; using( var sample = sourceReader.ReadSample( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, SharpDX.MediaFoundation.SourceReaderControlFlags.None, out dwActualStreamIndexRef, out dwStreamFlagsRef, out llTimestampRef ) ) { if( null == sample ) break; // EndOfStream やエラーも含まれる。 // サンプルをロックし、オーディオデータへのポインタを取得する。 int cbMaxLengthRef = 0; int cbCurrentLengthRef = 0; using( var mediaBuffer = sample.ConvertToContiguousBuffer() ) { // オーディオデータをメモリストリームに書き込む。 var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef ); byte[] dstData = new byte[ cbCurrentLengthRef ]; byte* psrcData = (byte*) audioData.ToPointer(); // fixed fixed ( byte* pdstData = dstData ) { CopyMemory( pdstData, psrcData, cbCurrentLengthRef ); } pcmWriter.Write( dstData, 0, cbCurrentLengthRef ); // サンプルのロックを解除する。 mediaBuffer.Unlock(); } } } // ストリームの内容を byte 配列に出力。(Position に関係なく全部出力される。) encodedPcm = pcmStream.ToArray(); } //----------------- #endregion } #region " オーバーサンプリングサウンドデータバッファを確保し、encodedPcm からサンプルを転送する。" //----------------- using( var pcmReader = new System.IO.BinaryReader( new System.IO.MemoryStream( encodedPcm ) ) ) { // PCMサイズを計算する。(16bit → 32bit でオーバーサンプリングする。) this.サウンドデータサイズbyte = encodedPcm.Length * 2; // 32bit は 16bit の2倍。 this.サウンドデータサイズsample = this.サウンドデータサイズbyte / 8; // 1sample = 32bit×2h = 64bit = 8bytes // オーバーサンプリングサウンドデータ用メモリを確保する。 this.サウンドデータ = (byte*) FDK.Memory.Alloc( this.サウンドデータサイズbyte ); // ストリームからオーバーサンプリングサウンドデータへ転送する。 var p = (Int32*) this.サウンドデータ; for( int i = 0; i < this.サウンドデータサイズsample; i++ ) { // 1サンプル = 2ch×INT16 を 2ch×INT32 に変換しながら格納。 *p++ = (Int32) pcmReader.ReadInt16(); // 左ch *p++ = (Int32) pcmReader.ReadInt16(); // 右ch } } //----------------- #endregion this.再生位置sample = 0; this.作成済み = true; } } public void 再生を開始する( double 再生開始位置sec = 0.0 ) { lock( this.排他利用 ) { if( false == this.作成済み ) return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。 int 開始位置sample = (int) ( 再生開始位置sec * this.WAVEフォーマット.SampleRate ); if( 開始位置sample < this.サウンドデータサイズsample ) { this.再生状態 = E再生状態.再生中; this.再生位置sample = 開始位置sample; } } } public void 再生を一時停止する() { lock( this.排他利用 ) { if( false == this.作成済み ) return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。 this.再生状態 = E再生状態.一時停止中; } } public void 再生を再開する() { lock( this.排他利用 ) { if( false == this.作成済み ) return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。 if( E再生状態.一時停止中 != this.再生状態 ) this.再生位置sample = 0; this.再生状態 = E再生状態.再生中; } } public void 再生を停止する() { lock( this.排他利用 ) { if( false == this.作成済み ) return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。 this.再生状態 = E再生状態.停止中; this.再生位置sample = 0; } } public CSCore.CoreAudioAPI.AudioClientBufferFlags 次のサウンドデータを出力する( void* 出力先, int 出力サンプル数, bool 最初の出力である ) { lock( this.排他利用 ) { #region " 未作成、または再生中でないなら無音フラグをもって帰還。" //----------------- if( ( false == this.作成済み ) || ( E再生状態.再生中 != this.再生状態 ) ) return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent; //----------------- #endregion int オーバーサンプルサイズbyte = 4 * 2; // 32bit×2ch Int32* 出力元 = (Int32*) ( this.サウンドデータ + ( this.再生位置sample * オーバーサンプルサイズbyte ) ); Int32* _出力先 = (Int32*) 出力先; // この実装ではサンプルは Int32 単位 int 出力できるサンプル数 = System.Math.Min( 出力サンプル数, ( this.サウンドデータサイズsample - this.再生位置sample ) ); int 出力できないサンプル数 = 出力サンプル数 - 出力できるサンプル数; if( 出力できるサンプル数 <= 0 ) this.再生状態 = E再生状態.再生終了; // 念のため if( 最初の出力である ) { #region " (A) 上書き。余った部分にもデータ(無音またはループ)を出力する。" //----------------- if( 1.0f == this.bs_音量 ) { // 原音(最大音量)。 CopyMemory( _出力先, 出力元, ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ); } else { // 音量を反映。 for( int i = 0; i < 出力できるサンプル数; i++ ) { // 1サンプル = 2ch×INT32 *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 ); *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 ); } } if( 0 < 出力できないサンプル数 ) // サウンドデータの末尾に達した { // 残りの部分は、とりあえず今は無音。(ループ再生未対応) ZeroMemory( (void*) ( ( (byte*) _出力先 ) + ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ), 出力できないサンプル数 * オーバーサンプルサイズbyte ); } //----------------- #endregion } else { #region " (B) 加算合成。余った部分は放置してもいいし、ループしてデータ加算を続けてもいい。" //----------------- for( int i = 0; i < 出力できるサンプル数; i++ ) { // 1サンプル = 2ch×INT32 *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 ); *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 ); } if( 0 < 出力できないサンプル数 ) { // 残りの部分は、今回の実装では無視。(ループ再生未対応。) } //----------------- #endregion } #region " 再生位置を移動。" //--------------------------------------------------- this.再生位置sample += 出力できるサンプル数; if( this.サウンドデータサイズsample <= this.再生位置sample ) // サウンドデータの末尾に達した { this.再生位置sample = this.サウンドデータサイズsample; this.再生状態 = E再生状態.再生終了; // 再生終了に伴う自動終了なので、"停止中" ではない。 } //--------------------------------------------------- #endregion } return CSCore.CoreAudioAPI.AudioClientBufferFlags.None; } #region " Dispose-Finalizeパターン " //---------------- ~Sound() { this.Dispose( false ); } public void Dispose() { this.Dispose( true ); GC.SuppressFinalize( this ); } protected void Dispose( bool Managedも解放する ) { Action サウンドデータを解放する = () => { if( null != this.サウンドデータ ) { FDK.Memory.Free( (void*) this.サウンドデータ ); this.サウンドデータ = null; } }; if( Managedも解放する ) { // C#オブジェクトの解放があればここで。 // this.排他利用を使った Unmanaged の解放。 lock( this.排他利用 ) { サウンドデータを解放する(); } } else { // (使える保証がないので)this.排他利用 を使わないUnmanaged の解放。 サウンドデータを解放する(); } } //---------------- #endregion private bool 作成済み = false; private byte* サウンドデータ = null; private int サウンドデータサイズbyte = 0; private int サウンドデータサイズsample = 0; private int 再生位置sample = 0; private readonly SharpDX.Multimedia.WaveFormat WAVEフォーマット = new SharpDX.Multimedia.WaveFormat( 44100, 16, 2 ); // 固定 private readonly object 排他利用 = new object(); #region " バックストア " //---------------- private float bs_音量 = 1.0f; //---------------- #endregion #region " Win32 API " //----------------- [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )] private static extern unsafe void CopyMemory( void* dst, void* src, int size ); [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )] private static extern unsafe void ZeroMemory( void* dst, int length ); //----------------- #endregion } }