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();
}
}