Microsoft DirectX 8.0

ビデオ キャプチャ フィルタの開発

このトピックでは、ビデオ キャプチャ フィルタを作成する際に考慮すべき重要な点について説明する。Microsoft® DirectShow® には標準の VFW キャプチャ フィルタが含まれている。

このトピックは、以下のセクションを含んでいる。

キャプチャ ピンとプレビュー ピンの要件

キャプチャ フィルタのキャプチャ ピンとプレビュー ピン (存在する場合) は、IKsPropertySet インターフェイスをサポートする必要がある。アプリケーションはこのインターフェイスを呼び出して、AMPROPSETID_Pin プロパティ セットの AMPROPERTY_PIN_CATEGORY の値を取得することにより "どのカテゴリのピンであるか" をたずねる。戻り値は、通常、PIN_CATEGORY_CAPTURE または PIN_CATEGORY_PREVIEW のいずれかの GUID である。詳細は、「ピン プロパティ セット」を参照すること。キャプチャ フィルタは IKsPropertySet をサポートする必要がある。サポートされていないと、アプリケーションはフィルタ グラフ内でフィルタを接続する方法を指示することができない。

ピンには任意の名前を付けることができ、その他の必要な目的のために出力ピンを追加できる。ピンの名前がチルダ (~) 文字で始まる場合、アプリケーションが IGraphBuilder::RenderFile を呼び出しても、フィルタ グラフはそのピンに対して自動的なレンダリングを行わない。たとえば、キャプチャ フィルタにキャプチャ ピンとプレビュー ピンが両方ともあり、キャプチャ ピンに "~capture" 、プレビュー ピンに "preview" という名前を付けたとする。この場合、アプリケーションがグラフ内でフィルタをレンダリングすると、プレビュー ピンはビデオ レンダラに接続され、キャプチャ ピンには何も接続されない。デフォルトでこのような設定を使用することは一般的である。これは、レンダリングを行わない単なる情報用のピンや、プロパティを設定するために列挙する必要があるピンなどにも応用できる。

接頭文字チルダ (~) は、RenderFile とインテリジェント接続 (IGraphBuilder::Connect) の動作にのみ影響する。それでも、このプロパティを含んでいるピンが IPin::Connect メソッドを実装していれば、それらのピンの接続にインテリジェントな接続を使用できる。しかし、名前がチルダ文字で始まる接続を完了するために中間フィルタを使用している場合は、インテリジェント接続に際してそのフィルタの出力ピンが接続されることはない。

オーディオ キャプチャ フィルタの詳細については、「オーディオ キャプチャ ピンの要件」を参照すること。

次のサンプル コードに、キャプチャ ピンに IKsPropertySet を実装する方法を示す。

//
// ピン カテゴリ - これがキャプチャ ピンであることを示す。
//

HRESULT CMyCapturePin::Set(REFGUID guidPropSet, DWORD dwPropID,
LPVOID pInstanceData, DWORD cbInstanceData, 
						   LPVOID pPropData, DWORD cbPropData)
{
    return E_NOTIMPL;
}

// プロパティを取得するために、呼び出し元は呼び出した関数がデータを格納するための
// バッファを割り当てる。必要なバッファ サイズを決定するために、pPropData=NULL および 
// cbPropData=0 を指定して Get を呼び出す。
HRESULT CMyCapturePin::Get(REFGUID guidPropSet, DWORD dwPropID,
LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, 
                           DWORD cbPropData, DWORD *pcbReturned)
{
if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pPropData == NULL && pcbReturned == NULL)
return E_POINTER;

    if (pcbReturned)
        *pcbReturned = sizeof(GUID);

    if (pPropData == NULL)
return S_OK;

    if (cbPropData < sizeof(GUID))
        return E_UNEXPECTED;

    *(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
return S_OK;
}


// QuerySupported は E_NOTIMPL を返すか、または、プロパティ セットとプロパティの
// 取得や設定がサポートされているかどうかを正しく示す必要がある。
// S_OK は、プロパティ セットとプロパティ ID の組み合わせが正しいことを示す。
HRESULT CMyCapturePin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID,
                                      DWORD *pTypeSupport)
{
if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pTypeSupport)
        *pTypeSupport = KSPROPERTY_SUPPORT_GET;
return S_OK;
}

キャプチャとプレビューの最適化 (オプション)

フィルタが動作中で、データのキャプチャを行っているときには、プレビュー ピンとキャプチャ ピンの両方からフレームのコピーを送信する必要がある。ハードウェア支援 (オーバーレイなど) によるプレビューが可能で、プレビュー ピンを使用している場合は、そのプレビュー ピンに対して IMemInputPin インターフェイスの代わりに IOverlay インターフェイス転送を使用できる。IOverlay の使用はオプションである。ハードウェア支援によるプレビューを実行できない場合は、空いた時間があるときにだけプレビュー ピンからフレームを送る。フレームをドロップするおそれがある場合は、これを行わない。キャプチャ ピンが優先される。

