OSDN Git Service

WASAPI 名前空間を WASAPIold に改名し、新しい WASAPI 名前空間に新しい実装を追加。
[strokestylet/CsWin10Desktop3.git] / FDK24 / メディア / サウンド / WASAPI / Device.cs
index 1066023..0495c97 100644 (file)
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using CSCore;
 
 namespace FDK.メディア.サウンド.WASAPI
 {
-       public unsafe class Device : IDisposable
+       public class Device : IDisposable
        {
-               public float 遅延ms
+               public CSCore.SoundOut.PlaybackState レンダリング状態
                {
-                       get { return this.更新間隔ms; }
+                       get { return this._レンダリング状態; }
                }
-               public CSCore.CoreAudioAPI.AudioClock AudioClock
+
+               public double 遅延sec
                {
-                       get { return this.bs_AudioClock; }
+                       get { return FDK.Utilities.変換_100ns単位からsec単位へ( this._遅延100ns ); }
+                       protected set { this._遅延100ns = FDK.Utilities.変換_sec単位から100ns単位へ( value ); }
                }
-               public bool Dispose済み
+
+               public long 遅延100ns
                {
-                       get;
-                       protected set;
-               } = true;
+                       get { return this._遅延100ns; }
+                       protected set { this._遅延100ns = value; }
+               }
 
-               public void 初期化する( float 希望更新間隔ms )
+               public CSCore.WaveFormat フォーマット
                {
-                       int hr = 0;
+                       get { return this._WaveFormat; }
+               }
 
-                       lock( this.スレッド間同期 )
+               /// <summary>
+               ///             レンダリングボリューム。
+               ///             0.0 (0%) ~ 1.0 (100%) 。
+               /// </summary>
+               public float 音量
+               {
+                       get
+                       {
+                               return ( null != this._レンダリング先 ) ? this._レンダリング先.Volume : 1.0f;
+                       }
+                       set
                        {
-                               Trace.Assert( this.Dispose済み );
-                               this.Dispose済み = false;
+                               if( ( 0.0f > value ) || ( 1.0f < value ) )
+                                       throw new ArgumentOutOfRangeException();
 
-                               this.希望更新間隔ms = 希望更新間隔ms;
+                               this._レンダリング先.Volume = value;
+                       }
+               }
 
-                               #region " AudioClientをアクティベートする。"
-                               //-----------------
-                               using( var devices = new CSCore.CoreAudioAPI.MMDeviceEnumerator() )
-                               using( var 既定のデバイス = devices.GetDefaultAudioEndpoint( CSCore.CoreAudioAPI.DataFlow.Render, CSCore.CoreAudioAPI.Role.Console ) )
-                               {
-                                       this.AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( 既定のデバイス );
-                               }
-                               //-----------------
-                               #endregion
-                               #region " 指定された希望更新間隔とデバイス能力をもとに、更新間隔を決定する。"
-                               //-----------------
-                               long 共有モードでの間隔in100ns = 0;
-                               long 排他モードでの最小間隔in100ns = 0;
-
-                               // 最小間隔を取得する。
-                               hr = this.AudioClient.GetDevicePeriodNative( out 共有モードでの間隔in100ns, out 排他モードでの最小間隔in100ns );
-                               if( 0 > hr )
-                                       System.Runtime.InteropServices.Marshal.ThrowExceptionForHR( hr );
-
-                               // 取得できたらms単位に変換。
-                               this.最小間隔ms = (float) 排他モードでの最小間隔in100ns / 10000.0f;
-
-                               // 更新間隔ms を「希望更新間隔とデバイスの最小間隔の大きい方以上 かつ 1秒以下で丸められた値」にする。
-                               this.更新間隔ms = System.Math.Min( 1000.0f, System.Math.Max( this.希望更新間隔ms, this.最小間隔ms ) );
-                               //-----------------
-                               #endregion
-                               #region " AudioClient を初期化する。"
-                               //-----------------
-                               var waveFormat = new CSCore.WaveFormat( 44100, 16, 2, CSCore.AudioEncoding.Pcm );
+               public CSCore.CoreAudioAPI.AudioClock AudioClock
+               {
+                       get { return this._AudioClock; }
+               }
 
-                               try
+               public Device( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double 遅延sec = 0.010, CSCore.WaveFormat 希望フォーマット = null )
+               {
+                       this._共有モード = 共有モード;
+                       this.遅延sec = 遅延sec;
+                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+
+                       this._初期化する( 希望フォーマット );
+               }
+
+               /// <summary>
+               ///             メディアファイル(動画、音声)からサウンドインスタンスを生成して返す。
+               /// </summary>
+               public Sound CreateSound( string path )
+               {
+                       return new Sound( path, this._Mixer );
+               }
+
+               /// <summary>
+               ///             ミキサーの出力を開始する。
+               ///             以降、ミキサーに Sound を追加すれば、自動的に再生される。
+               /// </summary>
+               public void PlayRendering()
+               {
+                       lock( this._スレッド間同期 )
+                       {
+                               if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Paused )
                                {
-                                       this.AudioClient.Initialize(
-                                               CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive, // 排他モード。
-                                               CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,    // イベント駆動モード。
-                                               (long) ( this.更新間隔ms * 10000.0f + 0.5f ),   // バッファサイズ。イベント駆動モードでは、更新間隔と同じ値でなければならない。
-                                               (long) ( this.更新間隔ms * 10000.0f + 0.5f ),   // 更新間隔。
-                                               waveFormat, // バッファのフォーマット。
-                                               Guid.Empty );   // この AudioClient = AudioStrem が所属する AudioSession。null ならデフォルトのAudioSessionに登録される。
+                                       // Pause 中なら Resume する。
+                                       this.ResumeRendering();
                                }
-                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               else if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Stopped )
                                {
-                                       // 排他&イベント駆動モードの場合、バッファのアライメントエラーが返される場合がある。この場合、サイズを調整してオーディオストリームを作成し直す。
-                                       if( AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED == e.ErrorCode )
+                                       using( var 起動完了通知 = new System.Threading.AutoResetEvent( false ) )
                                        {
-                                               int 更新間隔に一番近くてアライメントされているサイズsample = this.AudioClient.GetBufferSize();
-                                               this.更新間隔ms = ( 更新間隔に一番近くてアライメントされているサイズsample * 1000.0f / (float) waveFormat.SampleRate );
-
-                                               // AudioClient を一度解放し、もう一度アクティベートし直す。
-                                               this.AudioClient.Dispose();
-                                               using( var devices = new CSCore.CoreAudioAPI.MMDeviceEnumerator() )
-                                               using( var 既定のデバイス = devices.GetDefaultAudioEndpoint( CSCore.CoreAudioAPI.DataFlow.Render, CSCore.CoreAudioAPI.Role.Console ) )
-                                               {
-                                                       this.AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( 既定のデバイス );
-                                               }
-
-                                               // アライメントされたサイズを使って、AudioClient を再初期化する。
-                                               this.AudioClient.Initialize(
-                                                       CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive, // 排他モード。
-                                                       CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,    // イベント駆動モード。
-                                                       (long) ( this.更新間隔ms * 10000.0f + 0.5f ),   // バッファサイズ。イベント駆動モードでは、更新間隔と同じ値でなければならない。
-                                                       (long) ( this.更新間隔ms * 10000.0f + 0.5f ),   // 更新間隔。
-                                                       waveFormat, // バッファのフォーマット。
-                                                       Guid.Empty );   // この AudioClient = AudioStrem が所属する AudioSession。NULLならデフォルトのAudioSessionに登録される。
-
-                                               // それでもエラーなら例外発生。
+                                               // スレッドがすでに終了していることを確認する。
+                                               this._レンダリングスレッド?.Join();
+
+                                               // レンダリングスレッドを起動する。
+                                               this._レンダリングスレッド = new System.Threading.Thread( this._レンダリングスレッドエントリ ) {
+                                                       Name = "WASAPI Playback",
+                                                       Priority = System.Threading.ThreadPriority.AboveNormal, // 標準よりやや上
+                                               };
+                                               this._レンダリングスレッド.Start( 起動完了通知 );
+
+                                               // スレッドからの起動完了通知を待つ。
+                                               起動完了通知.WaitOne();
                                        }
                                }
+                       }
+               }
 
