C++ 言語でフィルタグラフの作成・制御

このページの目標

Visual Studio 2005 と C++ 言語を組み合わせ、フィルタグラフの作成・制御を行う DirectShow アプリケーションを作成します。DirectShow フィルタオブジェクトを生成し、フィルタグラフへ追加・フィルタ同士の接続まで実装します。

簡単な動画再生アプリケーションを作る

実装手順

プロジェクトをセットアップする。プロジェクトのセットアップは、ここここを参考にして コンパイルできるところまで進めてください。

COM の初期化をします。

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

クラス ID : CLSID_FilterGraph , インターフェイス ID : IID_IGraphBuilder を取得します。IGraphBuilder インターフェイスが結果となります。フィルタグラフが作成できるようになります。

IGraphBuilder *pGraph = NULL;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, reinterpret_cast<void **>(&pGraph));

IGraphBuilder から、IMediaControl インターフェイスを取得します。インターフェイス ID : IID_IMediaControl です。

IMediaControl *pControl = NULL;
pGraph->QueryInterface(IID_IMediaControl, reinterpret_cast<void **>(&pControl));

IGraphBuilder から IMediaEvent インターフェイスを取得します。インターフェイス ID : IID_IMediaEvent です。

pGraph->QueryInterface(IID_IMediaEvent, reinterpret_cast<void **>(&pEvent));

IGraphBuilder::RenderFile メソッドでフィルタグラフを作成します。このメソッドはフィルタを一つ一つ作成し繋がなくても、デフォルトのレンダラーフィルタで再生を行うフィルターグラフを自動的に作成してくれます。ここでは、c:\windows\clock.avi を再生します。

pGraph->RenderFile(TEXT("C:\\windows\\clock.avi"), NULL);

フィルタグラフを実行します。 IMediaControl::Run を使います。ただし再生開始直後に処理が戻ってくるため、IMediaEvent::WaitForCompletion を呼び出して、実行完了まで待っています。これはアプリケーションのスレッドとフィルタグラフを実行するスレッドが異なっているためです。

// フィルタグラフを実行する
pControl->Run();
// 再生が完了するまで待機
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);

解放処理を行ってアプリケーションを終了します。

ソースコード全体

#include "stdafx.h"

#define SAFE_RELEASE(p) { if((p)==NULL) { (p)->Release(); (p)=NULL; }}

IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent   *pEvent = NULL;

void ReleaseInterfaces(){
	SAFE_RELEASE(pGraph);
	SAFE_RELEASE(pControl);
	SAFE_RELEASE(pEvent);
	CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	
	HRESULT hr=NOERROR;

	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, reinterpret_cast<void **>(&pGraph));
	hr=pGraph->QueryInterface(IID_IMediaControl, reinterpret_cast<void **>(&pControl));
	if(FAILED(hr)){
		ReleaseInterfaces();
		return 0;
	}
	hr=pGraph->QueryInterface(IID_IMediaEvent, reinterpret_cast<void **>(&pEvent));
	if(FAILED(hr)){
		ReleaseInterfaces();
		return 0;
	}
	// c:\\windows\\clock.avi を再生するためのグラフを構築する
	hr = pGraph->RenderFile(TEXT("C:\\windows\\clock.avi"), NULL);
	if(FAILED(hr)){
		ReleaseInterfaces();
		return 0;
	}
	// フィルタグラフを実行する
	hr = pControl->Run();
	// 再生が完了するまで待機
	long evCode;
	pEvent->WaitForCompletion(INFINITE, &evCode);


	ReleaseInterfaces();
	return 0;
}

コンパイルと実行

メニュー [デバッグ]-[デバッグ開始]か F5 キーでプログラムをコンパイルして開始してください。clock.avi が再生されるはずです。

再生が終わると自動的にウィンドウが閉じられます。

フィルタグラフをほぼ手動で作成する

GraphEdit でフィルタグラフを確認

今度は、フィルタを 1 つ 1 つ手動で繋いでみます。しかし、どのようにフィルタを並べていけばいいのか分からないので GraphEdit で c:/windows/clock.avi を再生するグラフを作ってみます。GraphEdit を起動して、メニュー [File]-[Render Media File...] を選択し C:/windows/clock.avi を開きます。映像を画面に表示し、DirectSound で再生されるフィルタグラフが自動的に生成されます。