たとえば、キャプチャ ピンから直ちに送るべきものが 1 つもなく、ダウンストリーム フィルタがキャプチャ ピンから以前に配送されたバッファをすべて解放してある場合のみ、プレビュー ピンからフレームを配送することができる。

データのフォーマットを 1 種類しかキャプチャできず、そのためにプレビュー ピンとキャプチャ ピンが同じメディア タイプを生成する必要がある場合、または、ピンを適切に再接続する方法の詳細を知りたい場合は、先へ読み進む。それ以外の場合は、このトピックを読む必要はない。

プレビュー ピンとキャプチャ ピンから同じフォーマットのデータを送る。フィルタ グラフ マネージャがキャプチャ ピンを異なるフォーマットで再接続した場合は、プレビュー ピンを同じフォーマットで再接続しなければ機能しない。キャプチャ ピンが接続されていてプレビュー ピンが接続されていない場合は、キャプチャ ピンと同じメディア タイプでプレビュー ピンのみ接続する必要がある。2 つのピンは、一致していなければならない。

次のサンプル コードに、適切な再接続の手順を示す。

// プレビュー ピンの接続先のフィルタが新しいメディア タイプを受け入れない場合、
// キャプチャ ピンがそのメディア タイプを受け入れることはできない。プレビュー
// ピンは、常にキャプチャ ピンと同じタイプを出力しなければならない。
CCapturePin::CheckMediaType(CMediaType *pmt)
{
	if (m_pMyPreviewPin->IsConnected()) {
		// プレビュー ピンの接続はこのタイプと同じか?
  		if (m_pMyPreviewPin->GetConnected()->QueryAccept(pmt) != NOERROR) {
			// プレビュー ピンはこの新しいタイプでは再接続できない。
			return E_INVALIDARG;
		}
	}

	// キャプチャ ピンの接続先がわかれば、このメディア タイプで
	// よいかどうか決定できるであろう。しかし、その必要は
	// ない。キャプチャ ピンが接続されると、同じフォーマットを
	// 使用して再接続される。

	return NOERROR;
}


// プレビュー ピンが受け入れ可能なメディア タイプは、
// キャプチャ ピンが使用しているものだけである。
CPreviewPin::CheckMediaType(CMediaType *pmt)
{
	CMediaType cmt = m_pMyCapturePin->m_mt;
 	if (m_pMyCapturePin->IsConnected() && *pmt != cmt)
		// プレビュー ピンはキャプチャ ピンと同じ
		// フォーマットでなければ接続できない。
		return E_INVALIDARG;

	else if (!m_pMyCapturePin->IsConnected())
		// キャプチャ ピンの接続先がわかれば、このメディア タイプで
		// よいかどうか決定できるであろう。しかし、その必要は
		// ない。キャプチャ ピンが接続されると、同じフォーマットを
		// 使用して再接続される。

	// キャプチャ ピンが接続されていて、これが同じメディア タイプであれば、
	// OK である。
	return NOERROR;
}

// キャプチャ ピンは、特定のメディア タイプを使用するように指定されている。
// プレビュー ピンにも、同じメディア タイプを使用させなければならない。
CCapturePin::SetMediaType(CMediaType *pmt);
{
if (m_pMyPreviewPin->IsConnected()) {

	// このメディア タイプでプレビュー ピンを再接続する必要がある。
  	if (m_pMyPreviewPin->GetConnected()->QueryAccept(pmt) == NOERROR) {

		// プレビュー ピンが接続されている他のフィルタは、この新しい
		// メディア タイプを受け入れることができるので、そのまま再接続する。
		m_pFilter->m_pGraph->Reconnect(m_pMyPreviewPin);
	}
    }
}

ビデオ キャプチャ フィルタの登録

フィルタはビデオ キャプチャ フィルタ カテゴリに登録する必要がある。詳細については、AMovieDllRegisterServer2 の説明を参照すること。

データの生成

キャプチャ ピンおよびプレビュー ピンでのデータの生成は、フィルタ グラフが実行状態の場合にだけ行う。フィルタ グラフがポーズしている間は、ピンからデータを送信してはならない。もし送信すると、フィルタ グラフに混乱が生じる。これを避けるには CBaseFilter::GetState 関数から VFW_S_CANT_CUE を返すことで、フィルタ グラフに対して、ポーズ中はデータを送信しないことを警告する。これを次のコードに示す。

CMyVidcapFilter::GetState(DWORD dw, FILTER_STATE *State)
{
*State = m_State;
if (m_State == State_Paused)
return VFW_S_CANT_CUE;
else
return S_OK;
}

個々のストリームの制御

出力ピンはすべて IAMStreamControl インターフェイスをサポートする必要があるため、アプリケーションは各ピンを個別にオンまたはオフにすることができる (たとえば、キャプチャせずにプレビューする場合など)。IAMStreamControl を使用すると、別のグラフを再作成しなくとも、プレビューとキャプチャの間で切り替えることができる。

タイム スタンプ