-
-                               // 更新間隔を sample, byte 単位で保存する。
-                               this.更新間隔sample = this.AudioClient.GetBufferSize(); // バッファの長さはサンプル単位で返される。
-                               this.更新間隔byte = this.更新間隔sample * ( waveFormat.Channels * waveFormat.BitsPerSample / 8 );
-                               //-----------------
-                               #endregion
-                               #region " AudioClient から AudioRenderClient を取得する。"
-                               //-----------------
-                               this.AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this.AudioClient );
-                               //-----------------
-                               #endregion
-                               #region " AudioClient から AudioClock を取得する。"
-                               //-----------------
-                               this.bs_AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this.AudioClient );
-                               //-----------------
-                               #endregion
-
-                               #region " ミキサーを生成し初期化する。"
-                               //-----------------
-                               this.Mixer = new Mixer();
-                               this.Mixer.初期化する( this.更新間隔sample );
-                               //-----------------
-                               #endregion
-                               #region " 最初のエンドポイントバッファを無音で埋めておく。"
-                               //-----------------
-                               var bufferPtr = this.AudioRenderClient.GetBuffer( this.更新間隔sample );
-
-                               // 無音を書き込んだことにして、バッファをコミット。(bufferPrtは使わない。)
-                               this.AudioRenderClient.ReleaseBuffer( this.更新間隔sample, CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent );
-                               //-----------------
-                               #endregion
-
-                               #region " 情報表示。"
-                               //-----------------
-                               FDK.Log.Info( $"WASAPIクライアントを初期化しました。" );
-                               FDK.Log.Info( $" モード: 排他&イベント駆動" );
-                               FDK.Log.Info( $" フォーマット: {waveFormat.BitsPerSample} bits, {waveFormat.SampleRate} Hz" );
-                               FDK.Log.Info( $" エンドポイントバッファ: {( (float) this.更新間隔sample / (double) waveFormat.SampleRate ) * 1000.0f} ミリ秒 ({this.更新間隔sample} samples) × 2枚" );
-                               FDK.Log.Info( $" 希望更新間隔: {this.希望更新間隔ms} ミリ秒" );
-                               FDK.Log.Info( $" 更新間隔: {this.更新間隔ms} ミリ秒 ({this.更新間隔sample} samples)" );
-                               FDK.Log.Info( $" 最小間隔: {this.最小間隔ms} ミリ秒" );
-                               //-----------------
-                               #endregion
-
-                               #region " ワークキューとイベントを作成し、作業項目を登録する。"
-                               //-----------------
+               /// <summary>
+               ///             ミキサーの出力を停止する。
+               ///             ミキサーに登録されているすべての Sound の再生が停止する。
+               /// </summary>
+               public void StopRendering()
+               {
+                       lock( this._スレッド間同期 )
+                       {
+                               if( ( this._レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped ) && ( null != this._レンダリングスレッド ) )
+                               {
+                                       // レンダリングスレッドに終了を通知し、その終了を待つ。
+                                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+                                       this._レンダリングスレッド.Join();
+                                       this._レンダリングスレッド = null;
+                                       Debug.WriteLine( "WASAPIのレンダリングを停止しました。" );
+                               }
+                               else
                                {
-                                       // MediaFoundation が管理する、プロセス&MMCSSタスクごとに1つずつ作ることができる特別な共有ワークキューを取得、または生成して取得する。
-                                       int dwTaskId = 0;
-                                       SharpDX.MediaFoundation.MediaFactory.LockSharedWorkQueue(
-                                               ( 11.0 > this.更新間隔ms ) ? "Pro Audio" : "Games", 0, ref dwTaskId, out this.QueueID );
-
-                                       // エンドポイントバッファからの出力要請イベントを作成し、AudioClient に登録する。
-                                       this.出力要請イベント = CreateEvent( IntPtr.Zero, false, false, "WASAPI出力要請イベント" );
-                                       this.AudioClient.SetEventHandle( this.出力要請イベント );
-
-                                       // コールバックを作成し、ワークキューに最初の作業項目を登録する。
-                                       this.出力要請イベントのコールバック = new MFAsyncCallback( this.QueueID, ( ar ) => {
-                                               this.出力要請イベントへ対応する( ar );
-                                       } );
+                                       Debug.WriteLine( "WASAPIのレンダリングを停止しようとしましたが、すでに停止しています。" );
                                }
-                               //-----------------
-                               #endregion
-                               #region " 最初の作業項目を追加する。"
-                               //-----------------
-                               this.作業項目をキューに格納する();
-                               //-----------------
-                               #endregion
-                               #region " WASAPI レンダリングを開始。"
-                               //-----------------
-                               this.AudioClient.Start();
-                               //-----------------
-                               #endregion
                        }
                }
