Microsoft DirectX 8.0 |
ここでは、アプリケーションがメディア ストリーム内の複数の位置をシークする方法について説明する。次のトピックが含まれる。
Microsoft® DirectShow® は、IMediaSeeking インターフェイスによるシークをサポートしている。このインターフェイスを公開するのはフィルタ グラフ マネージャであるが、シーク機能を実装するのは常にフィルタである。
アプリケーションはフィルタ グラフ マネージャに IMediaSeeking を問い合わせ、これを使ってシーク コマンドを発行する。フィルタ グラフ マネージャも IMediaSeeking インターフェイスを使って、そのシーク コマンドをグラフ内のすべてのレンダリング フィルタに配布する。各レンダラは、これらのコマンドを実行できるフィルタに到達するまで、アップストリーム フィルタの出力ピンを介してコマンドをアップストリームに転送する。通常AVI Splitter などのソース フィルタまたはパーサー フィルタが、シーク操作を実行する。
フィルタがシーク操作を実行すると、保留中のデータはすべてフラッシュされる。ダウンストリーム フィルタでもデータがフラッシュされる。詳細については、「フィルタ開発者が使用するデータ フロー」を参照すること。結果としてグラフから既存のデータがフラッシュされるため、シーク コマンドの遅延時間が最小化されることになる。次の図は、処理の順序を具体的に示す。
パーサー フィルタに複数の出力ピンがある場合は通常、その中の 1 つがシーク コマンドを受け取るよう指定されている。その他のピンがシーク コマンドを受け取っても、拒否するか無視する。このように、パーサーはそのストリームのすべてが同期化する状態を維持する。
データによってはシークできないものがある。たとえば、カメラからのライブ ストリームをシークすることはできない。ストリームがシーク可能である場合、サポートされるシークの種類はさまざまである。シークには次の種類がある。
IMediaSeeking インターフェイスは、対応可能なシーク機能を表す AM_SEEKING_SEEKING_CAPABILITIES フラグを定義する。ストリームの機能を取得するには、IMediaSeeking::GetCapabilities メソッドを呼び出す。このメソッドにより、ビット単位のフラグの組み合わせが返される。アプリケーションは & (ビット単位の AND) 演算子を使って、フラグを検査できる。たとえば、次のコードはグラフが任意の位置をシークできるかどうかをチェックする。
DWORD dwCap = 0; HRESULT hr = pSeek->GetCapabilities(&dwCap); if (AM_SEEKING_CanSeekAbsolute & dwCap) { // グラフは絶対位置をシークできる。 }
このほかに、IMediaSeeking::CheckCapabilities メソッドを使って、シーク機能のサブセットを検査できる。アプリケーションは、次のようにフラグの組み合わせをビット単位で解析する。
// チェックする機能のフラグを設定する。 DWORD dwCaps = AM_SEEKING_CanSeekAbsolute | AM_SEEKING_CanSeekForwards | AM_SEEKING_CanSeekBackwards; HRESULT hr = pMediaSeeking->CheckCapabilities(&dwCaps);
指定した機能がサポートされる場合、戻り値は S_OK である。指定した機能がまったくサポートされない場合、エラー コードが返される。指定した機能の一部がサポートされる場合、戻り値は S_FALSE である。メソッドが戻った時点で、入力パラメータには、最初に渡されたセットではなく、サポートされる機能のビット単位の組み合わせが格納されている。この結果は次のように検査できる。
if (hr == S_FALSE) // 要求された機能の一部がサポートされない。 { if (dwCaps & AM_SEEKING_CanSeekAbsolute) // この機能がサポートされる。 ... }
IMediaSeeking インターフェイスは、メディア タイム (100 ナノ秒単位)、ビデオ フレーム、およびストリーム内のバイト オフセットなど、複数のタイム フォーマットをサポートしている。各タイム フォーマットは、グローバル一意識別子 (GUID) で定義される。DirectShow で定義されるタイム フォーマットの一覧については、「タイム フォーマット GUID」を参照すること。サード パーティもカスタムタイム フォーマットの GUID を定義できる。
フィルタ グラフ マネージャの既定値はメディア タイムである。別のタイム フォーマットを使用するには、IMediaSeeking::SetTimeFormat メソッドを呼び出す。このメソッドが正常終了すると、その後のすべてのシーク コマンドは新しく設定されたフォーマットを使用する。メディア ストリームが指定のタイム フォーマットをサポートするかどうかを判断するには、IMediaSeeking::IsFormatSupported メソッドを呼び出す。次のコードは、タイム フォーマットをフレーム単位に設定し、その結果を後で使用するためにブール値のフラグに格納する例である。
hr = pSeek->SetTimeFormat(&TIME_FORMAT_FRAME); BOOL bTimeInFrames = SUCCEEDED(hr);
フィルタ グラフが保持する位置値は、現在位置と停止位置の 2 つである。
現在位置を取得するには、IMediaSeeking::GetCurrentPosition メソッドを呼び出す。停止位置を取得するには、IMediaSeeking::GetStopPosition メソッドを呼び出す。戻り値は、現在のタイム フォーマットの単位を使用する (前の「タイム フォーマット」を参照すること)。戻り値は常に元のメディア ソースに対する相対値である。
新しい位置をシークする、または新しい停止位置を設定するには、IMediaSeeking::SetPositions メソッドを呼び出す。次のコードはその例である。
#define ONE_SECOND 10000000 REFERENCE_TIME rtNow = 2 * ONE_SECOND, rtStop = 5 * ONE_SECOND; hr = pSeek->SetPositions( &rtNow, AM_SEEKING_AbsolutePositioning, &rtStop, AM_SEEKING_AbsolutePositioning );
この例では、現在のタイム フォーマットがメディア タイムであることを前提としている。これは 100 ナノ秒単位である。1 秒は 10,000,000 単位となる。便宜上、例ではこの値を ONE_SECOND として定義している。
注 DirectShow 基底クラスを使用している場合、同じ値を持つ定数 UNITS が定義されている。詳細については、「基底クラス」を参照すること。
rtNow パラメータは新しい位置を指定する。2 番目のパラメータは、rtNow の解釈方法を定義するフラグである。この例のフラグは、rtNow がソース内の絶対位置として定義されることを示している。呼び出しが戻ると、フィルタ グラフは、ストリームの開始から 2 秒の位置にシークする。rtStop パラメータは終了タイムを指定する。最後のパラメータは、終了タイムも絶対位置であることを示すフラグである。
前の位置値からの相対位置を指定するには、AM_SEEKING_RelativePositioning フラグを使用する。位置値のいずれか 1 つを変更しないまま残すには、AM_SEEKING_NoPositioning フラグを使用する。その場合、対応する時間パラメータは NULL でもよい。次の例では、停止位置を変更せずに、10 秒先までシークする。
REFERENCE_TIME rtNow = 10 * ONE_SECOND; hr = pSeek->SetPositions( &rtNow, AM_SEEKING_RelativePositioning, NULL, AM_SEEKING_NoPositioning );
フィルタ グラフを停止すると、ビデオ レンダラはシーク操作後にイメージを更新しない。ユーザーにとっては、シークが発生しなかったかのように見える。イメージを更新するには、シーク操作後にグラフをポーズする。グラフをポーズすることで、ビデオ レンダラに新しいビデオ フレームが挿入される。IMediaControl::StopWhenReady メソッドを使うと、グラフはポーズしてから停止状態に戻る。
再生レートを変更するには、IMediaSeeking::SetRate メソッドを呼び出す。新しいレートを元のレートの比として指定する。たとえば、通常速度の 2 倍で再生するには、次のようにする。
pSeek->SetRate(2.0)
レートは 0 より大きくなければならない。レートが 1 より大きくなると、通常より高速になる。レートが 0 と 1 の間であれば、通常より低速になる。
再生レートにかかわらず、現在位置と停止位置は常にオリジナル ソースからの相対で表される。たとえば、ソース ファイルが 20 秒 (通常の再生レートで) である場合、現在位置を 10 秒に設定してファイルの中央にシークする。停止位置が 20 秒 (ファイルの最後) で、再生レートが 2.0 である場合、ファイルは実時間の 5 秒で再生が終了する。つまり、ソース ファイルの残り 10 秒間は通常の 2 倍の速度で再生される。
ここでは、メディア プレーヤー アプリケーションにシーク バーを実装する方法を説明する。シーク バーはトラック バー コントロールとして実装される。
アプリケーションの起動時に、トラック バーを初期化する。
HWND hTrack = GetDlgItem(hwnd, IDC_SLIDER1); EnableWindow(hTrack, FALSE); SendMessage(hTrack, TBM_SETRANGE, TRUE, MAKELONG(0, 100));
トラック バーは、ユーザーがメディア ファイルを開くまで無効である。トラック バーの範囲は 0 から 100 となる。アプリケーションはメディア タイムをファイル時間幅のパーセンテージに変換する。たとえば、トラック バーの位置 "50" は常にファイルの中央に相当する(このほかに、トラック バーの最大範囲をファイルの時間幅に設定する方法もある。ただし、メディア タイムは 64 ビットの整数であるのに対し、トラック バーの範囲は 32 ビットに制限されている)。
ユーザーがファイルを開くと、アプリケーションはファイル再生グラフを構築する。アプリケーションは、フィルタ グラフ マネージャに対して IMediaSeeking インターフェイスを問い合わせる (ここには示されない)。次に、アプリケーションは、グラフがシーク可能かどうか、およびファイル時間幅を取得可能かどうかをチェックする。グラフがこれら両方の機能をサポートする場合、アプリケーションはファイル時間幅を取得し、トラック バーを有効にする。
REFERENCE_TIME g_rtTotalTime; DWORD caps = AM_SEEKING_CanSeekAbsolute | AM_SEEKING_CanGetDuration; if (pSeek && S_OK == pSeek->CheckCapabilities(&caps)) { pSeek->GetDuration(&g_rtTotalTime); EnableWindow(hTrack, TRUE); }
ユーザーが "再生" コマンドを発行すると、アプリケーションはグラフを実行する。また、トラック バーの位置を定期的に更新するタイマ イベントも開始される。各タイマ イベントがアクティブになると、タイマ コールバックが現在の再生位置を取得し、それに応じてトラック バーを設定する。
#define TICKLEN 100 // ティックの長さ (トラック バーの更新間隔) MMRESULT wTimerID; void CALLBACK MediaTimer(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) { REFERENCE_TIME rtNow; pSeek->GetCurrentPosition(&rtNow); long sliderTick = (long)((rtNow * 100) / g_rtTotalTime); SendMessage( hScroll, TBM_SETPOS, TRUE, sliderTick ); } void OnUserPlay() { pGraph->Run(); if(wTimerID) // タイマ イベントは保留中か? { timeKillEvent(wTimerID); // タイマをキャンセルする。 wTimerID = 0; } wTimerID = timeSetEvent(TICKLEN, 50, MediaTimer, NULL, TIME_PERIODIC); }
ユーザーがグラフを停止またはポーズすると、アプリケーションはタイマ イベントをキャンセルする。
ユーザーがトラック バー コントロールをドラッグまたはクリックすると、アプリケーションは WM_HSCROLL イベントを受け取る。wParam パラメータの下位ワードは、トラック バー通知メッセージである。関係するメッセージは、トラック バー アクションの終了時に送信される TB_ENDTRACK、およびユーザーがトラック バーをドラッグ中に継続的に送信される TB_THUMBTRACK である。
次の関数は、これら両方のメッセージを処理する。
void HandleTrackbar(HWND hTrackbar, short int userReq) { // ファイルがシーク可能でない場合、トラック バーは無効になる。 // このため、シークはここでは有効である。 static OAFilterState state; static BOOL bStartOfScroll = TRUE; if (userReq == TB_ENDTRACK || userReq == TB_THUMBTRACK) { DWORD Position = SendMessage(hTrackbar, TBM_GETPOS, 0, 0); // スクロール操作が開始するとポーズする。 if (bStartOfScroll) { pControl->GetState(10, &state); bStartOfScroll = FALSE; pControl->Pause(); } // 位置を継続的に更新する。 REFERENCE_TIME newTime = (g_rtTotalTime * Position) / 100; pSeek->SetPositions(&newTime, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); // 最後で状態を復元する。 if (userReq == TB_ENDTRACK) { if (state == State_Stopped) pControl->Stop(); else if (state == State_Running) pControl->Run(); bStartOfScroll = TRUE; } } }
ユーザーがトラック バーをドラッグするのに合わせてシークを滑らかに行うために、ユーザーがトラック バーを離すまで、アプリケーションはグラフをポーズしている。グラフが実行中であった場合、ポーズ モードでは、ユーザーがコントロールをドラッグしている最中に、グラフの再生を継続できなくなる。グラフが停止していた場合、ポーズ モードでもビデオ ウィンドウが確実に更新される。TB_ENDTRACK メッセージを受け取ると、関数はグラフを当初の状態に復元する。