OSDN Git Service

aabf4d537c8430feffb11e6c17026a4c8860d9af
[strokestylet/CsWin10Desktop3.git] / FDK24 / メディア / サウンド / WASAPI / Device.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.Linq;
5 using System.Runtime.InteropServices;
6 using CSCore;
7
8 namespace FDK.メディア.サウンド.WASAPI
9 {
10         public class Device : IDisposable
11         {
12                 public CSCore.SoundOut.PlaybackState レンダリング状態
13                 {
14                         get { return this._レンダリング状態; }
15                 }
16
17                 public double 遅延sec
18                 {
19                         get { return FDK.Utilities.変換_100ns単位からsec単位へ( this._遅延100ns ); }
20                         protected set { this._遅延100ns = FDK.Utilities.変換_sec単位から100ns単位へ( value ); }
21                 }
22
23                 public long 遅延100ns
24                 {
25                         get { return this._遅延100ns; }
26                         protected set { this._遅延100ns = value; }
27                 }
28
29                 public CSCore.WaveFormat WaveFormat
30                 {
31                         get { return this._WaveFormat; }
32                 }
33
34                 /// <summary>
35                 ///             レンダリングボリューム。
36                 ///             0.0 (0%) ~ 1.0 (100%) 。
37                 /// </summary>
38                 public float 音量
39                 {
40                         get
41                         {
42                                 return ( null != this._Mixer ) ? this._Mixer.Volume : 1.0f;
43                         }
44                         set
45                         {
46                                 if( ( 0.0f > value ) || ( 1.0f < value ) )
47                                         throw new ArgumentOutOfRangeException();
48
49                                 this._Mixer.Volume = value;
50                         }
51                 }
52
53                 public Device( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double バッファサイズsec = 0.010, CSCore.WaveFormat 希望フォーマット = null )
54                 {
55                         this._共有モード = 共有モード;
56                         this.遅延sec = バッファサイズsec;
57                         this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
58
59                         this._初期化する( 希望フォーマット );
60
61                         this.PlayRendering();
62
63                         FDK.Log.Info( $"WASAPIデバイスを初期化しました。" );
64                         FDK.Log.Info( $" Mode: {this._共有モード}" );
65                         FDK.Log.Info( $" Laytency: {this.遅延sec * 1000.0} ms" );
66                         var wfx = this.WaveFormat as CSCore.WaveFormatExtensible;
67                         if( null == wfx )
68                                 FDK.Log.Info( $" Format: {this.WaveFormat.WaveFormatTag}, {this.WaveFormat.SampleRate}Hz, {this.WaveFormat.Channels}ch, {this.WaveFormat.BitsPerSample}bits" );
69                         else
70                                 FDK.Log.Info( $" Format: {wfx.WaveFormatTag}[{CSCore.AudioSubTypes.EncodingFromSubType( wfx.SubFormat )}], {wfx.SampleRate}Hz, {wfx.Channels}ch, {wfx.BitsPerSample}bits" );
71                 }
72
73                 /// <summary>
74                 ///             メディアファイル(動画、音声)からサウンドインスタンスを生成して返す。
75                 /// </summary>
76                 public Sound CreateSound( string path )
77                 {
78                         return new Sound( path, this._Mixer );
79                 }
80
81                 /// <summary>
82                 ///             IWaveSource からサウンドインスタンスを生成して返す。
83                 /// </summary>
84                 public Sound CreateSound( CSCore.IWaveSource source )
85                 {
86                         return new Sound( source, this._Mixer );
87                 }
88
89                 /// <summary>
90                 ///             ミキサーの出力を開始する。
91                 ///             以降、ミキサーに Sound を追加すれば、自動的に再生される。
92                 /// </summary>
93                 public void PlayRendering()
94                 {
95                         lock( this._スレッド間同期 )
96                         {
97                                 if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Paused )
98                                 {
99                                         // Pause 中なら Resume する。
100                                         this.ResumeRendering();
101                                 }
102                                 else if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Stopped )
103                                 {
104                                         using( var 起動完了通知 = new System.Threading.AutoResetEvent( false ) )
105                                         {
106                                                 // スレッドがすでに終了していることを確認する。
107                                                 this._レンダリングスレッド?.Join();
108
109                                                 // レンダリングスレッドを起動する。
110                                                 this._レンダリングスレッド = new System.Threading.Thread( this._レンダリングスレッドエントリ ) {
111                                                         Name = "WASAPI Playback",
112                                                         Priority = System.Threading.ThreadPriority.AboveNormal, // 標準よりやや上
113                                                 };
114                                                 this._レンダリングスレッド.Start( 起動完了通知 );
115
116                                                 // スレッドからの起動完了通知を待つ。
117                                                 起動完了通知.WaitOne();
118                                         }
119                                 }
120                         }
121                 }
122
123                 /// <summary>
124                 ///             ミキサーの出力を停止する。
125                 ///             ミキサーに登録されているすべての Sound の再生が停止する。
126                 /// </summary>
127                 public void StopRendering()
128                 {
129                         lock( this._スレッド間同期 )
130                         {
131                                 if( ( this._レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped ) && ( null != this._レンダリングスレッド ) )
132                                 {
133                                         // レンダリングスレッドに終了を通知し、その終了を待つ。
134                                         this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
135                                         this._レンダリングスレッド.Join();
136                                         this._レンダリングスレッド = null;
137                                         FDK.Log.Info( "WASAPIのレンダリングを停止しました。" );
138                                 }
139                                 else
140                                 {
141                                         FDK.Log.WARNING( "WASAPIのレンダリングを停止しようとしましたが、すでに停止しています。" );
142                                 }
143                         }
144                 }
145
146                 /// <summary>
147                 ///             ミキサーの出力を一時停止する。
148                 ///             ミキサーに登録されているすべての Sound の再生が一時停止する。
149                 ///             ResumeRendering()で出力を再開できる。
150                 /// </summary>
151                 public void PauseRendering()
152                 {
153                         lock( this._スレッド間同期 )
154                         {
155                                 if( this.レンダリング状態 == CSCore.SoundOut.PlaybackState.Playing )
156                                 {
157                                         this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Paused;
158                                         FDK.Log.Info( "WASAPIのレンダリングを一時停止しました。" );
159                                 }
160                                 else
161                                 {
162                                         FDK.Log.WARNING( "WASAPIのレンダリングを一時停止しようとしましたが、すでに一時停止しています。" );
163                                 }
164                         }
165                 }
166
167                 /// <summary>
168                 ///             ミキサーの出力を再開する。
169                 ///             PauseRendering() で一時停止状態にあるときのみ有効。
170                 /// </summary>
171                 public void ResumeRendering()
172                 {
173                         lock( this._スレッド間同期 )
174                         {
175                                 if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Paused )
176                                 {
177                                         this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Playing;
178                                         FDK.Log.Info( "WASAPIのレンダリングを再開しました。" );
179                                 }
180                                 else
181                                 {
182                                         FDK.Log.WARNING( "WASAPIのレンダリングを再開しようとしましたが、すでに再開されています。" );
183                                 }
184                         }
185                 }
186
187                 /// <summary>
188                 ///             現在のデバイス位置を返す[秒]。
189                 /// </summary>
190                 /// <returns>エラー時は double.NaN を返す。</returns>
191                 public double GetDevicePosition()
192                 {
193                         //lock( this._スレッド間同期 )
194                         {
195                                 long position, qpcPosition, frequency;
196                                 this.GetClock( out position, out qpcPosition, out frequency );
197
198                                 if( 0.0 >= frequency )
199                                         return double.NaN;
200
201                                 return (double) position / frequency;
202                         }
203                 }
204
205                 #region " 解放; Dispose-Finallize パターン "
206                 //----------------
207                 ~Device()
208                 {
209                         this.Dispose( false );
210                 }
211
212                 public void Dispose()
213                 {
214                         this.Dispose( true );
215                         GC.SuppressFinalize( this );
216                 }
217
218                 protected virtual void Dispose( bool bDisposeManaged )
219                 {
220                         if( !this._dispose済み )
221                         {
222                                 lock( this._スレッド間同期 )
223                                 {
224                                         if( bDisposeManaged )
225                                         {
226                                                 // (A) ここでマネージリソースを解放する。
227                                                 this.StopRendering();
228                                                 this._解放する();
229                                         }
230
231                                         // (B) ここでネイティブリソースを解放する。
232
233                                         // ...特にない。
234
235                                 }
236
237                                 this._dispose済み = true;
238                         }
239                 }
240                 //----------------
241                 #endregion
242
243
244                 private volatile CSCore.SoundOut.PlaybackState _レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
245
246                 private CSCore.CoreAudioAPI.AudioClientShareMode _共有モード;
247
248                 private long _遅延100ns = 0;
249
250                 private CSCore.WaveFormat _WaveFormat = null;
251
252                 private CSCore.CoreAudioAPI.AudioClock _AudioClock = null;
253
254                 private CSCore.CoreAudioAPI.AudioRenderClient _AudioRenderClient = null;
255
256                 private CSCore.CoreAudioAPI.AudioClient _AudioClient = null;
257
258                 private CSCore.CoreAudioAPI.MMDevice _MMDevice = null;
259
260                 private System.Threading.Thread _レンダリングスレッド = null;
261
262                 private System.Threading.EventWaitHandle _レンダリングイベント = null;
263
264                 private Mixer _Mixer = null;
265
266                 private readonly object _スレッド間同期 = new object();
267
268                 private bool _dispose済み = false;
269
270
271                 private void _初期化する( CSCore.WaveFormat 希望フォーマット )
272                 {
273                         lock( this._スレッド間同期 )
274                         {
275                                 if( this._レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped )
276                                         throw new InvalidOperationException( "WASAPI のレンダリングを停止しないまま初期化することはできません。" );
277
278                                 this._レンダリングスレッド?.Join();
279
280                                 this._解放する();
281
282                                 // MMDevice を取得する。
283                                 this._MMDevice = CSCore.CoreAudioAPI.MMDeviceEnumerator.DefaultAudioEndpoint(
284                                         CSCore.CoreAudioAPI.DataFlow.Render,    // 方向:再生
285                                         CSCore.CoreAudioAPI.Role.Console );     // 用途:ゲーム、システム通知音、音声命令
286
287                                 // AudioClient を取得する。
288                                 this._AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( this._MMDevice );
289
290                                 // フォーマットを決定する。
291                                 if( null == ( this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ) ) )
292                                         throw new NotSupportedException( "サポート可能な WaveFormat が見つかりませんでした。" );
293
294                                 // 遅延を既定値にする(共有モードの場合のみ)。
295                                 if( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
296                                         this._遅延100ns = this._AudioClient.DefaultDevicePeriod;
297
298                                 // AudioClient を初期化する。
299                                 Action AudioClientを初期化する = () => {
300                                         this._AudioClient.Initialize(
301                                                 this._共有モード,
302                                                 CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,    // イベント駆動で固定。
303                                                 this._遅延100ns,
304                                                 this._遅延100ns,              // イベント駆動の場合、Periodicity は BufferDuration と同じ値でなければならない。
305                                                 this._WaveFormat,
306                                                 Guid.Empty );
307                                 };
308                                 try
309                                 {
310                                         AudioClientを初期化する();
311                                 }
312                                 catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
313                                 {
314                                         // 排他モードかつイベント駆動 の場合、この例外が返されることがある。
315                                         // この場合、バッファサイズを調整して再度初期化する。
316                                         if( e.ErrorCode == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED )
317                                         {
318                                                 int サイズframe = this._AudioClient.GetBufferSize();   // アライメント済みサイズが取得できる。
319                                                 this._遅延100ns = (long) ( 10.0 * 1000.0 * 1000.0 * サイズframe / this._WaveFormat.SampleRate + 0.5 );   // +0.5 は四捨五入
320
321                                                 AudioClientを初期化する();    // それでも例外なら知らん。
322                                         }
323                                         else
324                                         {
325                                                 throw;
326                                         }
327                                 }
328
329                                 // イベント駆動用に使うイベントを生成し、AudioClient へ登録する。
330                                 this._レンダリングイベント = new System.Threading.EventWaitHandle( false, System.Threading.EventResetMode.AutoReset );
331                                 this._AudioClient.SetEventHandle( this._レンダリングイベント.SafeWaitHandle.DangerousGetHandle() );
332
333                                 // その他の WASAPI インターフェースを取得する。
334                                 this._AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this._AudioClient );
335                                 this._AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this._AudioClient );
336
337                                 // ミキサーを生成する。
338                                 this._Mixer = new Mixer( this._WaveFormat );
339                         }
340                 }
341
342                 private void _解放する()
343                 {
344                         FDK.Utilities.解放する( ref this._Mixer );
345                         FDK.Utilities.解放する( ref this._AudioClock );
346                         FDK.Utilities.解放する( ref this._AudioRenderClient );
347
348                         if( ( null != this._AudioClient ) && ( this._AudioClient.BasePtr != IntPtr.Zero ) )
349                         {
350                                 try
351                                 {
352                                         this._AudioClient.StopNative();
353                                         this._AudioClient.Reset();
354                                 }
355                                 catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
356                                 {
357                                         if( e.ErrorCode != AUDCLNT_E_NOT_INITIALIZED )
358                                                 throw;
359                                 }
360                         }
361
362                         FDK.Utilities.解放する( ref this._AudioClient );
363                         FDK.Utilities.解放する( ref this._レンダリングイベント );
364                         FDK.Utilities.解放する( ref this._MMDevice );
365                 }
366
367                 /// <summary>
368                 ///             希望したフォーマットをもとに、適切なフォーマットを調べて返す。
369                 /// </summary>
370                 /// <param name="waveFormat">希望するフォーマット</param>
371                 /// <param name="audioClient">AudioClient インスタンス。Initialize 前でも可。</param>
372                 /// <returns>適切なフォーマット。見つからなかったら null。</returns>
373                 private CSCore.WaveFormat _適切なフォーマットを調べて返す( CSCore.WaveFormat waveFormat )
374                 {
375                         Trace.Assert( null != this._AudioClient );
376
377                         var 最も近いフォーマット = (CSCore.WaveFormat) null;
378                         var 最終的に決定されたフォーマット = (CSCore.WaveFormat) null;
379
380                         if( ( null != waveFormat ) && this._AudioClient.IsFormatSupported( this._共有モード, waveFormat, out 最も近いフォーマット ) )
381                         {
382                                 // (A) そのまま使える。
383                                 最終的に決定されたフォーマット = waveFormat;
384                         }
385                         else if( null != 最も近いフォーマット )
386                         {
387                                 // (B) AudioClient が推奨フォーマットを返してきたなら、それを採択する。
388                                 最終的に決定されたフォーマット = 最も近いフォーマット;
389                         }
390                         else
391                         {
392                                 // (C) AudioClient からの提案がなかった場合は、共有モードのフォーマットを採択する。
393
394                                 var 共有モードのフォーマット = this._AudioClient.GetMixFormat();
395
396                                 if( ( null != 共有モードのフォーマット ) && this._AudioClient.IsFormatSupported( this._共有モード, 共有モードのフォーマット ) )
397                                 {
398                                         最終的に決定されたフォーマット = 共有モードのフォーマット;
399                                 }
400                                 else
401                                 {
402                                         // (D) AudioClient が共有モードのフォーマットもNGである場合は、以下から探す。
403
404                                         CSCore.WaveFormat closest = null;
405
406                                         bool found = this._AudioClient.IsFormatSupported( CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive,
407                                                 new WaveFormat( 48000, 24, 2, AudioEncoding.Pcm ) {
408                                                         
409                                                 },
410                                                 out closest );
411
412                                         最終的に決定されたフォーマット = new[] {
413                                                 new CSCore.WaveFormat( 48000, 32, 2, AudioEncoding.IeeeFloat ),
414                                                 new CSCore.WaveFormat( 44100, 32, 2, AudioEncoding.IeeeFloat ),
415                                                 /*
416                                                  * 24bit PCM には対応しない。
417                                                  * 
418                                                  * > wFormatTag が WAVE_FORMAT_PCM の場合、wBitsPerSample は 8 または 16 でなければならない。
419                                                  * > wFormatTag が WAVE_FORMAT_EXTENSIBLE の場合、この値は、任意の 8 の倍数を指定できる。
420                                                  * https://msdn.microsoft.com/ja-jp/library/cc371566.aspx
421                                                  * 
422                                                  * また、Realtek HD Audio の場合、IAudioClient.IsSupportedFormat() は 24bit PCM でも true を返してくるが、
423                                                  * 単純に 1sample = 3byte で書き込んでも正常に再生できない。
424                                                  * おそらく 32bit で包む必要があると思われるが、その方法は不明。
425                                                  */
426                                                 //new CSCore.WaveFormat( 48000, 24, 2, AudioEncoding.Pcm ),
427                                                 //new CSCore.WaveFormat( 44100, 24, 2, AudioEncoding.Pcm ),
428                                                 new CSCore.WaveFormat( 48000, 16, 2, AudioEncoding.Pcm ),
429                                                 new CSCore.WaveFormat( 44100, 16, 2, AudioEncoding.Pcm ),
430                                                 new CSCore.WaveFormat( 48000,  8, 2, AudioEncoding.Pcm ),
431                                                 new CSCore.WaveFormat( 44100,  8, 2, AudioEncoding.Pcm ),
432                                                 new CSCore.WaveFormat( 48000, 32, 1, AudioEncoding.IeeeFloat ),
433                                                 new CSCore.WaveFormat( 44100, 32, 1, AudioEncoding.IeeeFloat ),
434                                                 //new CSCore.WaveFormat( 48000, 24, 1, AudioEncoding.Pcm ),
435                                                 //new CSCore.WaveFormat( 44100, 24, 1, AudioEncoding.Pcm ),
436                                                 new CSCore.WaveFormat( 48000, 16, 1, AudioEncoding.Pcm ),
437                                                 new CSCore.WaveFormat( 44100, 16, 1, AudioEncoding.Pcm ),
438                                                 new CSCore.WaveFormat( 48000,  8, 1, AudioEncoding.Pcm ),
439                                                 new CSCore.WaveFormat( 44100,  8, 1, AudioEncoding.Pcm ),
440                                         }
441                                         .FirstOrDefault( ( format ) => ( this._AudioClient.IsFormatSupported( this._共有モード, format ) ) );
442
443                                         // (E) それでも見つからなかったら null 。
444                                 }
445                         }
446
447                         return 最終的に決定されたフォーマット;
448                 }
449
450                 /// <summary>
451                 ///             WASAPIイベント駆動スレッドのエントリ。
452                 /// </summary>
453                 /// <param name="起動完了通知">無事に起動できたら、これを Set して(スレッドの生成元に)知らせる。</param>
454                 private void _レンダリングスレッドエントリ( object 起動完了通知 )
455                 {
456                         var 例外 = (Exception) null;
457                         var avrtHandle = IntPtr.Zero;
458
459                         try
460                         {
461                                 #region " 初期化。"
462                                 //----------------
463                                 int バッファサイズframe = this._AudioClient.BufferSize;
464                                 var バッファ = new float[ バッファサイズframe * this.WaveFormat.Channels ];  // 前提1・this._レンダリング先(ミキサー)の出力は 32bit-float で固定。
465
466                                 // このスレッドの MMCSS 型を登録する。
467                                 int taskIndex;
468                                 string mmcssType = new[] {
469                                         new { 最大遅延 = 0.0105, 型名 = "Pro Audio" },            // 優先度の高いものから。
470                                         new { 最大遅延 = 0.0150, 型名 = "Games" },
471                                 }
472                                 .FirstOrDefault( ( i ) => ( i.最大遅延 > this.遅延sec ) )?.型名 ?? "Audio";
473                                 avrtHandle = Device.AvSetMmThreadCharacteristics( mmcssType, out taskIndex );
474
475                                 // AudioClient を開始する。
476                                 this._AudioClient.Start();
477                                 this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Playing;
478
479                                 // 起動完了を通知する。
480                                 ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set();
481                                 起動完了通知 = null;
482                                 //----------------
483                                 #endregion
484
485                                 #region " メインループ。"
486                                 //----------------
487                                 var イベントs = new System.Threading.WaitHandle[] { this._レンダリングイベント };
488                                 while( this.レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped )
489                                 {
490                                         int イベント番号 = System.Threading.WaitHandle.WaitAny(
491                                                 waitHandles: イベントs,
492                                                 millisecondsTimeout: (int) ( 3000.0 * this.遅延sec ), // 適正値は レイテンシ×3 [ms] (MSDN)
493                                                 exitContext: false );
494
495                                         if( イベント番号 == System.Threading.WaitHandle.WaitTimeout )
496                                                 continue;
497
498                                         if( this.レンダリング状態 == CSCore.SoundOut.PlaybackState.Playing )
499                                         {
500                                                 int 未再生数frame = ( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive ) ? 0 : this._AudioClient.GetCurrentPadding();
501                                                 int 空きframe = バッファサイズframe - 未再生数frame;
502
503                                                 if( 空きframe > 5 )   // あまりに空きが小さいならスキップする。
504                                                 {
505                                                         // レンダリング先からデータを取得して AudioRenderClient へ出力する。
506
507                                                         int 読み込むサイズsample = 空きframe * this.WaveFormat.Channels;       // 前提2・レンダリング先.WaveFormat と this.WaveFormat は同一。
508                                                         読み込むサイズsample -= ( 読み込むサイズsample % ( this.WaveFormat.BlockAlign / this.WaveFormat.BytesPerSample ) );  // BlockAlign 境界にそろえる。
509
510                                                         if( 0 < 読み込むサイズsample )
511                                                         {
512                                                                 // ミキサーからの出力をバッファに取得する。
513                                                                 int 読み込んだサイズsample = this._Mixer.Read( バッファ, 0, 読み込むサイズsample );
514
515                                                                 // バッファのデータを変換しつつ、AudioRenderClient へ出力する。
516                                                                 IntPtr bufferPtr = this._AudioRenderClient.GetBuffer( 空きframe );
517                                                                 try
518                                                                 {
519                                                                         var encoding = CSCore.AudioSubTypes.EncodingFromSubType( CSCore.WaveFormatExtensible.SubTypeFromWaveFormat( this.WaveFormat ) );
520
521                                                                         if( encoding == AudioEncoding.Pcm )
522                                                                         {
523                                                                                 if( 24 == this.WaveFormat.BitsPerSample )
524                                                                                 {
525                                                                                         #region " (A) Mixer:32bit-float → AudioRenderClient:24bit-PCM の場合 "
526                                                                                         //----------------
527                                                                                         unsafe
528                                                                                         {
529                                                                                                 byte* ptr = (byte*) bufferPtr.ToPointer();  // AudioRenderClient のバッファは GC 対象外なのでピン止め不要。
530
531                                                                                                 for( int i = 0; i < 読み込んだサイズsample; i++ )
532                                                                                                 {
533                                                                                                         float data = バッファ[ i ];
534                                                                                                         if( -1.0f > data ) data = -1.0f;
535                                                                                                         if( +1.0f < data ) data = +1.0f;
536
537                                                                                                         uint sample32 = (uint) ( data * 8388608f - 1f );    // 24bit PCM の値域は -8388608~+8388607
538                                                                                                         byte* psample32 = (byte*) &sample32;
539                                                                                                         *ptr++ = *psample32++;
540                                                                                                         *ptr++ = *psample32++;
541                                                                                                         *ptr++ = *psample32++;
542                                                                                                 }
543                                                                                         }
544                                                                                         //----------------
545                                                                                         #endregion
546                                                                                 }
547                                                                                 else if( 16 == this.WaveFormat.BitsPerSample )
548                                                                                 {
549                                                                                         #region " (B) Mixer:32bit-float → AudioRenderClient:16bit-PCM の場合 "
550                                                                                         //----------------
551                                                                                         unsafe
552                                                                                         {
553                                                                                                 byte* ptr = (byte*) bufferPtr.ToPointer();  // AudioRenderClient のバッファは GC 対象外なのでピン止め不要。
554
555                                                                                                 for( int i = 0; i < 読み込んだサイズsample; i++ )
556                                                                                                 {
557                                                                                                         float data = バッファ[ i ];
558                                                                                                         if( -1.0f > data ) data = -1.0f;
559                                                                                                         if( +1.0f < data ) data = +1.0f;
560
561                                                                                                         short sample16 = (short) ( data * short.MaxValue );
562                                                                                                         byte* psample16 = (byte*) &sample16;
563                                                                                                         *ptr++ = *psample16++;
564                                                                                                         *ptr++ = *psample16++;
565                                                                                                 }
566                                                                                         }
567                                                                                         //----------------
568                                                                                         #endregion
569                                                                                 }
570                                                                                 else if( 8 == this.WaveFormat.BitsPerSample )
571                                                                                 {
572                                                                                         #region " (C) Mixer:32bit-float → AudioRenderClient:8bit-PCM の場合 "
573                                                                                         //----------------
574                                                                                         unsafe
575                                                                                         {
576                                                                                                 byte* ptr = (byte*) bufferPtr.ToPointer();  // AudioRenderClient のバッファは GC 対象外なのでピン止め不要。
577
578                                                                                                 for( int i = 0; i < 読み込んだサイズsample; i++ )
579                                                                                                 {
580                                                                                                         float data = バッファ[ i ];
581                                                                                                         if( -1.0f > data ) data = -1.0f;
582                                                                                                         if( +1.0f < data ) data = +1.0f;
583
584                                                                                                         byte value = (byte) ( ( data + 1 ) * 128f );
585                                                                                                         *ptr++ = unchecked(value);
586                                                                                                 }
587                                                                                         }
588                                                                                         //----------------
589                                                                                         #endregion
590                                                                                 }
591                                                                         }
592                                                                         else if( encoding == AudioEncoding.IeeeFloat )
593                                                                         {
594                                                                                 #region " (D) Mixer:32bit-float → AudioRenderClient:32bit-float の場合 "
595                                                                                 //----------------
596                                                                                 Marshal.Copy( バッファ, 0, bufferPtr, 読み込んだサイズsample );
597                                                                                 //----------------
598                                                                                 #endregion
599                                                                         }
600                                                                 }
601                                                                 finally
602                                                                 {
603                                                                         int 出力したフレーム数 = 読み込んだサイズsample / this.WaveFormat.Channels;
604                                                                         this._AudioRenderClient.ReleaseBuffer(
605                                                                                 出力したフレーム数,
606                                                                                 ( 0 < 出力したフレーム数 ) ? CSCore.CoreAudioAPI.AudioClientBufferFlags.None : CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent );
607                                                                 }
608
609                                                                 // レンダリング先からの出力がなくなったらおしまい。
610                                                                 if( 0 == 読み込んだサイズsample )
611                                                                         this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
612                                                         }
613                                                 }
614                                         }
615                                 }
616                                 //----------------
617                                 #endregion
618
619                                 #region " 終了。"
620                                 //----------------
621                                 // このスレッドの MMCSS 特性を元に戻す。
622                                 Device.AvRevertMmThreadCharacteristics( avrtHandle );
623                                 avrtHandle = IntPtr.Zero;
624
625                                 // ハードウェアの再生が終わるくらいまで、少し待つ。
626                                 System.Threading.Thread.Sleep( (int) ( this.遅延sec * 1000 / 2 ) );
627
628                                 // AudioClient を停止する。
629                                 this._AudioClient.Stop();
630                                 this._AudioClient.Reset();
631                                 //----------------
632                                 #endregion
633                         }
634                         catch( Exception e )
635                         {
636                                 FDK.Log.ERROR( $"例外が発生しました。レンダリングスレッドを中断します。[{e.Message}]" );
637                                 例外 = e;
638                         }
639                         finally
640                         {
641                                 if( avrtHandle != IntPtr.Zero )
642                                         Device.AvRevertMmThreadCharacteristics( avrtHandle );
643
644                                 ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set();      // 失敗時を想定して。
645                         }
646                 }
647
648                 /// <summary>
649                 ///             現在のデバイス位置を取得する。
650                 /// </summary>
651                 private void GetClock( out long Pu64Position, out long QPCPosition, out long Pu64Frequency )
652                 {
653                         //lock( this._スレッド間同期 )   なくてもいいっぽい。
654                         {
655                                 this._AudioClock.GetFrequencyNative( out Pu64Frequency );
656
657                                 // IAudioClock::GetPosition() は、S_FALSE を返すことがある。
658                                 // これは、WASAPI排他モードにおいて、GetPosition 時に優先度の高いイベントが発生しており
659                                 // 既定時間内にデバイス位置を取得できなかった場合に返される。(MSDNより)
660
661                                 int hr = 0;
662                                 long pos = 0;
663                                 long qpcPos = 0;
664                                 for( int リトライ回数 = 0; リトライ回数 < 10; リトライ回数++ )    // 最大10回までリトライ。
665                                 {
666                                         hr = this._AudioClock.GetPositionNative( out pos, out qpcPos );
667
668                                         if( ( (int) CSCore.Win32.HResult.S_OK ) == hr )
669                                         {
670                                                 break;      // OK
671                                         }
672                                         else if( ( (int) CSCore.Win32.HResult.S_FALSE ) == hr )
673                                         {
674                                                 continue;   // リトライ
675                                         }
676                                         else
677                                         {
678                                                 throw new CSCore.Win32.Win32ComException( hr, "IAudioClock", "GetPosition" );
679                                         }
680                                 }
681
682                                 Pu64Position = pos;
683                                 QPCPosition = qpcPos;
684                         }
685                 }
686
687                 #region " Win32 "
688                 //----------------
689                 private const int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
690                 private const int AUDCLNT_E_INVALID_DEVICE_PERIOD = unchecked((int) 0x88890020);
691                 private const int AUDCLNT_E_NOT_INITIALIZED = unchecked((int) 0x88890001);
692
693                 [DllImport( "Avrt.dll", CharSet = CharSet.Unicode )]
694                 private static extern IntPtr AvSetMmThreadCharacteristics( [MarshalAs( UnmanagedType.LPWStr )] string proAudio, out int taskIndex );
695
696                 [DllImport( "Avrt.dll" )]
697                 private static extern bool AvRevertMmThreadCharacteristics( IntPtr avrtHandle );
698                 //----------------
699                 #endregion
700         }
701 }