-               public void Dispose()
-               {
-                       Trace.Assert( false == this.Dispose済み );
 
-                       #region " WASAPI作業項目を終了させる。オーディオのレンダリングを止める前に行うこと。"
-                       //-----------------
+               /// <summary>
+               ///             ミキサーの出力を一時停止する。
+               ///             ミキサーに登録されているすべての Sound の再生が一時停止する。
+               ///             ResumeRendering()で出力を再開できる。
+               /// </summary>
+               public void PauseRendering()
+               {
+                       lock( this._スレッド間同期 )
                        {
-                               //SharpDX.MediaFoundation.MediaFactory.CancelWorkItem( this.出力要請イベントキャンセル用キー ); --> コールバックの実行中にキャンセルしてしまうと NullReference例外
-                               this.出力終了通知.状態 = 同期.TriStateEvent.状態種別.ON;
-                               this.出力終了通知.OFFになるまでブロックする();
-                               FDK.Log.Info( "WASAPI出力処理を終了しました。" );
+                               if( this.レンダリング状態 == CSCore.SoundOut.PlaybackState.Playing )
+                               {
+                                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Paused;
+                                       Debug.WriteLine( "WASAPIのレンダリングを一時停止しました。" );
+                               }
+                               else
+                               {
+                                       Debug.WriteLine( "WASAPIのレンダリングを一時停止しようとしましたが、すでに一時停止しています。" );
+                               }
                        }
-                       //-----------------
-                       #endregion
+               }
 
-                       lock( this.スレッド間同期 )
+               /// <summary>
+               ///             ミキサーの出力を再開する。
+               ///             PauseRendering() で一時停止状態にあるときのみ有効。
+               /// </summary>
+               public void ResumeRendering()
+               {
+                       lock( this._スレッド間同期 )
                        {
-                               #region " オーディオのレンダリングを停止する。"
-                               //-----------------
-                               this.AudioClient?.Stop();
-                               //-----------------
-                               #endregion
-                               #region " ミキサー(とサウンドリスト)は現状を維持する。"
-                               //-----------------
-
-                               // 何もしない。
-
-                               //-----------------
-                               #endregion
-                               #region " WASAPIオブジェクトを解放する。"
-                               //-----------------
-                               FDK.Utilities.解放する( ref this.bs_AudioClock );
-                               FDK.Utilities.解放する( ref this.AudioRenderClient );
-                               FDK.Utilities.解放する( ref this.AudioClient );
-                               //-----------------
-                               #endregion
-                               #region " 共有ワークキューをこのプロセスから解放する。"
-                               //-----------------
-                               if( int.MaxValue != this.QueueID )
+                               if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Paused )
+                               {
+                                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Playing;
+                                       Debug.WriteLine( "WASAPIのレンダリングを再開しました。" );
+                               }
+                               else
                                {
-                                       SharpDX.MediaFoundation.MediaFactory.UnlockWorkQueue( this.QueueID );
-                                       this.QueueID = int.MaxValue;
+                                       Debug.WriteLine( "WASAPIのレンダリングを再開しようとしましたが、すでに再開されています。" );
                                }
-                               //-----------------
-                               #endregion
-                               #region " WASAPIイベント駆動用のコールバックとイベントを解放する。"
-                               //-----------------
-                               FDK.Utilities.解放する( ref this.出力要請イベントのコールバック );
-
-                               if( IntPtr.Zero != this.出力要請イベント )
-                                       CloseHandle( this.出力要請イベント );
-                               //-----------------
-                               #endregion
-
-                               this.Dispose済み = true;
                        }
+               }
 
