OSDN Git Service

BGMを、途中からの再生に対応した。動画はまだ。
[strokestylet/CsWin10Desktop3.git] / FDK24 / メディア / サウンド / WASAPI排他 / Sound.cs
1 using System;
2
3 namespace FDK.メディア.サウンド.WASAPI排他
4 {
5         public unsafe class Sound : IDisposable
6         {
7                 public enum E再生状態
8                 {
9                         停止中,    // 初期状態
10                         再生中,
11                         一時停止中,
12                         再生終了,
13                 };
14                 public E再生状態 再生状態 = E再生状態.停止中;
15
16                 /// <summary>
17                 /// 0.0(最小)~1.0(原音) の範囲で指定する。再生中でも反映される。
18                 /// </summary>
19                 public float 音量
20                 {
21                         set
22                         {
23                                 float 設定値 = Math.Min( Math.Max( value, 0.0f ), 1.0f );  // 0.0未満は0.0へ、1.0超は1.0へ。
24                                 lock( this.排他利用 )
25                                 {
26                                         this.bs_音量 = 設定値;
27                                 }
28                         }
29                         get
30                         {
31                                 lock( this.排他利用 )
32                                 {
33                                         return this.bs_音量;
34                                 }
35                         }
36                 }
37                 public double 長さsec
38                 {
39                         get
40                         {
41                                 lock( this.排他利用 )
42                                 {
43                                         return ( this.サウンドデータサイズsample / this.WAVEフォーマット.SampleRate );
44                                 }
45                         }
46                 }
47
48                 public Sound()
49                 {
50                 }
51                 public Sound( string サウンドファイルuri ) : this()
52                 {
53                         this.ファイルから作成する( サウンドファイルuri );
54                 }
55                 public void ファイルから作成する( string サウンドファイルuri )
56                 {
57                         lock( this.排他利用 )
58                         {
59                                 #region " 作成済みなら先にDisposeする。"
60                                 //-----------------
61                                 if( this.作成済み )
62                                         this.Dispose();
63
64                                 this.作成済み = false;
65                                 //-----------------
66                                 #endregion
67
68                                 byte[] encodedPcm = null;
69
70                                 using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( サウンドファイルuri ) )
71                                 using( var pcmStream = new System.IO.MemoryStream() )
72                                 {
73                                         #region " サウンドファイル名から SourceReader を作成する。"
74                                         //-----------------
75
76                                         // 先述の using で作成済み。
77
78                                         // 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
79                                         sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
80                                         sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
81
82                                         // メディアタイプを作成し、オーディオフォーマットを設定する。(固定フォーマットとする。)
83                                         using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
84                                         {
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
93
94                                                 // 作成したメディアタイプを sourceReader にセットする。sourceReader は、必要なデコーダをロードするだろう。
95                                                 sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, mediaType );
96                                         }
97
98                                         // 最初のオーディオストリームが選択されていることを保証する。
99                                         sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
100                                         //-----------------
101                                         #endregion
102                                         #region " sourceReader からサンプルを取得してデコードし、メモリストリーム pcmStream へ書き込んだのち、encodedPcm へ変換する。"
103                                         //-----------------
104                                         using( var pcmWriter = new System.IO.BinaryWriter( pcmStream ) )
105                                         {
106                                                 while( true )
107                                                 {
108                                                         // 次のサンプルを読み込む。
109                                                         int dwActualStreamIndexRef = 0;
110                                                         var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None;
111                                                         Int64 llTimestampRef = 0;
112
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 ) )
119                                                         {
120                                                                 if( null == sample )
121                                                                         break;      // EndOfStream やエラーも含まれる。
122
123                                                                 // サンプルをロックし、オーディオデータへのポインタを取得する。
124                                                                 int cbMaxLengthRef = 0;
125                                                                 int cbCurrentLengthRef = 0;
126                                                                 using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
127                                                                 {
128                                                                         // オーディオデータをメモリストリームに書き込む。
129                                                                         var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef );
130
131                                                                         byte[] dstData = new byte[ cbCurrentLengthRef ];
132                                                                         byte* psrcData = (byte*) audioData.ToPointer(); // fixed
133                                                                         fixed ( byte* pdstData = dstData )
134                                                                         {
135                                                                                 CopyMemory( pdstData, psrcData, cbCurrentLengthRef );
136                                                                         }
137                                                                         pcmWriter.Write( dstData, 0, cbCurrentLengthRef );
138
139                                                                         // サンプルのロックを解除する。
140                                                                         mediaBuffer.Unlock();
141                                                                 }
142                                                         }
143                                                 }
144
145                                                 // ストリームの内容を byte 配列に出力。(Position に関係なく全部出力される。)
146                                                 encodedPcm = pcmStream.ToArray();
147                                         }
148                                         //-----------------
149                                         #endregion
150                                 }
151                                 #region " オーバーサンプリングサウンドデータバッファを確保し、encodedPcm からサンプルを転送する。"
152                                 //-----------------
153                                 using( var pcmReader = new System.IO.BinaryReader( new System.IO.MemoryStream( encodedPcm ) ) )
154                                 {
155                                         // PCMサイズを計算する。(16bit → 32bit でオーバーサンプリングする。)
156                                         this.サウンドデータサイズbyte = encodedPcm.Length * 2;       // 32bit は 16bit の2倍。
157                                         this.サウンドデータサイズsample = this.サウンドデータサイズbyte / 8;    // 1sample = 32bit×2h = 64bit = 8bytes
158
159                                         // オーバーサンプリングサウンドデータ用メモリを確保する。
160                                         this.サウンドデータ = (byte*) FDK.Memory.Alloc( this.サウンドデータサイズbyte );
161
162                                         // ストリームからオーバーサンプリングサウンドデータへ転送する。
163                                         var p = (Int32*) this.サウンドデータ;
164                                         for( int i = 0; i < this.サウンドデータサイズsample; i++ )
165                                         {
166                                                 // 1サンプル = 2ch×INT16 を 2ch×INT32 に変換しながら格納。
167                                                 *p++ = (Int32) pcmReader.ReadInt16();   // 左ch
168                                                 *p++ = (Int32) pcmReader.ReadInt16();   // 右ch
169                                         }
170                                 }
171                                 //-----------------
172                                 #endregion
173
174                                 this.再生位置sample = 0;
175                                 this.作成済み = true;
176                         }
177                 }
178                 public void 再生を開始する( double 再生開始位置sec = 0.0 )
179                 {
180                         lock( this.排他利用 )
181                         {
182                                 if( false == this.作成済み )
183                                         return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
184
185                                 int 開始位置sample = (int) ( 再生開始位置sec * this.WAVEフォーマット.SampleRate );
186                                 if( 開始位置sample < this.サウンドデータサイズsample )
187                                 {
188                                         this.再生状態 = E再生状態.再生中;
189                                         this.再生位置sample = 開始位置sample;
190                                 }
191                         }
192                 }
193                 public void 再生を一時停止する()
194                 {
195                         lock( this.排他利用 )
196                         {
197                                 if( false == this.作成済み )
198                                         return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
199
200                                 this.再生状態 = E再生状態.一時停止中;
201                         }
202                 }
203                 public void 再生を再開する()
204                 {
205                         lock( this.排他利用 )
206                         {
207                                 if( false == this.作成済み )
208                                         return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
209
210                                 if( E再生状態.一時停止中 != this.再生状態 )
211                                         this.再生位置sample = 0;
212
213                                 this.再生状態 = E再生状態.再生中;
214                         }
215                 }
216                 public void 再生を停止する()
217                 {
218                         lock( this.排他利用 )
219                         {
220                                 if( false == this.作成済み )
221                                         return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
222
223                                 this.再生状態 = E再生状態.停止中;
224                                 this.再生位置sample = 0;
225                         }
226                 }
227                 public CSCore.CoreAudioAPI.AudioClientBufferFlags 次のサウンドデータを出力する( void* 出力先, int 出力サンプル数, bool 最初の出力である )
228                 {
229                         lock( this.排他利用 )
230                         {
231                                 #region " 未作成、または再生中でないなら無音フラグをもって帰還。"
232                                 //-----------------
233                                 if( ( false == this.作成済み ) || ( E再生状態.再生中 != this.再生状態 ) )
234                                         return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
235                                 //-----------------
236                                 #endregion
237
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 出力できないサンプル数 = 出力サンプル数 - 出力できるサンプル数;
243
244                                 if( 出力できるサンプル数 <= 0 )
245                                         this.再生状態 = E再生状態.再生終了; // 念のため
246
247                                 if( 最初の出力である )
248                                 {
249                                         #region " (A) 上書き。余った部分にもデータ(無音またはループ)を出力する。"
250                                         //-----------------
251                                         if( 1.0f == this.bs_音量 )
252                                         {
253                                                 // 原音(最大音量)。
254                                                 CopyMemory( _出力先, 出力元, ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) );
255                                         }
256                                         else
257                                         {
258                                                 // 音量を反映。
259                                                 for( int i = 0; i < 出力できるサンプル数; i++ )
260                                                 {
261                                                         // 1サンプル = 2ch×INT32
262                                                         *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
263                                                         *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
264                                                 }
265                                         }
266
267                                         if( 0 < 出力できないサンプル数 ) // サウンドデータの末尾に達した
268                                         {
269                                                 // 残りの部分は、とりあえず今は無音。(ループ再生未対応)
270                                                 ZeroMemory(
271                                                         (void*) ( ( (byte*) _出力先 ) + ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ),
272                                                         出力できないサンプル数 * オーバーサンプルサイズbyte );
273                                         }
274                                         //-----------------
275                                         #endregion
276                                 }
277                                 else
278                                 {
279                                         #region " (B) 加算合成。余った部分は放置してもいいし、ループしてデータ加算を続けてもいい。"
280                                         //-----------------
281                                         for( int i = 0; i < 出力できるサンプル数; i++ )
282                                         {
283                                                 // 1サンプル = 2ch×INT32
284                                                 *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
285                                                 *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
286                                         }
287
288                                         if( 0 < 出力できないサンプル数 )
289                                         {
290                                                 // 残りの部分は、今回の実装では無視。(ループ再生未対応。)
291                                         }
292                                         //-----------------
293                                         #endregion
294                                 }
295
296                                 #region " 再生位置を移動。"
297                                 //---------------------------------------------------
298                                 this.再生位置sample += 出力できるサンプル数;
299
300                                 if( this.サウンドデータサイズsample <= this.再生位置sample )  // サウンドデータの末尾に達した
301                                 {
302                                         this.再生位置sample = this.サウンドデータサイズsample;
303                                         this.再生状態 = E再生状態.再生終了;   // 再生終了に伴う自動終了なので、"停止中" ではない。
304                                 }
305                                 //---------------------------------------------------
306                                 #endregion
307                         }
308
309                         return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
310                 }
311
312                 #region " Dispose-Finalizeパターン "
313                 //----------------
314                 ~Sound()
315                 {
316                         this.Dispose( false );
317                 }
318                 public void Dispose()
319                 {
320                         this.Dispose( true );
321                         GC.SuppressFinalize( this );
322                 }
323                 protected void Dispose( bool Managedも解放する )
324                 {
325                         Action サウンドデータを解放する = () => {
326                                 if( null != this.サウンドデータ )
327                                 {
328                                         FDK.Memory.Free( (void*) this.サウンドデータ );
329                                         this.サウンドデータ = null;
330                                 }
331                         };
332
333                         if( Managedも解放する )
334                         {
335                                 // C#オブジェクトの解放があればここで。
336
337                                 // this.排他利用を使った Unmanaged の解放。
338                                 lock( this.排他利用 )
339                                 {
340                                         サウンドデータを解放する();
341                                 }
342                         }
343                         else
344                         {
345                                 // (使える保証がないので)this.排他利用 を使わないUnmanaged の解放。
346                                 サウンドデータを解放する();
347                         }
348                 }
349                 //----------------
350                 #endregion
351
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();
359
360                 #region " バックストア "
361                 //----------------
362                 private float bs_音量 = 1.0f;
363                 //----------------
364                 #endregion
365
366                 #region " Win32 API "
367                 //-----------------
368                 [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
369                 private static extern unsafe void CopyMemory( void* dst, void* src, int size );
370
371                 [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
372                 private static extern unsafe void ZeroMemory( void* dst, int length );
373                 //-----------------
374                 #endregion
375         }
376 }