このようなフィルタグラフを作成すれば再生されるということがわかります。

実装手順

フィルタオブジェクト生成

フィルタは COM になっていますので、CoCreateInstance を使ってオブジェクトを生成します。ソースフィルタについては、IGraphBuilder::AddSourceFilter というメソッドが用意されているので、それを使いましょう。クラス ID は各フィルタのクラス ID です。インターフェイス ID は IID_IBaseFilter となります。DirectShow フィルタは必ず IBaseFilter インターフェイスを持っています。

HRSEULT hr=NOERROR;
IBaseFilter   *pSourceFilter=NULL;
IBaseFilter   *pSplitter=NULL;
// ソースフィルタを作成する場合
hr=pGraph->AddSourceFilter(pFilename, pFilename, &pSourceFilter);
// それ以外のフィルタを作成する場合
hr=CoCreateInstance(CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pSplitter));

フィルタをフィルタグラフへ追加

AddSourceFilter の場合はフィルタグラフへ追加するところまで行いますが、CoCreateInstance を使って作成した場合は、AddFilter を呼び出す必要があります。第 2引数は任意の名前が付けられます。

hr=pGraph->AddFilter(pSplitter,TEXT("Splitter"));

フィルタ同士の接続

フィルタグラフへ追加したら、フィルタ同士を接続します。この処理を全て自前で実装するとかなりの量になるので、インテリジェント接続機能のお世話になりましょう。

おおまかな手順としては、次のようになります。
1. 接続したい 2つのフィルタから、それぞれ ピン を取得します。データが出力されるピンを出力ピン、データが入力されるピンを入力ピンと呼びます。
2. 出力ピンから入力ピンに向かって接続します。

まずフィルタからピンを取得しましょう。IBaseFilter::EnumPins を使ってフィルタの持つピンを列挙します。ピンは IPin インターフェイスで取得します。 IEnumPins::Next で列挙します。イテレータのようなふるまいです。ただし取得したピンは必ず Release を呼びます。

IEnumPins *e=NULL;
IPin *pResult=NULL;//取得したピン
hr=pFilter->EnumPins(&e);
while(e->Next(1, &pResult, NULL) == S_OK){
	// pResult にピンが取得された...
	SAFE_RELEASE(pResult);
}

次に、取得したピンが出力ピンなのか入力ピンなのか判定します。IPin::QueryDirection を使います。

	PIN_DIRECTION PinDirThis;
	hr=pResult->QueryDirection(&PinDirThis);

PinDirThis には PINDIR_OUTPUT または PINDIR_INPUT が代入されています。データを出力するフィルタであれば出力ピン、入力するフィルタであれば入力ピンを取得します。ところで、GraphEdit の図を見るとわかるのですが AVI Splitter フィルタには、2つの出力ピンがあり、それぞれ映像と音声に分岐しています。従って、取得した出力ピンが映像なのか音声なのかを判定できないとですが、それには IPin::EnumMediaTypes を使います。

IPin::EnumMediaTypes で IEnumMediaTypes を取得し IEnumMediaTypes::Next で列挙しています。AM_MEDIA_TYPE 構造体でピンのメディアタイプ、すなわち映像なのか音声なのかという情報を取得します。もし目的のメディアタイプであれば列挙を終了します。なお、複数の出力ピンを持つ場合、このメディアタイプの判定処理は、それぞれの出力ピンに対して実行することになります。

メジャーメディアタイプというのは DirectShow の用語で、メディアタイプの大分類です。単なるバイナリストリーム(MEDIATYPE_Stream)、映像(MEDIATYPE_Video)、音声(MEDIATYPE_Audio) などがあります。

IEnumMediaTypes *em=NULL;
AM_MEDIA_TYPE *amt;
hr=pResult->EnumMediaTypes(&em);
if(SUCCEEDED(hr)){
	while(em->Next(1,&amt,NULL)==S_OK){
		GUID mj=amt->majortype; // 取得したメジャーメディアタイプ
		// amt を解放
		if (amt->cbFormat != 0){
			CoTaskMemFree((PVOID)amt->pbFormat);
		}
		SAFE_RELEASE(amt->pUnk);
		CoTaskMemFree(amt);
		if(mj==majorType){ // 補足 : 目的のメジャーメディアタイプ = majorType
			//目的のメディアタイプであるピンでした
			break;
		}
	}
}
SAFE_RELEASE(em);