-                       FDK.Log.Info( "WASAPIクライアントを終了しました。" );
+               #region " 解放; Dispose-Finallize パターン "
+               //----------------
+               ~Device()
+               {
+                       this.Dispose( false );
                }
-               public void サウンドをミキサーに追加する( Sound sound )
+
+               public void Dispose()
                {
-                       this.Mixer.サウンドを追加する( sound );
+                       this.Dispose( true );
+                       GC.SuppressFinalize( this );
                }
-               public void サウンドをミキサーから削除する( Sound sound )
+
+               protected virtual void Dispose( bool bDisposeManaged )
+               {
+                       if( !this._dispose済み )
+                       {
+                               lock( this._スレッド間同期 )
+                               {
+                                       if( bDisposeManaged )
+                                       {
+                                               // (A) ここでマネージリソースを解放する。
+                                               this.StopRendering();
+                                               this._解放する();
+                                       }
+
+                                       // (B) ここでネイティブリソースを解放する。
+
+                                       // ...特にない。
+
+                               }
+
+                               this._dispose済み = true;
+                       }
+               }
+               //----------------
+               #endregion
+
+
+               private volatile CSCore.SoundOut.PlaybackState _レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+
+               private CSCore.CoreAudioAPI.AudioClientShareMode _共有モード;
+
+               private long _遅延100ns = 0;
+
+               private CSCore.WaveFormat _WaveFormat = null;
+
+               private CSCore.CoreAudioAPI.AudioClock _AudioClock = null;
+
+               private CSCore.CoreAudioAPI.AudioRenderClient _AudioRenderClient = null;
+
+               private CSCore.CoreAudioAPI.AudioClient _AudioClient = null;
+
+               private CSCore.CoreAudioAPI.MMDevice _MMDevice = null;
+
+               private System.Threading.Thread _レンダリングスレッド = null;
+
+               private System.Threading.EventWaitHandle _レンダリングイベント = null;
+
+               private CSCore.Streams.VolumeSource _レンダリング先 = null;
+
+               private CSCore.IWaveSource _生レンダリング先 = null;
+
+               private Mixer _Mixer = null;
+
+               private readonly object _スレッド間同期 = new object();
+
+               private bool _dispose済み = false;
+
+
+               private void _初期化する( CSCore.WaveFormat 希望フォーマット = null )
                {
-                       this.Mixer.サウンドを削除する( sound );
+                       lock( this._スレッド間同期 )
+                       {
+                               if( this._レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped )
+                                       throw new InvalidOperationException( "WASAPI のレンダリングを停止しないまま初期化することはできません。" );
+
+                               this._レンダリングスレッド?.Join();
+
+                               this._解放する();
+
+                               // MMDevice を取得する。
+                               this._MMDevice = CSCore.CoreAudioAPI.MMDeviceEnumerator.DefaultAudioEndpoint(
+                                       CSCore.CoreAudioAPI.DataFlow.Render,    // 方向:再生
+                                       CSCore.CoreAudioAPI.Role.Console );     // 用途:ゲーム、システム通知音、音声命令
+
+                               // AudioClient を取得する。
+                               this._AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( this._MMDevice );
+
+                               // フォーマットを決定する。
+                               var defaultFormat = ( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared ) ?
+                                       this._AudioClient.GetMixFormat() :
+                                       new CSCore.WaveFormat( 48000, 32, 2, AudioEncoding.IeeeFloat );
+
+                               if( null == ( this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ?? defaultFormat ) ) )
+                               {
+                                       throw new NotSupportedException( "サポート可能な WaveFormat が見つかりませんでした。" );
+                               }
+
+                               // 遅延を既定値にする(共有モードの場合のみ)。
+                               if( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
+                                       this._遅延100ns = this._AudioClient.DefaultDevicePeriod;
+
+                               // AudioClient を初期化する。
+                               Action AudioClientを初期化する = () => {
+                                       this._AudioClient.Initialize(
+                                               this._共有モード,
+                                               CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,    // イベント駆動で固定。
+                                               this._遅延100ns,
+                                               this._遅延100ns,
+                                               this._WaveFormat,
+                                               Guid.Empty );
+                               };
+                               try
+                               {
+                                       AudioClientを初期化する();
+                               }
+                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               {
+                                       // 排他モードかつイベント駆動 の場合、この例外が返されることがある。
+                                       // この場合、バッファサイズを調整して再度初期化する。
+                                       if( e.ErrorCode == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED )
+                                       {
+                                               int サイズframe = this._AudioClient.GetBufferSize();
+                                               this._遅延100ns = (long) ( 10.0 * 1000.0 * 1000.0 * サイズframe / this._WaveFormat.SampleRate + 0.5 );   // +0.5 は四捨五入
+
+                                               AudioClientを初期化する();    // それでも例外なら知らん。
+                                       }
+                               }
+
+                               // イベント駆動用に使うイベントを生成し、AudioClient へ登録する。
+                               this._レンダリングイベント = new System.Threading.EventWaitHandle( false, System.Threading.EventResetMode.AutoReset );
+                               this._AudioClient.SetEventHandle( this._レンダリングイベント.SafeWaitHandle.DangerousGetHandle() );
+
+                               // その他の WASAPI インターフェースを取得する。
+                               this._AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this._AudioClient );
+                               this._AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this._AudioClient );
+
+                               // ミキサーを生成し、デバイスのソース(DirectSound でいうところのプライマリバッファ)として登録する。
+                               this._Mixer = new Mixer( this._WaveFormat ) {
+                                       DivideResult = false,
+                               };
+                               this._SetSource( this._Mixer );
+                       }
                }
 
