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 メッセージを受け取ると、関数はグラフを当初の状態に復元する。