入力ピンと出力ピンを取得できたら、IPin::Connect メソッドを呼び出します。

hr=pPinOut->Connect(pPinIn,NULL);

これで、接続完了です。Connect メソッドの第 2 引数はピン同士を接続するためのメディアタイプを指定するのですが、ここを NULL とするとインテリジェント接続機能が働いて煩雑な処理を省くことができます。つまり入出力ピンが繋がるメディアタイプが選ばれます。

※ インテリジェント接続を使ったとしても、メジャーメディアタイプが映像の出力ピンを、音声用の入力ピンに接続することはできません。

全体のソースコード

#include "stdafx.h"

#define SAFE_RELEASE(p) { if((p)!=NULL) { (p)->Release(); (p)=NULL; }}
#define FAILED_RELEASE(r) if(FAILED(r)){ printf("%s:(%d) Failed. HRESULT=%x\n",__FILE__,__LINE__,hr); ReleaseInterfaces(); return 0; }

IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent   *pEvent = NULL;
IBaseFilter   *pSourceFilter=NULL;
IBaseFilter   *pSplitter=NULL;
IBaseFilter   *pAviDecomp=NULL;
IBaseFilter   *pColour=NULL;
IBaseFilter   *pRenderer=NULL;
IBaseFilter   *pACM=NULL;
IBaseFilter   *pDSRenderer=NULL;

const LPCTSTR pFilename=TEXT("c:\\windows\\clock.avi");

void ReleaseInterfaces(){
	SAFE_RELEASE(pDSRenderer);
	SAFE_RELEASE(pACM);
	SAFE_RELEASE(pRenderer);
	SAFE_RELEASE(pColour);
	SAFE_RELEASE(pAviDecomp);
	SAFE_RELEASE(pSplitter);
	SAFE_RELEASE(pSourceFilter);
	SAFE_RELEASE(pGraph);
	SAFE_RELEASE(pControl);
	SAFE_RELEASE(pEvent);
	CoUninitialize();
}

// ピンを取得する
HRESULT GetPin(IBaseFilter *pFilter,PIN_DIRECTION dir,IPin *&pPin,GUID majorType){
	HRESULT retCode=E_FAIL;
	HRESULT hr=NOERROR;
	IEnumPins *e=NULL;
	IPin *pResult=NULL;
	hr=pFilter->EnumPins(&e);
	if(FAILED(hr))
		return hr;
	FILTER_INFO filinfo;
	pFilter->QueryFilterInfo(&filinfo);
	while(e->Next(1, &pResult, NULL) == S_OK){
		PIN_DIRECTION PinDirThis;
		hr=pResult->QueryDirection(&PinDirThis);
		if (pResult!=NULL && SUCCEEDED(hr) && PinDirThis==dir){
			PIN_INFO info;
			pResult->QueryPinInfo(&info);
			if(dir==PINDIR_INPUT){
				pPin=pResult;
				retCode=S_OK;
			}else{
				IEnumMediaTypes *em=NULL;
				AM_MEDIA_TYPE *amt;
				hr=pResult->EnumMediaTypes(&em);
				if(SUCCEEDED(hr)){
					while(em->Next(1,&amt,NULL)==S_OK){
						GUID mj=amt->majortype;
						// amt を解放
						if (amt->cbFormat != 0){
							CoTaskMemFree((PVOID)amt->pbFormat);
						}
						SAFE_RELEASE(amt->pUnk);
						CoTaskMemFree(amt);
						if(mj==majorType){
							pPin=pResult;
							retCode=S_OK;
							break;
						}
					}
				}
				SAFE_RELEASE(em);
			}
			if(retCode==S_OK)
				break;
		}
		SAFE_RELEASE(pResult);
	}
	SAFE_RELEASE(e);
	return retCode;
}