-               // WASAPI オブジェクト
-               protected CSCore.CoreAudioAPI.AudioClient AudioClient = null;
-               protected CSCore.CoreAudioAPI.AudioRenderClient AudioRenderClient = null;
+               private void _解放する()
+               {
+                       FDK.Utilities.解放する( ref this._Mixer );
+                       FDK.Utilities.解放する( ref this._AudioClock );
+                       FDK.Utilities.解放する( ref this._AudioRenderClient );
 
-               // エンドポイントバッファ情報
-               protected float 希望更新間隔ms;
-               protected float 最小間隔ms;
-               protected float 更新間隔ms;
-               protected int 更新間隔sample;
-               protected int 更新間隔byte;
+                       if( ( null != this._AudioClient ) && ( this._AudioClient.BasePtr != IntPtr.Zero ) )
+                       {
+                               try
+                               {
+                                       this._AudioClient.StopNative();
+                                       this._AudioClient.Reset();
+                               }
+                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               {
+                                       if( e.ErrorCode != AUDCLNT_E_NOT_INITIALIZED )
+                                               throw;
+                               }
+                       }
 
-               // ミキサー。サウンドリストもここ。
-               protected Mixer Mixer = null;
+                       FDK.Utilities.解放する( ref this._AudioClient );
+                       FDK.Utilities.解放する( ref this._レンダリングイベント );
+                       FDK.Utilities.解放する( ref this._MMDevice );
+               }
 