フレームをキャプチャして送信する場合、フレームがキャプチャされたときのグラフのクロックの時刻によって、フレームにタイム スタンプを記録する。終了タイムは、開始タイムに時間幅を加えた値である。たとえば、1 秒間に 10 フレームというレートでキャプチャされ、キャプチャされた時点でグラフのクロックが 200,000,000 単位を示している場合、サンプルのタイム スタンプは (200000000, 201000000) となる (1 秒あたり 10,000,000 単位)。

プレビュー フレームには遅延時間の問題があるので、タイム スタンプを記録しないほうがよい。サンプルのタイム スタンプがプレビュー ピンから出力されたときのグラフの時刻である場合、サンプルがレンダラに到着するまでに遅れが生じるため、遅延時間が発生する。したがって、レンダラは時間を節約して "遅れを取り戻す" ために、そのサンプルを描画しない場合がある。これは、ライブ ストリームでは許されない。IAMStreamControl を実装するにはタイム スタンプが必要である。そのため、プレビュー ピンにはストリーム コントロールを実装しないか、処理待ちの開始または停止要求がある場合のみプレビュー ピン サンプルにタイム スタンプを記録するか、あるいは遅延時間の問題を許容するか、のいずれかを選択する。

配送するサンプルのメディア タイムを設定しなければならない。また、キャプチャ ピンに対する通常のタイム スタンプの設定も必要である。メディア タイムはサンプルのフレーム番号である。たとえば、フレームのキャプチャと送信を行い、フレーム 3 がドロップされる場合、メディア タイム値を (0,1) (1,2) (2,3) (4,5) (5,6) のように設定する。これにより、使用されているクロックがビデオ デジタル化用のクロックではなく通常のタイム スタンプがややランダムな場合でも、ビデオ フレームにドロップが生じると、ダウンストリームにそれを通知できる。

また、実行状態からポーズし、再び実行する場合は、ポーズの前に送信された最後のタイム スタンプよりも小さいタイム スタンプでサンプルを送信してはならない。タイム スタンプは時間をさかのぼることはできず、ポーズ前の時間に戻ることもできない。

フレーム レート

フィルタがデータを生成するフレーム レートは、出力ピンが接続されているメディア タイプの VIDEOINFOHEADERAvgTimePerFrame フィールドによって決定される。キャプチャは任意のフレーム レートでは行えず、特定のレートでのみ行える。ピンが接続されているメディア タイプから、実行できないフレーム レートを要求された場合、そのレートより低く、最も近い値の実行可能なフレーム レートによってフレームを提供しなければならない。たとえば、メディア タイプの AvgTimePerFrame が 333333 (1 秒の約 1/30、つまり 1 秒あたり 30 フレーム) であるのに対して、実行可能なフレーム レートが 1 秒に 29.97 フレームまたは 35 フレームである場合、1 秒あたり 29.97 フレームというレートにしなければならない。このフレーム レートが実行可能な値の中で最も 30 に近く、30 より小さい値だからである。AvgTimePerFrame には丸め誤差があるので、提供するフレーム レートによるフレームの時間幅が要求された値より 1 マイクロ秒以上短くならなければ、要求された値より大きなフレーム レートにすることができる。AvgTimePerFrame フィールドが 0 の場合、任意のデフォルト フレーム レートでフレームを供給できる。

IAMDroppedFrames の実装

キャプチャ フィルタのビデオ出力ピンは、必ず IAMDroppedFrames インターフェイスを実装しなければならない。

キャプチャ フィルタを実行すると、フィルタは 0、1、2、3 というシーケンスで始まるフレーム番号を送信する (フレームがドロップされた場合、その番号は欠落する) 。送信された各フレームのタイム スタンプは、イメージがデジタル化されたときのグラフ クロックの時刻に対応する。終了タイムは、開始タイムにビデオ フレームの時間幅を加えた値である。

CMediaSample::SetMediaTime を使用し、開始タイムと終了タイムのフレーム番号に基づいて、各サンプルのメディア タイムを設定する。たとえば、開始タイムと終了タイムのシーケンスは (0,1) (1,2) (2,3) のように表示される。ダウンストリーム フィルタは、通常のタイム スタンプにおける欠落を探す代わりに、フレーム番号のシーケンスにおける欠落を調べることにより、フレームがドロップされたかどうかを簡単に確認できる。開始タイムと終了タイムのシーケンスが (1,2) (2,3) (4,5) (5,6) であれば、フレーム番号 3 がドロップされたことがわかる。

キャプチャ フィルタでは、状態が State_Stopped から State_Paused に変化するたびに、カウントをすべて 0 にリセットする必要がある。

フィルタが実行状態からポーズされ、再び実行された場合、ポーズ状態と関係なくフレームの配送を継続する必要がある。2 回目の実行の後の最初のフレームには、ポーズの前に送信された最後のフレームよりも早い時刻のタイム スタンプを記録してはならない。つまり、フィルタは送信された各サンプルのメディア タイムを常に増加させる必要がある。同じフレーム番号を 2 回送信したり、時間をさかのぼったりしてはならない。

必要なインターフェイス

以下のインターフェイスの説明を読み、その実装を考慮すること。これらのインターフェイスはアプリケーションが使用する可能性のある機能を提供するので、実装することを強く推奨する。