using System; namespace FDK.メディア.サウンド.WASAPI { /// /// WASAPIデバイス(のIAudioClock)をソースとするタイマー。 /// /// /// WASAPIデバイスと厳密に同期をとる場合には、QPCTimer ではなく、このクラスを使用する。 /// public class SoundTimer { /// /// エラー時は double.NaN を返す。 /// public double 現在のデバイス位置secを取得する( CSCore.CoreAudioAPI.AudioClock audioClock ) { lock( this.スレッド間同期 ) { int hr = 0; long デバイス周波数 = 0;// audioClock.Pu64Frequency; audioClock.GetFrequencyNative( out デバイス周波数 ); long QPC周波数 = FDK.カウンタ.QPCTimer.周波数; long デバイス位置 = 0; long デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 = 0; if( 0.0 >= デバイス周波数 ) return double.NaN; // IAudioClock::GetPosition() は、S_FALSE を返すことがある。 // これは、WASAPI排他モードにおいて、GetPosition 時に優先度の高いイベントが発生しており // 既定時間内にデバイス位置を取得できなかった場合に返される。(MSDNより) for( int リトライ回数 = 0; リトライ回数 < 10; リトライ回数++ ) // 最大10回までリトライ。 { hr = audioClock.GetPositionNative( out デバイス位置, out デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 ); if( ( (int) CSCore.Win32.HResult.S_OK ) == hr ) { break; // OK } else if( ( (int) CSCore.Win32.HResult.S_FALSE ) == hr ) { continue; // リトライ } else { throw new CSCore.Win32.Win32ComException( hr, "IAudioClock", "GetPosition" ); } } if( ( (int) CSCore.Win32.HResult.S_FALSE ) == hr ) { // 全部リトライしてもまだダメならエラー。 return double.NaN; } // デバイス位置は、GetPosition() で取得した瞬間からここに至るまでに、既にいくらか進んでいる。 // そこで、この時点で最新のパフォーマンスカウンタを取得し、時間の増加分をデバイス位置に加えて精度を上げる。(MSDNより) double QPCから調べた差分sec = ( (double) FDK.カウンタ.QPCTimer.生カウント / QPC周波数 ) - ( (double) デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 ) / 10000000.0; return ( (double) デバイス位置 ) / デバイス周波数 + QPCから調べた差分sec; } } private readonly object スレッド間同期 = new object(); } }