-               // WASAPIバッファ出力用
-               private int QueueID = int.MaxValue;
-               private IntPtr 出力要請イベント = IntPtr.Zero;
-               private MFAsyncCallback 出力要請イベントのコールバック = null;
-               private long 出力要請イベントキャンセル用キー = 0;
-               private FDK.同期.TriStateEvent 出力終了通知 = new 同期.TriStateEvent();
+               private void _SetSource( CSCore.ISampleSource targetSource )
+               {
+                       if( null != this._レンダリング先 )
+                               throw new InvalidOperationException( "レンダリングターゲットはすでに設定済みです。" );
 
-               private readonly object スレッド間同期 = new object();
+                       this._レンダリング先 = new CSCore.Streams.VolumeSource( targetSource );  // サンプル(float)単位のレンダリング先と、
+                       this._生レンダリング先 = targetSource.ToWaveSource();                                           // データ(byte)単位のレンダリング先とを持っておく。
+               }
 
-               private void 作業項目をキューに格納する()
+               /// <summary>
+               ///             希望したフォーマットをもとに、適切なフォーマットを調べて返す。
+               /// </summary>
+               /// <param name="waveFormat">希望するフォーマット</param>
+               /// <param name="audioClient">AudioClient インスタンス。Initialize 前でも可。</param>
+               /// <returns>適切なフォーマット。見つからなかったら null。</returns>
+               private CSCore.WaveFormat _適切なフォーマットを調べて返す( CSCore.WaveFormat waveFormat )
                {
-                       var asyncResult = (SharpDX.MediaFoundation.AsyncResult) null;
-                       try
+                       Trace.Assert( null != this._AudioClient );
+
+                       var 最も近いフォーマット = (CSCore.WaveFormat) null;
+                       var 最終的に決定されたフォーマット = (CSCore.WaveFormat) null;
+
+                       if( this._AudioClient.IsFormatSupported( this._共有モード, waveFormat, out 最も近いフォーマット ) )
                        {
-                               // IAsyncCallback を内包した AsyncResult を作成する。
-                               SharpDX.MediaFoundation.MediaFactory.CreateAsyncResult(
-                                       null,
-                                       SharpDX.ComObject.ToCallbackPtr<SharpDX.MediaFoundation.IAsyncCallback>( this.出力要請イベントのコールバック ),
-                                       null,
-                                       out asyncResult );
-
-                               // 作成した AsyncResult を、ワークキュー投入イベントの待機状態にする。
-                               SharpDX.MediaFoundation.MediaFactory.PutWaitingWorkItem(
-                                       hEvent: this.出力要請イベント,
-                                       priority: 0,
-                                       resultRef: asyncResult,
-                                       keyRef: out this.出力要請イベントキャンセル用キー );
+                               // (A) そのまま使える。
+                               最終的に決定されたフォーマット = waveFormat;
                        }
-                       finally
+                       else if( null != 最も近いフォーマット )
                        {
-                               // out 引数に使う変数は using 変数にはできないので、代わりに try-finally を使う
-                               asyncResult?.Dispose();
+                               // (B) AudioClient が推奨フォーマットを返してきたなら、それを採択する
+                               最終的に決定されたフォーマット = 最も近いフォーマット;
                        }
+                       else
+                       {
+                               // (C) AudioClient からの提案がなかった場合は、共有モードのフォーマットを採択する。
+
+                               var 共有モードのフォーマット = this._AudioClient.GetMixFormat();
+
+                               if( ( null != 共有モードのフォーマット ) && this._AudioClient.IsFormatSupported( this._共有モード, 共有モードのフォーマット ) )
+                               {
+                                       最終的に決定されたフォーマット = 共有モードのフォーマット;
+                               }
+                               else
+                               {
+                                       // (D) AudioClient が共有モードのフォーマットすらNGと言ってきた場合は、以下から探す。
+
+                                       最終的に決定されたフォーマット = new[]
+                                       {
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate, 32, waveFormat.Channels, CSCore.AudioSubTypes.IeeeFloat ),
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate, 24, waveFormat.Channels, CSCore.AudioSubTypes.Pcm ),
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate, 16, waveFormat.Channels, CSCore.AudioSubTypes.Pcm ),
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate,  8, waveFormat.Channels, CSCore.AudioSubTypes.Pcm ),
+                                               }
+                                       .FirstOrDefault( ( format ) => ( this._AudioClient.IsFormatSupported( this._共有モード, format ) ) );
+
+                                       // (E) それでも見つからなかったら null 。
+                               }
+                       }
+
+                       return 最終的に決定されたフォーマット;
                }
 
                /// <summary>
