3 namespace FDK.メディア.サウンド.WASAPI
5 public unsafe class Sound : IDisposable
14 public E再生状態 再生状態 = E再生状態.停止中;
17 /// 0.0(最小)~1.0(原音) の範囲で指定する。再生中でも反映される。
23 float 設定値 = Math.Min( Math.Max( value, 0.0f ), 1.0f ); // 0.0未満は0.0へ、1.0超は1.0へ。
43 return ( this.サウンドデータサイズsample / this.WAVEフォーマット.SampleRate );
51 public Sound( string サウンドファイルuri ) : this()
53 this.ファイルから作成する( サウンドファイルuri );
55 public void ファイルから作成する( string サウンドファイルuri )
59 #region " 作成済みなら先にDisposeする。"
68 byte[] encodedPcm = null;
70 using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( サウンドファイルuri ) )
71 using( var pcmStream = new System.IO.MemoryStream() )
73 #region " サウンドファイル名から SourceReader を作成する。"
78 // 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
79 sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
80 sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
82 // メディアタイプを作成し、オーディオフォーマットを設定する。(固定フォーマットとする。)
83 using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
85 mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio );
86 mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Pcm );
87 mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WAVEフォーマット.Channels );
88 mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WAVEフォーマット.SampleRate );
89 mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WAVEフォーマット.BlockAlign );
90 mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WAVEフォーマット.AverageBytesPerSecond );
91 mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, this.WAVEフォーマット.BitsPerSample );
92 mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE
94 // 作成したメディアタイプを sourceReader にセットする。sourceReader は、必要なデコーダをロードするだろう。
95 sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, mediaType );
98 // 最初のオーディオストリームが選択されていることを保証する。
99 sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
102 #region " sourceReader からサンプルを取得してデコードし、メモリストリーム pcmStream へ書き込んだのち、encodedPcm へ変換する。"
104 using( var pcmWriter = new System.IO.BinaryWriter( pcmStream ) )
109 int dwActualStreamIndexRef = 0;
110 var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None;
111 Int64 llTimestampRef = 0;
113 using( var sample = sourceReader.ReadSample(
114 SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream,
115 SharpDX.MediaFoundation.SourceReaderControlFlags.None,
116 out dwActualStreamIndexRef,
117 out dwStreamFlagsRef,
118 out llTimestampRef ) )
121 break; // EndOfStream やエラーも含まれる。
123 // サンプルをロックし、オーディオデータへのポインタを取得する。
124 int cbMaxLengthRef = 0;
125 int cbCurrentLengthRef = 0;
126 using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
128 // オーディオデータをメモリストリームに書き込む。
129 var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef );
131 byte[] dstData = new byte[ cbCurrentLengthRef ];
132 byte* psrcData = (byte*) audioData.ToPointer(); // fixed
133 fixed ( byte* pdstData = dstData )
135 CopyMemory( pdstData, psrcData, cbCurrentLengthRef );
137 pcmWriter.Write( dstData, 0, cbCurrentLengthRef );
140 mediaBuffer.Unlock();
145 // ストリームの内容を byte 配列に出力。(Position に関係なく全部出力される。)
146 encodedPcm = pcmStream.ToArray();
151 #region " オーバーサンプリングサウンドデータバッファを確保し、encodedPcm からサンプルを転送する。"
153 using( var pcmReader = new System.IO.BinaryReader( new System.IO.MemoryStream( encodedPcm ) ) )
155 // PCMサイズを計算する。(16bit → 32bit でオーバーサンプリングする。)
156 this.サウンドデータサイズbyte = encodedPcm.Length * 2; // 32bit は 16bit の2倍。
157 this.サウンドデータサイズsample = this.サウンドデータサイズbyte / 8; // 1sample = 32bit×2h = 64bit = 8bytes
159 // オーバーサンプリングサウンドデータ用メモリを確保する。
160 this.サウンドデータ = (byte*) FDK.Memory.Alloc( this.サウンドデータサイズbyte );
162 // ストリームからオーバーサンプリングサウンドデータへ転送する。
163 var p = (Int32*) this.サウンドデータ;
164 for( int i = 0; i < this.サウンドデータサイズsample; i++ )
166 // 1サンプル = 2ch×INT16 を 2ch×INT32 に変換しながら格納。
167 *p++ = (Int32) pcmReader.ReadInt16(); // 左ch
168 *p++ = (Int32) pcmReader.ReadInt16(); // 右ch
178 public void 再生を開始する( double 再生開始位置sec = 0.0 )
182 if( false == this.作成済み )
183 return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
185 int 開始位置sample = (int) ( 再生開始位置sec * this.WAVEフォーマット.SampleRate );
186 if( 開始位置sample < this.サウンドデータサイズsample )
188 this.再生状態 = E再生状態.再生中;
189 this.再生位置sample = 開始位置sample;
193 public void 再生を一時停止する()
197 if( false == this.作成済み )
198 return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
200 this.再生状態 = E再生状態.一時停止中;
203 public void 再生を再開する()
207 if( false == this.作成済み )
208 return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
210 if( E再生状態.一時停止中 != this.再生状態 )
213 this.再生状態 = E再生状態.再生中;
216 public void 再生を停止する()
220 if( false == this.作成済み )
221 return; // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
223 this.再生状態 = E再生状態.停止中;
227 public CSCore.CoreAudioAPI.AudioClientBufferFlags 次のサウンドデータを出力する( void* 出力先, int 出力サンプル数, bool 最初の出力である )
231 #region " 未作成、または再生中でないなら無音フラグをもって帰還。"
233 if( ( false == this.作成済み ) || ( E再生状態.再生中 != this.再生状態 ) )
234 return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
238 int オーバーサンプルサイズbyte = 4 * 2; // 32bit×2ch
239 Int32* 出力元 = (Int32*) ( this.サウンドデータ + ( this.再生位置sample * オーバーサンプルサイズbyte ) );
240 Int32* _出力先 = (Int32*) 出力先; // この実装ではサンプルは Int32 単位
241 int 出力できるサンプル数 = System.Math.Min( 出力サンプル数, ( this.サウンドデータサイズsample - this.再生位置sample ) );
242 int 出力できないサンプル数 = 出力サンプル数 - 出力できるサンプル数;
244 if( 出力できるサンプル数 <= 0 )
245 this.再生状態 = E再生状態.再生終了; // 念のため
249 #region " (A) 上書き。余った部分にもデータ(無音またはループ)を出力する。"
251 if( 1.0f == this.bs_音量 )
254 CopyMemory( _出力先, 出力元, ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) );
259 for( int i = 0; i < 出力できるサンプル数; i++ )
262 *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
263 *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
267 if( 0 < 出力できないサンプル数 ) // サウンドデータの末尾に達した
269 // 残りの部分は、とりあえず今は無音。(ループ再生未対応)
271 (void*) ( ( (byte*) _出力先 ) + ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ),
272 出力できないサンプル数 * オーバーサンプルサイズbyte );
279 #region " (B) 加算合成。余った部分は放置してもいいし、ループしてデータ加算を続けてもいい。"
281 for( int i = 0; i < 出力できるサンプル数; i++ )
284 *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
285 *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
288 if( 0 < 出力できないサンプル数 )
290 // 残りの部分は、今回の実装では無視。(ループ再生未対応。)
297 //---------------------------------------------------
298 this.再生位置sample += 出力できるサンプル数;
300 if( this.サウンドデータサイズsample <= this.再生位置sample ) // サウンドデータの末尾に達した
302 this.再生位置sample = this.サウンドデータサイズsample;
303 this.再生状態 = E再生状態.再生終了; // 再生終了に伴う自動終了なので、"停止中" ではない。
305 //---------------------------------------------------
309 return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
312 #region " Dispose-Finalizeパターン "
316 this.Dispose( false );
318 public void Dispose()
320 this.Dispose( true );
321 GC.SuppressFinalize( this );
323 protected void Dispose( bool Managedも解放する )
325 Action サウンドデータを解放する = () => {
326 if( null != this.サウンドデータ )
328 FDK.Memory.Free( (void*) this.サウンドデータ );
335 // C#オブジェクトの解放があればここで。
337 // this.排他利用を使った Unmanaged の解放。
345 // (使える保証がないので)this.排他利用 を使わないUnmanaged の解放。
352 private bool 作成済み = false;
353 private byte* サウンドデータ = null;
354 private int サウンドデータサイズbyte = 0;
355 private int サウンドデータサイズsample = 0;
356 private int 再生位置sample = 0;
357 private readonly SharpDX.Multimedia.WaveFormat WAVEフォーマット = new SharpDX.Multimedia.WaveFormat( 44100, 16, 2 ); // 固定
358 private readonly object 排他利用 = new object();
362 private float bs_音量 = 1.0f;
366 #region " Win32 API "
368 [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
369 private static extern unsafe void CopyMemory( void* dst, void* src, int size );
371 [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
372 private static extern unsafe void ZeroMemory( void* dst, int length );