// フィルタの同士を接続する
HRESULT ConnectFilter(IBaseFilter *pSrc,IBaseFilter *pDest,GUID majorType){
	HRESULT hr=NOERROR;
	IPin *pPinOut=NULL;
	IPin *pPinIn=NULL;
	hr=GetPin(pSrc,PINDIR_OUTPUT,pPinOut,majorType);
	if(FAILED(hr)){
		printf("Output pin is not found. : from %p to %p\n",pSrc,pDest);
		return hr;
	}
	hr=GetPin(pDest,PINDIR_INPUT,pPinIn,GUID_NULL);
	if(SUCCEEDED(hr)){
		hr=pPinOut->Connect(pPinIn,NULL);
	}
	if(FAILED(hr)){
		printf("Failed Connecting. : from %p to %p\n",pSrc,pDest);
	}
	// 取得したピン インターフェイスを解放
	SAFE_RELEASE(pPinIn);
	SAFE_RELEASE(pPinOut);
	return hr;
}

int _tmain(int argc, _TCHAR* argv[])
{
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	
	HRESULT hr=NOERROR;

	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, reinterpret_cast<void **>(&pGraph));
	hr=pGraph->QueryInterface(IID_IMediaControl, reinterpret_cast<void **>(&pControl));
	FAILED_RELEASE(hr);
	hr=pGraph->QueryInterface(IID_IMediaEvent, reinterpret_cast<void **>(&pEvent));
	FAILED_RELEASE(hr);
	// c:\\windows\\clock.avi を再生するためのグラフを構築する (コメントアウト済)
	// hr = pGraph->RenderFile(TEXT("C:\\windows\\clock.avi"), NULL);

	// フィルタの作成と、フィルタグラフへ追加

	// ソースフィルタ
	hr=pGraph->AddSourceFilter(pFilename, pFilename, &pSourceFilter);
	FAILED_RELEASE(hr);
	// AVI Splitter
	hr=CoCreateInstance(CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pSplitter));
	FAILED_RELEASE(hr);
	hr=pGraph->AddFilter(pSplitter,TEXT("Splitter"));
	FAILED_RELEASE(hr);
	// AVI Decommpressor
	hr=CoCreateInstance(CLSID_AVIDec, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pAviDecomp));
	FAILED_RELEASE(hr);
	hr=pGraph->AddFilter(pAviDecomp,TEXT("Avi Decommpressor"));
	FAILED_RELEASE(hr);
	// Color space converter
	hr=CoCreateInstance(CLSID_Colour, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pColour));
	FAILED_RELEASE(hr);
	hr=pGraph->AddFilter(pColour,TEXT("Color space converter"));
	FAILED_RELEASE(hr);
	// Video Renderer
	hr=CoCreateInstance(CLSID_VideoRenderer, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pRenderer));
	FAILED_RELEASE(hr);
	hr=pGraph->AddFilter(pRenderer,TEXT("Video Renderer"));
	FAILED_RELEASE(hr);
	// ACM Wrapper
	hr=CoCreateInstance(CLSID_ACMWrapper, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pACM));
	FAILED_RELEASE(hr);
	hr=pGraph->AddFilter(pACM,TEXT("ACM wrapper"));
	FAILED_RELEASE(hr);
	// DirectSound Renderer
	hr=CoCreateInstance(CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, reinterpret_cast<void **>(&pDSRenderer));
	FAILED_RELEASE(hr);
	hr=pGraph->AddFilter(pDSRenderer,TEXT("DirectSound Renderer"));
	FAILED_RELEASE(hr);
	// フィルタの同士を接続する
	hr=ConnectFilter(pSourceFilter,pSplitter,MEDIATYPE_Stream);
	FAILED_RELEASE(hr);
	hr=ConnectFilter(pSplitter,pAviDecomp,MEDIATYPE_Video);
	FAILED_RELEASE(hr);
	hr=ConnectFilter(pAviDecomp,pColour,MEDIATYPE_Video);
	FAILED_RELEASE(hr);
	hr=ConnectFilter(pColour,pRenderer,MEDIATYPE_Video);
	FAILED_RELEASE(hr);
	hr=ConnectFilter(pSplitter,pACM,MEDIATYPE_Audio);
	FAILED_RELEASE(hr);
	hr=ConnectFilter(pACM,pDSRenderer,MEDIATYPE_Audio);
	FAILED_RELEASE(hr);

	// フィルタグラフを実行する
	hr = pControl->Run();
	// 再生が完了するまで待機
	long evCode;
	pEvent->WaitForCompletion(INFINITE, &evCode);

	ReleaseInterfaces();
	return 0;
}

少々長いですが、フィルタオブジェクトの作成、接続、解放が含まれており DirectShow アプリケーションの基本となります。