ソース フィルターでのシークのサポート
[このページに関連付けられている機能 DirectShow は、従来の機能です。 MediaPlayer、IMFMediaEngine、および Media Foundation のオーディオ/ビデオ キャプチャに置き換わりました。 これらの機能は、Windows 10とWindows 11用に最適化されています。 新しいコードでは、可能であれば、DirectShow ではなく Media Foundation で MediaPlayer、IMFMediaEngine、Audio/Video Capture を使用することを強くお勧めします。 Microsoft は、従来の API を使用する既存のコードを、可能であれば新しい API を使用するように書き直すよう提案しています。]
このトピックでは、Microsoft DirectShow ソース フィルターでシークを実装する方法について説明します。 開始点として Ball Filter サンプルを使用し、このフィルターでのシークをサポートするために必要な追加のコードについて説明します。
ボール フィルター サンプルは、アニメーション化されたバウンス ボールを作成するソース フィルターです。 この記事では、このフィルターにシーク機能を追加する方法について説明します。 この機能を追加したら、GraphEdit でフィルターをレンダリングし、GraphEdit スライダーをドラッグしてボールを制御できます。
このトピックは、次のセクションで構成されています。
DirectShow でのシークの概要
アプリケーションは、フィルター グラフ マネージャーで IMediaSeeking メソッドを 呼び出してフィルター グラフをシークします。 次に、フィルター グラフ マネージャーは、グラフ内のすべてのレンダラーに呼び出しを分散します。 各レンダラーは、次のアップストリーム フィルターの出力ピンを介して、呼び出しをアップストリームに送信します。 呼び出しは、seek コマンド (通常はソース フィルターまたはパーサー フィルター) を実行できるフィルターに到達するまで上流に移動します。 一般に、タイムスタンプを生成するフィルターはシークも処理します。
フィルターは、次のように seek コマンドに応答します。
- フィルターによってグラフがフラッシュされます。 これにより、古いデータがグラフからクリアされ、応答性が向上します。 それ以外の場合は、seek コマンドの前にバッファーされたサンプルが配信される可能性があります。
- このフィルターは IPin::NewSegment を呼び出して、新しい停止時間、開始時刻、再生速度をダウンストリーム フィルターに通知します。
- フィルターは、seek コマンドの後の最初のサンプルで不連続性フラグを設定します。
タイム スタンプは、seek コマンド (レート変更を含む) の後に 0 から開始されます。
ボールフィルターの概要
ボール フィルターはプッシュ ソースです。つまり、ダウンストリーム フィルターがサンプルを要求するのを受動的に待機するプル ソースとは対照的に、ワーカー スレッドを使用してサンプルをダウンストリームに配信します。 Ball フィルターは CSource クラスからビルドされ、その出力ピンは CSourceStream クラスからビルドされます。 CSourceStream クラスは、データフローを駆動するワーカー スレッドを作成します。 このスレッドは、アロケーターからサンプルを取得し、データを入力してダウンストリームに配信するループに入ります。
CSourceStream のほとんどのアクションは、派生クラスが実装する CSourceStream::FillBuffer メソッドで行われます。 このメソッドの引数は、配信するサンプルへのポインターです。 ボール フィルターの FillBuffer の実装は、サンプル バッファーのアドレスを取得し、個々のピクセル値を設定してバッファーに直接描画します。 (描画はヘルパー クラス CBall によって行われるので、ビット深度やパレットなどに関する詳細は無視できます)。
シーク用のボールフィルターの変更
Ball フィルターをシーク可能にするには、 CSourceSeeking クラスを 使用します。これは、1 つの出力ピンを持つフィルターでシークを実装するために設計されています。 CSourceSeeking クラスを CBallStream クラスの継承リストに追加します。
class CBallStream : // Defines the output pin.
public CSourceStream, public CSourceSeeking
また、 CSourceSeeking の初期化子を CBallStream コンストラクターに追加する必要があります。
CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),
このステートメントは、 CSourceSeeking の基本コンストラクターを呼び出します。 パラメーターは、名前、所有ピンへのポインター、 HRESULT 値、およびクリティカル セクション オブジェクトのアドレスです。 名前はデバッグにのみ使用され、 NAME マクロは小売ビルドで空の文字列にコンパイルされます。 ピンは CSourceSeeking を直接継承するため、2 番目のパラメーターはそれ自体へのポインターであり、 IPin ポインターにキャストされます。 HRESULT 値は、基底クラスの現在のバージョンでは無視されます。これは、以前のバージョンとの互換性のために残っています。 クリティカル セクションは、現在の開始時刻、停止時間、再生速度などの共有データを保護します。
CSourceSeeking コンストラクター内に次のステートメントを追加します。
m_rtStop = 60 * UNITS;
m_rtStop変数は、停止時間を指定します。 既定では、値は _I64_MAX / 2 で、約 14,600 年です。 前のステートメントでは、より保守的な 60 秒に設定されています。
2 つの追加メンバー変数を CBallStream に追加する必要があります。
BOOL m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME m_rtBallPosition; // Position of the ball.
すべての seek コマンドの後、フィルターは IMediaSample::SetDiscontinuity を呼び出して、次のサンプルの不連続性フラグを設定する必要があります。 m_bDiscontinuity変数は、これを追跡します。 m_rtBallPosition変数は、ビデオ フレーム内のボールの位置を指定します。 元の Ball フィルターはストリーム時間からの位置を計算しますが、ストリーム時間は各 seek コマンドの後に 0 にリセットされます。 シーク可能なストリームでは、絶対位置はストリーム時間に依存しません。
QueryInterface
CSourceSeeking クラスは、IMediaSeeking インターフェイスを実装します。 このインターフェイスをクライアントに公開するには、 NonDelegatingQueryInterface メソッドを オーバーライドします。
STDMETHODIMP CBallStream::NonDelegatingQueryInterface
(REFIID riid, void **ppv)
{
if( riid == IID_IMediaSeeking )
{
return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
}
return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}
DirectShow 基本クラスがコンポーネント オブジェクト モデル (COM) 集計をサポートするため、メソッドは "NonDelegating" と呼ばれます。 詳細については、DirectShow SDK の「IUnknown を実装する方法」トピックを参照してください。
メソッドのシーク
CSourceSeeking クラスは、シークに関連するいくつかのメンバー変数を保持します。
変数 | 説明 | 既定値 |
---|---|---|
m_rtStart | 開始時刻 | ゼロ |
m_rtStop | 停止時刻 | _I64_MAX / 2 |
m_dRateSeeking | 再生速度 | 1.0 |
IMediaSeeking::SetPositions の CSourceSeeking 実装は、開始時刻と停止時刻を更新し、派生クラス CSourceSeeking::ChangeStart と CSourceSeeking::ChangeStop の 2 つの純粋な仮想メソッドを呼び出します。 IMediaSeeking::SetRate の実装は似ています。再生速度を更新し、純粋な仮想メソッド CSourceSeeking::ChangeRate を呼び出します。 これらの各仮想メソッドでは、ピンで次の操作を行う必要があります。
- IPin::BeginFlush を呼び出して、データのフラッシュを開始します。
- ストリーミング スレッドを停止します。
- IPin::EndFlush を呼び出します。
- ストリーミング スレッドを再起動します。
- IPin::NewSegment を呼び出します。
- 次のサンプルで不連続性フラグを設定します。
ストリーミング スレッドは、サンプルの配信または新しいサンプルの取得を待機している間にブロックできるため、これらの手順の順序は非常に重要です。 BeginFlush メソッドを使用すると、ストリーミング スレッドがブロックされないため、手順 2 でデッドロックが発生しないようにします。 EndFlush 呼び出しは、新しいサンプルを想定するようにダウンストリーム フィルターに通知するため、手順 4 でスレッドが再び起動しても拒否されません。
次のプライベート メソッドは、手順 1 から 4 を実行します。
void CBallStream::UpdateFromSeek()
{
if (ThreadExists())
{
DeliverBeginFlush();
// Shut down the thread and stop pushing data.
Stop();
DeliverEndFlush();
// Restart the thread and start pushing data again.
Pause();
}
}
ストリーミング スレッドが再び起動すると、 CSourceStream::OnThreadStartPlay メソッドが呼び出されます。 手順 5 と 6 を実行するには、このメソッドをオーバーライドします。
HRESULT CBallStream::OnThreadStartPlay()
{
m_bDiscontinuity = TRUE;
return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}
ChangeStart メソッドで、ストリーム時間を 0 に設定し、ボールの位置を新しい開始時刻に設定します。 次に、CBallStream::UpdateFromSeek を呼び出します。
HRESULT CBallStream::ChangeStart( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_rtSampleTime = 0;
m_rtBallPosition = m_rtStart;
}
UpdateFromSeek();
return S_OK;
}
ChangeStop メソッドで、新しい停止時間が現在の位置より小さい場合は CBallStream::UpdateFromSeek を呼び出します。 それ以外の場合、停止時間は今後も続くので、グラフをフラッシュする必要はありません。
HRESULT CBallStream::ChangeStop( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
if (m_rtBallPosition < m_rtStop)
{
return S_OK;
}
}
// We're already past the new stop time. Flush the graph.
UpdateFromSeek();
return S_OK;
}
レート変更の場合、CSourceSeeking::SetRate メソッドは、ChangeRate を呼び出す前に、m_dRateSeekingを新しいレート (古い値を破棄) に設定します。 残念ながら、呼び出し元が無効なレート (0 未満など) を指定した場合、 ChangeRate が呼び出されるまでに遅すぎます。 1 つの解決策は、有効なレートに対して SetRate とチェックをオーバーライドすることです。
HRESULT CBallStream::SetRate(double dRate)
{
if (dRate <= 1.0)
{
return E_INVALIDARG;
}
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_dRateSeeking = dRate;
}
UpdateFromSeek();
return S_OK;
}
// Now ChangeRate won't ever be called, but it's pure virtual, so it needs
// a dummy implementation.
HRESULT CBallStream::ChangeRate() { return S_OK; }
バッファー内の描画
各フレームにボールを描画するルーチンである CSourceStream::FillBuffer の変更されたバージョンを次に示します。
HRESULT CBallStream::FillBuffer(IMediaSample *pMediaSample)
{
BYTE *pData;
long lDataLen;
pMediaSample->GetPointer(&pData);
lDataLen = pMediaSample->GetSize();
{
CAutoLock cAutoLockShared(&m_cSharedState);
if (m_rtBallPosition >= m_rtStop)
{
// End of the stream.
return S_FALSE;
}
// Draw the ball in its current position.
ZeroMemory( pData, lDataLen );
m_Ball->MoveBall(m_rtBallPosition);
m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);
// The sample times are modified by the current rate.
REFERENCE_TIME rtStart, rtStop;
rtStart = static_cast<REFERENCE_TIME>(
m_rtSampleTime / m_dRateSeeking);
rtStop = rtStart + static_cast<int>(
m_iRepeatTime / m_dRateSeeking);
pMediaSample->SetTime(&rtStart, &rtStop);
// Increment for the next loop.
m_rtSampleTime += m_iRepeatTime;
m_rtBallPosition += m_iRepeatTime;
}
pMediaSample->SetSyncPoint(TRUE);
if (m_bDiscontinuity)
{
pMediaSample->SetDiscontinuity(TRUE);
m_bDiscontinuity = FALSE;
}
return NOERROR;
}
このバージョンと元のバージョンの主な違いは次のとおりです。
- 前述のように、 m_rtBallPosition 変数を使用して、ストリーム時間ではなくボールの位置を設定します。
- クリティカル セクションを保持した後、メソッドは現在の位置が停止時間を超えているかどうかを確認します。 その場合は、 データの送信を停止し、ストリームの終了通知を配信するように基底クラスに通知するS_FALSEが返されます。
- タイム スタンプは現在のレートで割ります。
- m_bDiscontinuityが TRUE の場合、メソッドはサンプルに不連続性フラグを設定します。
もう 1 つの小さな違いがあります。 元のバージョンはバッファーを 1 つだけ持つことに依存しているため、ストリーミングが開始されると、バッファー全体に 1 回ゼロが入力されます。 その後、ボールを前の位置から消去するだけです。 ただし、この最適化により、ボール フィルターのわずかなバグが明らかになります。 CBaseOutputPin::D ecideAllocator メソッドが IMemInputPin::NotifyAllocator を呼び出すと、読み取り専用フラグが FALSE に設定されます。 その結果、ダウンストリーム フィルターはバッファーに自由に書き込まれます。 1 つの解決策は、 DecideAllocator をオーバーライドして、読み取り専用フラグを TRUE に設定 することです。 ただし、わかりやすくするために、新しいバージョンでは最適化が完全に削除されます。 代わりに、このバージョンでは、毎回バッファーにゼロが設定されます。
その他の変更
新しいバージョンでは、次の 2 行が CBall コンストラクターから削除されます。
m_iRandX = rand();
m_iRandY = rand();
元のボール フィルターは、これらの値を使用して、最初のボール位置にランダム性を追加します。 我々の目的のために、我々はボールが決定論的でありたいと考えています。 また、一部の変数は CRefTime オブジェクトから REFERENCE_TIME 変数に変更されています。 ( CRefTime クラスは、 REFERENCE_TIME 値のシン ラッパーです)。最後に、 IQualityControl::Notify の実装が若干変更されました。詳細については、ソース コードを参照してください。
CSourceSeeking クラスの制限事項
CSourceSeeking クラスは、クロスピン通信に問題があるため、複数の出力ピンを持つフィルター用ではありません。 たとえば、インターリーブされたオーディオ ビデオ ストリームを受信し、ストリームをオーディオおよびビデオ コンポーネントに分割し、ある出力ピンからビデオを配信し、別の出力ピンからオーディオを配信するパーサー フィルターを想像してください。 どちらの出力ピンもすべての seek コマンドを受け取りますが、フィルターは seek コマンドごとに 1 回だけシークする必要があります。 解決策は、シークを制御するピンの 1 つを指定し、もう一方のピンで受信したシーク コマンドを無視することです。
ただし、seek コマンドの後に、両方のピンでデータをフラッシュする必要があります。 さらに複雑にするために、seek コマンドは、ストリーミング スレッドではなくアプリケーション スレッドで実行されます。 そのため、どちらのピンもブロックされておらず、 IMemInputPin::Receive 呼び出しが返されるのを待っているか、デッドロックが発生する可能性があることを確認する必要があります。 ピンでのスレッド セーフフラッシュの詳細については、「 スレッドとクリティカル セクション」を参照してください。
関連トピック