-               /// このメソッドは、WASAPIイベント発生時にワークキューに投入され作業項目から呼び出される
+               ///             WASAPIイベント駆動スレッドのエントリ
                /// </summary>
-               private void 出力要請イベントへ対応する( SharpDX.MediaFoundation.AsyncResult asyncResult )
+               /// <param name="起動完了通知">無事に起動できたら、これを Set して(スレッドの生成元に)知らせる。</param>
+               private void _レンダリングスレッドエントリ( object 起動完了通知 )
                {
+                       var 例外 = (Exception) null;
+                       var avrtHandle = IntPtr.Zero;
+
                        try
                        {
-                               // 出力終了通知が来ていれば、応答してすぐに終了する。
-                               if( this.出力終了通知.状態 == 同期.TriStateEvent.状態種別.ON )
-                               {
-                                       this.出力終了通知.状態 = 同期.TriStateEvent.状態種別.無効;
-                                       return;
+                               int バッファサイズframe = this._AudioClient.BufferSize;
+                               int フレームサイズbyte = this._WaveFormat.Channels * this._WaveFormat.BytesPerSample;
+                               var バッファ = new byte[ バッファサイズframe * フレームサイズbyte ];
+
+                               // このスレッドの MMCSS 型を登録する。
+                               int taskIndex;
+                               string mmcssType = new[] {
+                                       new { 最大遅延 = 0.010, 型名 = "Pro Audio" },             // 優先度の高いものから。
+                                       new { 最大遅延 = 0.015, 型名 = "Games" },
                                }
+                               .FirstOrDefault( ( i ) => ( i.最大遅延 > this.遅延sec ) )?.型名 ?? "Audio";
+                               avrtHandle = Device.AvSetMmThreadCharacteristics( mmcssType, out taskIndex );
 
-                               lock( this.スレッド間同期 )
-                               {
-                                       // エンドポインタの空きバッファへのポインタを取得する。
-                                       // このポインタが差すのはネイティブで確保されたメモリなので、GCの対象外である。はず。
-                                       var bufferPtr = this.AudioRenderClient.GetBuffer( this.更新間隔sample );    // イベント駆動なのでサイズ固定。
+                               // AudioClient を開始する。
+                               this._AudioClient.Start();
+                               this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Playing;
+
+                               // 起動完了を通知する。
+                               ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set();
+                               起動完了通知 = null;
 
-                                       // ミキサーを使って、エンドポインタへサウンドデータを出力する。
-                                       var flags = this.Mixer.エンドポイントへ出力する( (void*) bufferPtr, this.更新間隔sample );
+                               // 以下、メインループ。
 
-                                       // エンドポインタのバッファを解放する。
-                                       this.AudioRenderClient.ReleaseBuffer( this.更新間隔sample, flags );
+                               var イベントs = new System.Threading.WaitHandle[] { this._レンダリングイベント };
+                               while( this.レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped )
+                               {
+                                       int イベント番号 = System.Threading.WaitHandle.WaitAny(
+                                               waitHandles: イベントs,
+                                               millisecondsTimeout: (int) ( 3000.0 * this.遅延sec ), // 適正値は レイテンシ×3 [ms] (MSDN)
+                                               exitContext: false );
 
-                                       // 後続のイベント待ち作業項目をキューに格納する。
-                                       this.作業項目をキューに格納する();
+                                       if( イベント番号 == System.Threading.WaitHandle.WaitTimeout )
+                                               continue;
+
+                                       if( this.レンダリング状態 == CSCore.SoundOut.PlaybackState.Playing )
+                                       {
+                                               int 未再生数frame = ( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive ) ? 0 : this._AudioClient.GetCurrentPadding();
+                                               int 空きframe = バッファサイズframe - 未再生数frame;
 
-                                       // 以降、WASAPIからイベントが発火されるたび、作業項目を通じて本メソッドが呼び出される。
+                                               if( 空きframe > 5 )   // あまりに空きが小さいならスキップする。
+                                               {
+                                                       if( !this._バッファを埋める( バッファ, 空きframe, フレームサイズbyte ) )
+                                                               this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+                                               }
+                                       }
                                }
+
+                               // 以下、終了処理。
+
+                               // このスレッドの MMCSS 特性を元に戻す。
+                               Device.AvRevertMmThreadCharacteristics( avrtHandle );
+                               avrtHandle = IntPtr.Zero;
+
+                               // ハードウェアの再生が終わるくらいまで、少し待つ。
+                               System.Threading.Thread.Sleep( (int) ( this.遅延sec * 1000 / 2 ) );
+
+                               // AudioClient を停止する。
+                               this._AudioClient.Stop();
+                               this._AudioClient.Reset();
                        }
-                       catch
+                       catch( Exception e )
                        {
-                               // 例外は無視。
+                               例外 = e;
+                       }
+                       finally
+                       {
+                               if( avrtHandle != IntPtr.Zero )
+                                       Device.AvRevertMmThreadCharacteristics( avrtHandle );
+
+                               ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set();      // 失敗時を想定して。
                        }
                }
 
-               #region " バックストア。"
-               //----------------
-               private CSCore.CoreAudioAPI.AudioClock bs_AudioClock = null;
-               //----------------
-               #endregion
+               private bool _バッファを埋める( byte[] バッファ, int フレーム数, int フレームサイズbyte )
+               {
+                       int 読み込むサイズbyte = フレーム数 * フレームサイズbyte;
+                       読み込むサイズbyte -= ( 読み込むサイズbyte % this._生レンダリング先.WaveFormat.BlockAlign );  // BlockAlign の倍数にする。
+
+                       if( 読み込むサイズbyte <= 0 )
+                               return true;
 
-               #region " Win32 API "
-               //-----------------
-               private static int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
+                       int 読み込んだサイズbyte = this._生レンダリング先.Read( バッファ, 0, 読み込むサイズbyte );
 
-               [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
-               private static extern IntPtr CreateEvent( IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName );
+                       IntPtr ptr = this._AudioRenderClient.GetBuffer( フレーム数 );
+                       Marshal.Copy( バッファ, 0, ptr, 読み込んだサイズbyte );
+                       this._AudioRenderClient.ReleaseBuffer( 読み込んだサイズbyte / フレームサイズbyte, CSCore.CoreAudioAPI.AudioClientBufferFlags.None );
 
-               [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
-               private static extern bool CloseHandle( IntPtr hObject );
-               //-----------------
+                       return ( 0 < 読み込んだサイズbyte );
+               }
+
+               #region " Win32 "
+               //----------------
+               private const int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
+               private const int AUDCLNT_E_INVALID_DEVICE_PERIOD = unchecked((int) 0x88890020);
+               private const int AUDCLNT_E_NOT_INITIALIZED = unchecked((int) 0x88890001);
+
+               [DllImport( "Avrt.dll", CharSet = CharSet.Unicode )]
+               private static extern IntPtr AvSetMmThreadCharacteristics( [MarshalAs( UnmanagedType.LPWStr )] string proAudio, out int taskIndex );
+
+               [DllImport( "Avrt.dll" )]
+               private static extern bool AvRevertMmThreadCharacteristics( IntPtr avrtHandle );
+               //----------------
                #endregion
        }
 }