カテゴリー別アーカイブ: DirectShow

開発環境移行 VS2005=>VS2010編

マシンを更新したついでに開発環境も一気にバージョンアップすることにしました。といっても完全に最新にはしませんでした。まあ、一応WinXPでプログラムを動作させるなどを考えるとあまりにも最新にしすぎると問題が出る可能性がある、ということで一つ手前であるVS2010にアップすることにしました。そのため、話題的に最新ではないです。

しかしながら、これだけバージョンが上がるといろいろと動作変更やコンパイル時に問題が出たりして・・・。(自分が開発してきた各プロジェクトを)この環境を整合させるのに3日間ほどかかってしまいました。一応すべてバージョンアップが終わってある程度の動作チェックができたのでよしとしています。先に書いておくと、以降開発環境はVS2010を基準としていきます。MovieLayerPlayerやDirectShow Extend Filter Libraryも更新版を作成しましたのでもう少し動作チェックができれば公開する予定です。WindowModePatchも動作は確認したのですがあるプログラムに対する対応コードを組んでいるときに環境が更新されたのでその部分ができたら公開する予定となっています。

 

インストールそのものは何も変わりなし

というか以前にSliverlightのプログラムを構築したときに一時的にVS2010を使っていたのでそこまで戸惑うようなことはなかったですね。普通にインストールして終わりです。ただ、以前と違ってヘルプを完全にオフラインで参照することができなくなっていたり、コンパイルエラーの行を選択してF1を押しても対象のコンパイルエラーに関するヘルプが参照されなかったりとヘルプの機能は大幅に劣化しているという印象が第一ですね。で、最新の環境に持って行くなら、ということでいつもの通り最新のWindowsSDKを入れて環境を適応させ・・・ようとしていろいろと大変だったのですよ。

 

ライブラリやインクルードディレクトリの参照はVisualStudioの設定ではなく別のファイルに分離されている

まず戸惑った点第一。WindowsSDKや、古いですがDirectXSDK、また自分で作ったライブラリやインクルードファイルをVisualStudioから参照させるためにはライブラリディレクトリやインクルードディレクトリをプロジェクトに教えなければ使えません。その設定がVisualStudio本体から別ファイルに移動させられているのでそちらを編集することになります。ファイル位置は(自分のホームディレクトリ)\AppData\Local\Microsoft\MSBuild以降(にバージョン番号などがある)のファイルとなります。x86であれば「Microsoft.Cpp.Win32.user.props」、x86-64であれば「Microsoft.Cpp.x64.user.props」が対応します。インストールされた直後はほとんど何も設定されていないので、まずは設定しやすいように(Microsoft.Cpp.Win32.user.propsを例にすると)

<?xml version="1.0" encoding="utf-8"?> 
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <ExecutablePath>$(ExecutablePath)</ExecutablePath>
    <IncludePath>$(IncludePath)</IncludePath>
    <ReferencePath>$(ReferencePath)</ReferencePath>
    <LibraryPath>$(LibraryPath)</LibraryPath>
    <SourcePath>$(SourcePath)</SourcePath>
    <ExcludePath>$(ExcludePath)</ExcludePath>
  </PropertyGroup>
</Project>

というようにPropertyGroupで各ディレクトリ情報のデフォルトを設定した上で必要なパスを書き加えていく必要があります。SDKをインストールした場合はIncludePathおよびLibraryPathにそのパス情報を追加すればC++系のどのプロジェクトでもライブラリが参照されるようになります。注意が必要なのはExecutablePathに追加する場合にはとても注意が必要である、ということです。この話は次の項で。

 

Trackerのエラーって何だよ~

VS2010でコンパイルすると環境によってはTrackerという機構がエラーを返すことがあります。特にx86のビルド環境からプロジェクトを継承してx64のビルド環境を作成し、IDEでビルドを行うとTRK0002を出したりすることがあって大変。どうも環境変数の引き継ぎなどでパスが狂うとこんなことになるようでこの辺はほかのサイトでもいろいろと書いてあるので詳しくはそちらに譲ります。で、私の場合はほかのサイトに書いてあった原因ではなく、なんとSDKの実行ファイルを参照するためにExecutablePathに追加してあった(しかもデフォルトの環境変数より前)ためにTRK0002エラーが発生するという何ともよくわからない原因でした。結局実行ファイル群はデフォルトの環境変数の後ろに回してとりあえず一段落。

 

古い環境を移行していく場合は最新SDKのライブラリやインクルードファイルが使えないことも

で、次にはまった項目がこちら。最新のWindowsSDKにはDirectShowのライブラリ群であるbaseclassesが同梱されていません。というか、そもそもサンプルファイルがインストールされないのでネットワーク上からとってこないとだめなのですが、そのサンプル群にbaseclassesがないので撃沈です。

仕方が無いので古いSDK(WindowsSDK for 7)からbaseclassesを取り出してコンパイル、そしてそのライブラリを使おうと思ったのですがここで第二弾が。通常baseclassesを使うときはbaseclassesのディレクトリをデフォルトのインクルードディレクトリに追加するのですが、なんと最新のWindowsSDKが持っているインクルードファイルとbaseclassesのインクルードファイルのファイル名が同じであるために優先順位によっては間違えたファイルをインクルードすることに。これを回避するためには優先順位を変更するかbaseclasses側のコードを変更して読み込まれるインクルードファイルを変更するか、となると思います。

最後に待っていた第三弾は最新のWindowsSDKの情報を追加した状態でライブラリを構築したはずなのですがライブラリの衝突か何かよくわかりませんが、必要な関数がライブラリにないためにビルドができない、という現象が。DirectShowが参照しているvsnwprintfを最新のWindowsSDKが定義していないらしく大変な状態となってしまいました。結局このままだとどうしようもないので今の段階では最新のWIndowsSDKを使わないように設定して構築したライブラリを使うことにしています。

ちなみに、最新のWindowsSDKでXAudio2のコードをコンパイルしようとすると「このライブラリを使うとWindows8以降でないと動作しないコードが作成されます。それ以前で動作させるためにはDirectXSDKのライブラリを使用してください」とのメッセージ(注:意訳です)が出ますのでこれも効いていたりします。

 

VS2010以降でライブラリを構築するときに依存性を設定しても自動でリンクされない

というわけでです。はい。ライブラリを構築していて依存するライブラリを自分の中に含めようとするときにVS2008までだとプロジェクトに依存性を持たせるだけで自動的にリンクされたのですが、VS2010以降だと依存性を持たせるだけでは自動的にリンクしてくれません。依存性を持たせると同時にプロジェクトの設定から[構成プロパティ]=>[ライブラリアン]=>[全般]=>[ライブラリ依存関係のリンク]を「はい」にする必要があります。ちなみに「依存性を持たせる」にもちょっとしたトリックがあり、単に依存性のチェックをしただけではリンクしてくれないこともあります。この場合はプロジェクトの設定から[共通プロパティ]=>[Frameworkと参照]で依存するプロジェクトを参照として追加する必要があります。

 

そしてsetのiteratorの仕様が変わったのね

正しくいうなら「あまりよくない仕様だったものが直された」というべきでしょうか。VS2010になってsetのiteratorの扱いがconst_iteratorと同じになっています。そのため、iterator経由であってもデータの書き換えができない、という状態になっています。確かにデータの書き換えができると二分木が保てなくなるのでその変更は正しいのですが、使い方によってはかなり影響がある変更です。たとえばこんな感じで使っているとちょっとやばい。

class CItem{
public:
	CItem(int no,string value) : _no(no), _value(value) { }
	~CItem() { }

	const string &getvalue(void) const { return _value; }
	void setvalue(const string &value){ _value = value; }

	bool operator < (const CItem &item){ return _no < item._no; }

private:
	int _no;
	string _value;
};

set<CItem> setitem; set::iterator it;

CItem val(1,"first");
setitem.insert(val);

it = setitem.find(val);
it->setvalue("second");

かなりいい加減なコードですが、こんな感じです。アイテムの集合体としてsetによる検索を有効にしておいて見つかったアイテムに対して(添え字を変えずに)アイテムの値を変える、というやつです。一応mapでもできますが、番号とデータが分離してしまうと扱いにくいというのもありsetでやっていたわけですが・・・。これができなくなるわけです。

回避する方法としては・・・。まずはsetではなくmapの使用を検討してみる、というところでしょう。ただ、添え字がデータがから分離するのでそれによって整合性が崩れないかどうか、を考える必要があります。そして緊急回避的な処置としてはconst_castをかませることで逃れる、ですね。たとえば例のコードで最後の部分を

const_cast<CItem &>(*it).setvalue("second");

const_cast<CItem *>(&(*it))->setvalue("second");

とすれば何とかなります。どちらにしてもまあ微妙ですか。

 

とりあえずは何とかなった

Win8への移行を開始してからかなり期間をかけましたが、なんとか安定して動作させる環境を構築できました。なお、どうでもよくない話としてAdvancedCodecsにやばいマルウェアが同梱されるようになった、という話を聞いたので今回はAdvancedCodecsに頼ることなく別のコーデックをいれて対応しています。さて、ここからどうしましょうかね・・・。まずはWindowModePatchの修正を続行するところからでしょうか。

 

DirectShowを使う 記事まとめ

MediaFoundationで記事まとめをやったのにDirectShowでやらないのは微妙だと思うのでこちらもまとめておきます。

平気で三年前の記事とか混じっていますが、気にしないでください。

  • DirectShowを使う
  • 番外編(IStream実装編)
  • VMR9編
  • CBaseVideoRenderer編
  • ソースフィルタ編

いろいろなジャンルでやっているものだな・・・

こうやって並べてみるとよくわかります。基本的な使い方が少ないのが問題点でしょうか。

まあ、COMを自分で実装して使うというのはDirectShowではよくあることなのでこういう話が多くなるのは仕方がないのかもしれませんが。

記事を連続させるのに苦労している・・・

コードなんて書いてもよほどなことがない限りあまりblogの記事として意味がないのはわかっているつもりです。

まあ、サンプルコード的なものですので仕事やレポートなどで困ったときに参考にしてもらえれば、という気持ちです。

じわじわと効いてくれば、という思いでもあります。

ちなみに「こんなにコードを書いていると記事が無くなるのでは?」と思うかもしれませんが、それはありません。

(完全に動く)ADVシステムを一つ組んでいるのでそれのコードを部分的に取り出してサンプル形式で書いているだけです。

今の部分も出しても問題になるようなものでもないですし、本当に解説しながらコードを提示すると毎日書いてもたぶん三年くらいは持つと思います。

古くはDirectDrawからDirect3D、補助コード、ライブラリの使用方法、自作のスクリプト言語のコンパイラ、スクリプトエンジン、ADVシステム・・・。

まあ、事実上そのコードが使われないコードとなる可能性が高いのでこんな出し方をしています。

やるならどこかのサイトや雑誌で連載をやらせてもらいたいですし、自前でやるにもVPS+blogシステム(WordPressあたり?)+Adsenseで運営したいところですね。

DirectShowを使った自前ソースフィルタの実装 その4

ソースフィルタ編はこれが最後です。

前に実装していなかったIDataStreamをWinAPI系で実装してみたりフィルタを微妙な拡張をしてみたり、使用するコードを出してみたり。

 

追加するコードの宣言部

//アーカイブファイル読み込みクラス
class CFileStream : public IDataStream{
public:
	CFileStream(); //コンストラクタ
	virtual ~CFileStream(); //デストラクタ
	virtual HRESULT GetMediaType(CMediaType *lpMediaType,const AM_MEDIA_TYPE *lpDefaultType); //メディアの種類を取得する
	virtual HRESULT Seek(LONGLONG llPosition); //読み取り位置の移動
	virtual HRESULT Read(void *lpBuffer,DWORD dwToReadSize,DWORD *lpdwReadSize,BOOL bIsAligned); //現在の位置からデータを読み取る
	virtual BOOL GetSize(LONGLONG *lpllTotalSize,LONGLONG *lpllAvailableSize = NULL) const; //データのサイズを取得する
	virtual DWORD GetAlignment(void) const; //データにアライメントあわせの必要があるとき、そのサイズを返す
	virtual void Lock(void); //データを読み取るためにロックする
	virtual void Unlock(void); //データのロックを解除する
	BOOL SetFileHandle(const WCHAR *lpwFileName,HANDLE hFile); //ハンドルを設定する
private:
	HANDLE m_hFile; //ファイルハンドル
	WCHAR m_wszFileNameExt[16]; //ファイル拡張子
	CCriticalSection m_vReadCriticalSection; //読み込みクリティカルセクション
};
//データストリームを使ったロードクラス
class CFileStreamFilter : public CDataSourceReader, public IFileSourceFilter{
public:
	CFileStreamFilter(IUnknown *lpOwner,HRESULT *lphr); //コンストラクタ
	virtual ~CFileStreamFilter(); //デストラクタ
	DECLARE_IUNKNOWN;
	STDMETHODIMP NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface); //QueryInterfaceの実装
	//IFileSourceFilterよりの継承
	STDMETHODIMP Load(LPCOLESTR lpwFileName,const AM_MEDIA_TYPE *lpMediaType); //ファイルを開く
	STDMETHODIMP GetCurFile(LPOLESTR *lplpwFileName,AM_MEDIA_TYPE *lpMediaType); //現在開いているファイル名を取得する
private:
	HANDLE m_hFile; //ファイルハンドル
	WCHAR *m_lpwFileName; //ファイル名
	CFileStream m_vFileStream; //データストリーム
};

フィルタについてはIFileSourceFilterを追加しています。実装しなくても実はいいのですが、これを実装しておくと

  • ファイルを開く関数を実装するのでそれを使うことができる
  • GraphEditなどでフィルタを見るとロードしたファイル名が表示される

という利点があるので上から実装しています。

 

追加実装コード

まずはCFileStreamから。

//拡張子とGUIDの関連づけ
typedef struct _ExtMediaType{
	WCHAR wszExtName[8]; //拡張子
	GUID guidMediaType; //メディアタイプ
} EXTMEDIATYPE;
//拡張子との関連づけ
static const EXTMEDIATYPE c_avExtMediaType[] = {
	{ L"mpg", MEDIASUBTYPE_MPEG1System },
	{ L"mpeg", MEDIASUBTYPE_MPEG1System },
	{ L"wma", MEDIASUBTYPE_Asf },
	{ L"wmv", MEDIASUBTYPE_Asf },
	{ L"avi", MEDIASUBTYPE_Avi },
};
//ファイルストリーム読み込み
//コンストラクタ
CFileStream::CFileStream() : m_hFile(NULL)
{
	memset(&m_wszFileNameExt,0x00,sizeof(m_wszFileNameExt));
}
//デストラクタ
CFileStream::~CFileStream()
{
}
//メディアの種類を取得する
HRESULT CFileStream::GetMediaType(CMediaType *lpMediaType,const AM_MEDIA_TYPE *lpDefaultType)
{
	const EXTMEDIATYPE *lpMedia; const WCHAR *lpwFileExt;
				
	if(lpDefaultType != NULL) return lpMediaType->Set(*lpDefaultType);
	lpMediaType->SetType(&MEDIATYPE_Stream);
	lpMediaType->SetSubtype(&MEDIASUBTYPE_NULL);
	lpMediaType->SetTemporalCompression(TRUE);
	lpMediaType->SetSampleSize(1);
	lpwFileExt = m_wszFileNameExt;
	if(*lpwFileExt == L'\0') return S_OK;
	for(lpMedia = c_avExtMediaType;lpMedia->wszExtName[0] != L'\0';lpMedia++){
		if(lstrcmpiW(lpMedia->wszExtName,lpwFileExt) == 0){
			lpMediaType->SetSubtype(&(lpMedia->guidMediaType));
			break;
		}
	}
	return S_OK;
}
//読み取り位置の移動
HRESULT CFileStream::Seek(LONGLONG llPosition)
{
	CheckPointer(m_hFile,E_FAIL);
	BOOL bRet = ::SetFilePointerEx(m_hFile,*(LARGE_INTEGER *)&llPosition,NULL,FILE_BEGIN);
	return bRet ? S_OK : E_FAIL;
}
//現在の位置からデータを読み取る
HRESULT CFileStream::Read(void *lpBuffer,DWORD dwToReadSize,DWORD *lpdwReadSize,BOOL bIsAligned)
{
	CheckPointer(m_hFile,E_FAIL);
	BOOL bRet = ::ReadFile(m_hFile,lpBuffer,dwToReadSize,lpdwReadSize,NULL);
	return bRet ? S_OK : E_FAIL;
}
//データのサイズを取得する
BOOL CFileStream::GetSize(LONGLONG *lpllTotalSize,LONGLONG *lpllAvailableSize) const
{
	if(lpllTotalSize == NULL || m_hFile == NULL) return FALSE;
	::GetFileSizeEx(m_hFile,(LARGE_INTEGER *)lpllTotalSize);
	if(lpllAvailableSize != NULL) *lpllAvailableSize = *lpllTotalSize;
	return TRUE;
}
//データにアライメントあわせの必要があるとき、そのサイズを返す
DWORD CFileStream::GetAlignment(void) const
{
	return 1;
}
//データを読み取るためにロックする
void CFileStream::Lock(void)
{
	m_vReadCriticalSection.Enter();
}
//データのロックを解除する
void CFileStream::Unlock(void)
{
	m_vReadCriticalSection.Leave();
}
//ハンドルを設定する
BOOL CFileStream::SetFileHandle(const WCHAR *lpwFileName,HANDLE hFile)
{
	int i;
	m_hFile = hFile;
	for(i = lstrlenW(lpwFileName) - 1;i >= 0;i--){ if(lpwFileName[i] == L'.'){ break; } }
	if(i > 0) lstrcpynW(m_wszFileNameExt,lpwFileName + i + 1,sizeof(m_wszFileNameExt) / sizeof(WCHAR));
	return TRUE;
}

宣言部で書いたとおり、LockやらUnlockは持っていますが実動作で非同期処理を意識する必要はないのでそのまま呼び出しています。

面倒なのがGetMediaTypeですね。できる限りsubtypeは設定して置いた方がいいので拡張子からGUIDを判別してそれを設定しています。

拡張子検知の方は今回はUNICODEなので普通に逆方向から調べたものを使っています。

 

で、拡張されたソースフィルタのコードはというと。

//ファイルストリームフィルタ
//コンストラクタ
CFileStreamFilter::CFileStreamFilter(IUnknown *lpOwner,HRESULT *lphr)
	: CDataSourceReader(NAME("FileStreamFilter"),lpOwner,lphr,&m_vFileStream), m_hFile(INVALID_HANDLE_VALUE), m_lpwFileName(NULL)
{
	if(lphr != NULL) *lphr = S_OK;
}
//デストラクタ
CFileStreamFilter::~CFileStreamFilter()
{
	if(m_lpwFileName != NULL){
		delete[] m_lpwFileName;
		m_lpwFileName = NULL;
	}
	if(m_hFile != INVALID_HANDLE_VALUE){
		::CloseHandle(m_hFile);
		m_hFile = INVALID_HANDLE_VALUE;
	}
}
//QueryInterfaceの実装
STDMETHODIMP CFileStreamFilter::NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface)
{
	if(IsEqualIID(refiid,IID_IFileSourceFilter)) return GetInterface(static_cast(this),lplpInterface);
	else return CDataSourceReader::NonDelegatingQueryInterface(refiid,lplpInterface);
}
//ファイルを開く
STDMETHODIMP CFileStreamFilter::Load(LPCOLESTR lpwFileName,const AM_MEDIA_TYPE *lpMediaType)
{
	int nLength;
	CheckPointer(lpwFileName,E_POINTER);
	if(m_lpwFileName != NULL) return E_FAIL;
	
	nLength = lstrlenW(lpwFileName) + 2; m_lpwFileName = new WCHAR[nLength]; lstrcpynW(m_lpwFileName,lpwFileName,nLength);
	{
		CAutoLock lock(&m_vCSFilter);
		m_hFile = ::CreaetFileW(m_lpwFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
		if(m_hFile == INVALID_HANDLE_VALUE){ return E_INVALIDARG; }
		m_vFileStream.SetFileHandle(m_lpwFileName,m_hFile);
		m_vFileStream.GetMediaType(&m_vMediaType,lpMediaType);
		m_vMediaType = m_vMediaType;
	}
	return S_OK;
}
//現在開いているファイル名を取得する
STDMETHODIMP CFileStreamFilter::GetCurFile(LPOLESTR *lplpwFileName,AM_MEDIA_TYPE *lpMediaType)
{
	int nNameLength; LPOLESTR lpwFileName;
	
	CheckPointer(m_hFile,E_FAIL); CheckPointer(lplpwFileName,E_POINTER);
	nNameLength = lstrlenW(m_lpwFileName) + 1;
	lpwFileName = (LPOLESTR)CoTaskMemAlloc(nNameLength * sizeof(WCHAR));
	if(lpwFileName == NULL) return E_OUTOFMEMORY;
	memcpy(lpwFileName,m_lpwFileName,nNameLength * sizeof(WCHAR)); *lplpwFileName = lpwFileName;
	
	if(lpMediaType != NULL){
		HRESULT hr = CopyMediaType(lpMediaType,&m_vMediaType);
		if(hr != S_OK) return hr;
	}
	return S_OK;
}

IDataStreamをこのフィルタ内に所有してしまったわけですね。おかげで開く動作がコンストラクタ上でやりづらくなってしまっている、と。

あと、IFileSourceFilterを実装するためにQueryInterface、Load、GetCurFileをそれぞれ実装しています。

ファイル名として渡されるLPOLESTR(LPCOLESTR)は「メモリ確保時にCoTaskMemAllocを使っているUNICODE文字列」だと思えばいいです。

(constがつく場合は単にUNICODE文字列としていいのだが)

それさえわかっていれば動作は簡単ですね。stringを使っていないのでメモリを確保して・・・が面倒です。

 

そしてIGraphBuilderでロードするコードに一手間

かな~り古いですが、DirectShowを使う(1)より使用ポイントを言うなら

//ファイル名を指定してグラフを生成する
hr = m_lpGraphBuilder->RenderFile(lpwFileName,NULL);

の部分をこのように変更します。

do{
	CFileStreamFilter *lpStreamFilter; IBaseFilter *lpBaseFilter;
	lpStreamFilter = new CFileStreamFilter(NULL,&hr);
	if(FAILED(hr)){ delete lpStreamFilter; break; }
	hr = lpStreamFilter->QueryInterface(IID_IBaseFilter,&lpBaseFilter);
	if(FAILED(hr)){ delete lpStreamFilter; break; }
	hr = m_lpGraphBuilder->AddFilter(lpBaseFilter,L"File Stream Filter");
	if(FAILED(hr)){ delete lpStreamFilter; break; }
	hr = lpStreamFilter->Load(lpwFileName,NULL);
	if(FAILED(hr)) break;
	hr = m_lpGraphBuilder->Render(lpStreamFilter->GetPin(0));
} while(0);

手順としては

  1. CFileStreamFilterをクラスとして作成
  2. QueryInterfaceでIBaseFilterを取得
  3. IGraphBuilderにAddFilterでフィルタを追加
  4. IFileSourceFilterのLoadで読み込み
  5. IGraphBuilderのRenderで出力ピンから先をレンダリング

となります。RenderFileを使うときに比べると面倒ですね。

また、コード中でdeleteで解放しているパターンとしていないパターンが混じっていますが、これは参照状態の問題です。

newで作成された時点では参照カウントは0なので解放はReleaseではできません。

また、AddFilterが成功した時点では参照カウントは1となりまたGraphBuilderに参照されている状態となるため、GraphBuilderの解放とともに解放されるようになります。

そのため、失敗しても解放の指示をGraphBuilderに任せることになるからです。

 

というわけで一連の流れでした

見てわかるとおり自前の読み込み処理を提示するならMediaFoundationの方がDirectShowより簡単です。

それ以外はそうでもないのですが、ちょっとおもしろい現象でした。

その原因はDirectShowの動作構造が「フィルタ」と「ピン」で汎用的に書かれているためですね。

いろいろな面で使いやすいのは事実ですが。

DirectShowを使った自前ソースフィルタの実装 その3

今回は実装編その2です。自前でDirectShowのフィルタを実装するときの注意点などを見ていきます。

前にDirectShowのフィルタを実装したときと違い、CBasePinやらCBaseFilterやらとbaseclassesの基本オブジェクトを使って実装しているのでこちらで実装しなければならない動作が多いです。

その辺に注意しながら見ていきます。

ソースフィルタの出力ピンの実装部

まずはピンの基本的処理から。

//出力ピン
//コンストラクタ
CDataSourceOutputPin::CDataSourceOutputPin(HRESULT *lphr,CDataSourceReader *lpSourceReader,CDataAsyncReader *lpAsyncReader,CCritSec *lpLockCritical)
	: CBasePin(NAME("DataSourceOutputPin"),lpSourceReader,lpLockCritical,lphr,L"SourceOutput",PINDIR_OUTPUT),
	  m_lpSourceReader(lpSourceReader), m_lpAsyncReader(lpAsyncReader), m_bIsQueriedForAsyncReader(FALSE)
{
}
//デストラクタ
CDataSourceOutputPin::~CDataSourceOutputPin()
{
}
//QueryInterfaceの実装
STDMETHODIMP CDataSourceOutputPin::NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface)
{
	if(IsEqualGUID(refiid,IID_IAsyncReader)){ m_bIsQueriedForAsyncReader = TRUE; return GetInterface(static_cast<IAsyncReader *>(this),lplpInterface); }
	return CBasePin::NonDelegatingQueryInterface(refiid,lplpInterface);
}
//ピンを接続する
STDMETHODIMP CDataSourceOutputPin::Connect(IPin *lpReceivePin,const AM_MEDIA_TYPE *lpMediaType)
{
	return m_lpSourceReader->Connect(lpReceivePin,lpMediaType);
}
//CBasePinを使ってピンを接続する
HRESULT CDataSourceOutputPin::ConnectOnBasePin(IPin *lpReceivePin,const AM_MEDIA_TYPE *lpMediaType)
{
	return CBasePin::Connect(lpReceivePin,lpMediaType);
}
//このピンから出力されるメディアタイプを取得する
HRESULT CDataSourceOutputPin::GetMediaType(int nPosition,CMediaType *lpMediaType)
{
	CheckPointer(lpMediaType,E_POINTER);
	if(nPosition < 0){ return E_INVALIDARG; } else if(nPosition > 0){ return VFW_S_NO_MORE_ITEMS; }
	CheckPointer(m_lpSourceReader,E_UNEXPECTED);
	*lpMediaType = *m_lpSourceReader->GetMediaType();
	return S_OK;
}
//指定されたメディアタイプがサポートされているかどうかをチェックする
HRESULT CDataSourceOutputPin::CheckMediaType(const CMediaType *lpMediaType)
{
	const CMediaType *lpSourceMediaType = m_lpSourceReader->GetMediaType();
	if(!IsEqualGUID(lpSourceMediaType->majortype,lpMediaType->majortype)) return VFW_E_TYPE_NOT_ACCEPTED;
	if(IsEqualGUID(lpSourceMediaType->subtype,lpMediaType->subtype)) return S_OK;
	if(IsEqualGUID(lpSourceMediaType->subtype,MEDIASUBTYPE_NULL)) return S_OK;
	return VFW_E_TYPE_NOT_ACCEPTED;
}
//指定されたピント適切に接続されているかどうか
HRESULT CDataSourceOutputPin::CheckConnect(IPin *lpPin)
{
	m_bIsQueriedForAsyncReader = FALSE;
	return CBasePin::CheckConnect(lpPin);
}
//ピンへの接続を確立する
HRESULT CDataSourceOutputPin::CompleteConnect(IPin *lpReceivePin)
{
	if(m_bIsQueriedForAsyncReader) return CBasePin::CompleteConnect(lpReceivePin);
	else
#ifdef VFW_E_NO_TRANSPORT
		return VFW_E_NO_TRANSPORT;
#else
		return E_FAIL;
#endif
}
//ピンへの接続を解放する
HRESULT CDataSourceOutputPin::BreakConnect(void)
{
	m_bIsQueriedForAsyncReader = FALSE;
	return CBasePin::BreakConnect();
}

基本的な処理はCBasePinに実装されているのでそちらに回しているものも多いです。

コンストラクタがちょっと長いですが、引数の役目にあわせて渡せば問題はありません。

こちらで任意に設定できるのは第一引数のpObjectNameおよび第五引数のpNameです。pNameはGraphEditなどでピンの名前として表示されますので設定しておくといいです。

QueryInterfaceではIAsyncReaderが追加されるのでそれを返しています。

またConnect処理が微妙な遠回りとなっています。Connect時に一度フィルタに投げているのでそうするとCBasePinがもつConnect処理が継承でつぶされてしまうのでそこを別の関数で補っています。

次にメモリアロケータを。

//メモリ確保インターフェイスを初期化する
HRESULT CDataSourceOutputPin::InitializeAllocator(IMemAllocator **lplpAllocator)
{
	CMemAllocator *lpMemoryAllocator; HRESULT hr;
	CheckPointer(lplpAllocator,E_POINTER);
	*lplpAllocator = NULL; lpMemoryAllocator = NULL; hr = S_OK;
	lpMemoryAllocator = new CMemAllocator(NAME("BaseMemoryAllocator"),NULL,&hr);
	if(lpMemoryAllocator == NULL) return E_OUTOFMEMORY;
	if(hr != S_OK){ delete lpMemoryAllocator; return hr; }
	hr = lpMemoryAllocator->QueryInterface(IID_PPV_ARGS(lplpAllocator));
	if(hr != S_OK){ delete lpMemoryAllocator; return E_NOINTERFACE; }
	if(*lplpAllocator == NULL){ delete lpMemoryAllocator; return E_FAIL; }
	return S_OK;
}
//メモリ確保インターフェイスを取得する
STDMETHODIMP CDataSourceOutputPin::RequestAllocator(IMemAllocator *lpPreferred,ALLOCATOR_PROPERTIES *lpProperties,IMemAllocator **lplpActualAllocator)
{
	ALLOCATOR_PROPERTIES ActualProperties,RequestProperties; IMemAllocator *lpMemoryAllocator; HRESULT hr;
	CheckPointer(lpProperties,E_POINTER); CheckPointer(lplpActualAllocator,E_POINTER);
	ActualProperties = RequestProperties = *lpProperties;
	if(RequestProperties.cbAlign == 0 || !m_lpAsyncReader->IsAligned(RequestProperties.cbAlign)){
		m_lpAsyncReader->Alignment(&(RequestProperties.cbAlign));
	}
	
	if(lpPreferred != NULL){
		hr = lpPreferred->SetProperties(&RequestProperties,&ActualProperties);
		if(hr == S_OK && m_lpAsyncReader->IsAligned(ActualProperties.cbAlign)){
			lpPreferred->AddRef();
			*lplpActualAllocator = lpPreferred;
			return S_OK;
		}
	}
	hr = InitializeAllocator(&lpMemoryAllocator);
	if(hr != S_OK) return hr;
	hr = lpMemoryAllocator->SetProperties(&RequestProperties,&ActualProperties);
	if(hr == S_OK && m_lpAsyncReader->IsAligned(ActualProperties.cbAlign)){
		*lplpActualAllocator = lpMemoryAllocator;
		return S_OK;
	}
	lpMemoryAllocator->Release();
	if(hr == S_OK) hr = VFW_E_BADALIGN;
	return hr;
}

基本的には接続先から使ってほしいアロケータが投げられるので使用可能かどうか判定して使えるようならそちらを使います。

使える要件はメモリのアライメントが非同期読み取りのアライメント調整とあっているかどうかです。失敗すればbaseclassesが持っているCMemAllocatorが登場するというものです。

最後にIAsyncReaderの実装です。RequestAllocatorもIAsyncReaderに必要ですが、残りの方が重要です。

//リクエストキューにデータ要求を入れる
STDMETHODIMP CDataSourceOutputPin::Request(IMediaSample *lpSample,DWORD_PTR dwUser)
{
	REFERENCE_TIME tStart,tStop;
	LONGLONG llPos,llTotalSize,llAvailableSize;
	long lLength,lAlignment; BYTE *lpBuffer; HRESULT hr;
	CheckPointer(lpSample,E_POINTER);
	hr = lpSample->GetTime(&tStart,&tStop);
	if(hr != S_OK) return hr;
	llPos = tStart / UNITS; lLength = (long)((tStop - tStart) / UNITS); llTotalSize = llAvailableSize = 0;
	m_lpAsyncReader->Length(&llTotalSize,&llAvailableSize);
	if(llPos + (LONGLONG)lLength > llTotalSize){
		m_lpAsyncReader->Alignment(&lAlignment);
		llTotalSize = (llTotalSize + (LONGLONG)lAlignment - 1) & ~((LONGLONG)(lAlignment - 1));
		if(llPos + (LONGLONG)lLength > llTotalSize){
			lLength = (long)(llTotalSize - llPos); tStop = llTotalSize * UNITS;
			lpSample->SetTime(&tStart,&tStop);
		}
	}
	hr = lpSample->GetPointer(&lpBuffer);
	if(hr != S_OK) return hr;
	return m_lpAsyncReader->Request(llPos,lLength,TRUE,lpBuffer,lpSample,dwUser);
}
//次のデータ読み取りまで待機する
STDMETHODIMP CDataSourceOutputPin::WaitForNext(DWORD dwTimeOut,IMediaSample **lplpSample,DWORD_PTR *lpdwUser)
{
	long lActualSize; HRESULT hr;
	CheckPointer(lplpSample,E_POINTER); CheckPointer(lpdwUser,E_POINTER);
	hr = m_lpAsyncReader->WaitForNext(dwTimeOut,(void **)lplpSample,lpdwUser,&lActualSize);
	if(SUCCEEDED(hr)) (*lplpSample)->SetActualDataLength(lActualSize);
	return hr;
}
//アライメントを合わせた同期読み取りを行う
STDMETHODIMP CDataSourceOutputPin::SyncReadAligned(IMediaSample *lpSample)
{
	REFERENCE_TIME tStart,tStop; LONGLONG llPos,llTotalSize,llAvailableSize;
	long lLength,lAlignment,lActualSize; BYTE *lpBuffer; HRESULT hr;
	CheckPointer(lpSample,E_POINTER);
	hr = lpSample->GetTime(&tStart,&tStop);
	if(hr != S_OK) return hr;
	llPos = tStart / UNITS; lLength = (long)((tStop - tStart) / UNITS); llTotalSize = llAvailableSize = 0;
	m_lpAsyncReader->Length(&llTotalSize,&llAvailableSize);
	if(llPos + (LONGLONG)lLength > llTotalSize){
		m_lpAsyncReader->Alignment(&lAlignment);
		llTotalSize = (llTotalSize + (LONGLONG)lAlignment - 1) & ~((LONGLONG)(lAlignment - 1));
		if(llPos + (LONGLONG)lLength > llTotalSize){
			lLength = (long)(llTotalSize - llPos); tStop = llTotalSize * UNITS;
			lpSample->SetTime(&tStart,&tStop); lpSample->SetMediaTime(&tStart,&tStop);
		}
	}
	hr = lpSample->GetPointer(&lpBuffer);
	if(hr != S_OK) return hr;
	hr = m_lpAsyncReader->SyncReadAligned(llPos,lLength,lpBuffer,&lActualSize,lpSample);
	lpSample->SetActualDataLength(lActualSize);
	return hr;
}
//同期読み取りを行う
STDMETHODIMP CDataSourceOutputPin::SyncRead(LONGLONG llPosition,long lLength,BYTE *lpBuffer)
{
	return m_lpAsyncReader->SyncRead(llPosition,lLength,lpBuffer);
}
//メディアストリームの長さを取得する
STDMETHODIMP CDataSourceOutputPin::Length(LONGLONG *lpllTotalLength,LONGLONG *lpllAvailableLength)
{
	return m_lpAsyncReader->Length(lpllTotalLength,lpllAvailableLength);
}
//フラッシュ処理を開始する
STDMETHODIMP CDataSourceOutputPin::BeginFlush(void)
{
	return m_lpAsyncReader->BeginFlush();
}
//フラッシュ処理を終了する
STDMETHODIMP CDataSourceOutputPin::EndFlush(void)
{
	return m_lpAsyncReader->EndFlush();
}

ほとんどは先に実装したIAsyncReaderの機能を受け持つクラスに動作を投げています。

しかもよく見たらRequestとSyncReadAlignedでIMediaSampleに対して行っている処理コードってほぼ同じですよね・・・さすがに古いコード・・・。

なお、IMediaSampleから取得できるタイムコードとサンプルの位置関係についてはコードの通りです。

なぜそうなるかは「仕様です」の一言でしかありません。時間とストリーム位置が何でこんな関係なのか・・・

後はフィルタに関するコード

//GUIDの定義
class __declspec(uuid("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}")) CDataSourceReader;
//データリーダ
//コンストラクタ
CDataSourceReader::CDataSourceReader(const TCHAR *lpFilterName,IUnknown *lpOwner,HRESULT *lphr,IDataStream *lpDataStream)
	: CBaseFilter(lpFilterName,lpOwner,&m_vCSFilter,__uuidof(CDataSourceReader)), m_lpOutputPin(NULL), m_lpAsyncReader(NULL)
{
	m_lpAsyncReader = new CDataAsyncReader(lpDataStream);
	m_lpOutputPin = new CDataSourceOutputPin(lphr,this,m_lpAsyncReader,&m_vCSFilter);
}
//デストラクタ
CDataSourceReader::~CDataSourceReader()
{
	delete m_lpOutputPin;
	delete m_lpAsyncReader;
}
//接続できるピンの数を取得する
int CDataSourceReader::GetPinCount()
{
	return 1;
}
//ピンを取得する
CBasePin *CDataSourceReader::GetPin(int n)
{
	CBasePin *lpPin;
	switch(n){
	case 0: lpPin = m_lpOutputPin; break;
	default: lpPin = NULL;
	}
	return lpPin;
}
//保持しているメディア形式を取得する
const CMediaType *CDataSourceReader::GetMediaType() const
{
	return
}
//ピンを接続する
HRESULT CDataSourceReader::Connect(IPin *lpReceivePin,const AM_MEDIA_TYPE *lpMediaType)
{
	return m_lpOutputPin->ConnectOnBasePin(lpReceivePin,lpMediaType);
}

GUIDについては自分で設定してください。

非同期読み取りサポートクラスはnew~deleteは当然ですが、出力ピンまでnew~deleteになっています。

これがCBasePinとCBaseFilterの関係性になるので注意です。

CBasePinの所有者としてCBaseFilterを渡してCBasePinのコンストラクタを動かしますが、この時点でピンは参照カウントを自身で保持しなくなります。

このあたりのコードはbaseclassesのCUnknownの実装を参考にしてほしいですが、ピン側にAddRefやReleaseなどの参照カウント処理を行うとそれはフィルタ側の参照カウント処理になる、という動作になります。

参照カウントがフィルタ側だけで処理されるのでピンとフィルタのオブジェクト寿命が一緒になる、というわけです。

その代わり、ピンのメモリ解放はReleaseではできず(ピンのReleaseを行った場合はフィルタのReleaseが呼び出されるのと同じになるため)deleteが必要になります。

また、CBaseFilterにはあるのにインターフェイス側のIBaseFilterにない処理としてGetPinCountおよびGetPinがあります。

これはCBaseFilterでは純粋仮想メソッドとして定義されているため実装は必須となります。

ちなみにこれら(+IncrementPinVersion)は何をするのかというとIEnumPinsというピンを列挙するインターフェイスを実装するための内部処理を担当しています。

IEnumPinsを実装するのは手間なのでこの関数群で処理を簡単に記述できるようになっている、という理屈です。

戻って言うならCBasePinのGetMediaTypeもこれの仲間(IEnumMediaTypesの内部実装)ですね。

あとは使用側ですか

とりあえず使用側のコードを書いて今回は終了ですね。

DirectShowを使った自前ソースフィルタの実装 その2

今回は実装編その1です。IAsyncReaderの動作部分を実装してみます。

宣言部については前回を参考に。

まずは読み取りパケット部のコードから

//非同期読み取りアイテム
//コンストラクタ
CDataRequestItem::CDataRequestItem()
{
}
//コピーコンストラクタ
CDataRequestItem::CDataRequestItem(const CDataRequestItem &item)
	: m_lpAsyncReader(item.m_lpAsyncReader), m_lpDataStream(item.m_lpDataStream), m_lpBuffer(item.m_lpBuffer), m_lpContext(item.m_lpContext),
	  m_llReadPos(item.m_llReadPos), m_dwUser(item.m_dwUser), m_lLength(item.m_lLength), m_bIsAligned(item.m_bIsAligned), m_hr(item.m_hr)
{
}
//コピー演算子
CDataRequestItem &CDataRequestItem::operator = (const CDataRequestItem &item)
{
	if(&item != this){ memcpy(this,&item,sizeof(*this)); }
	return *this;
}
//アイテムを設定する
BOOL CDataRequestItem::SetItem(CDataAsyncReader *lpAsyncReader,IDataStream *lpDataStream,LONGLONG llReadPos,BOOL bIsAligned,long lLength,void *lpBuffer,void *lpContext,DWORD_PTR dwUser)
{
	m_lpAsyncReader = lpAsyncReader; m_lpDataStream = lpDataStream;
	m_llReadPos = llReadPos; m_bIsAligned = bIsAligned; m_lLength = lLength;
	m_lpBuffer = lpBuffer; m_lpContext = lpContext; m_dwUser = dwUser;
	m_hr = VFW_E_TIMEOUT;
	return TRUE;
}
//要求を完了する
HRESULT CDataRequestItem::CompleteRequest(void)
{
	DWORD dwReadSize;
	m_lpDataStream->Lock();
	m_hr = m_lpDataStream->Seek(m_llReadPos);
	if(m_hr == S_OK){
		m_hr = m_lpDataStream->Read(m_lpBuffer,m_lLength,&dwReadSize,m_bIsAligned);
		if(m_hr == OLE_S_FIRST){
			if(m_lpContext != NULL){
				((IMediaSample *)m_lpContext)->SetDiscontinuity(TRUE);
				m_hr = S_OK;
			}
		}
		if(SUCCEEDED(m_hr)){
			if(dwReadSize != (DWORD)m_lLength){
				m_lLength = dwReadSize;
				m_hr = S_FALSE;
			}
			else m_hr = S_OK;
		}
	}
	m_lpDataStream->Unlock();
	return m_hr;
}
//要求をキャンセルする
HRESULT CDataRequestItem::CancelRequest(void)
{
	return S_OK;
}

実際に動作をしているのはCompleteRequestだけです。

それ以外は単にデータコピーを行っていたりパケットとして内部データを設定したりするだけです。

CompleteRequestも非同期にデータを読み取るためにLock=>Seek=>Read=>Unlockという流れをとっているいつものものですし。

失敗したときにエラーコードを設定する必要があるのでそれだけ気をつけて。

次にIAsyncReaderを支えるためのクラスの基礎部分

//非同期読み取りインターフェイス
//コンストラクタ
CDataAsyncReader::CDataAsyncReader(IDataStream *lpDataStream) : m_lpDataStream(lpDataStream),
	m_vEventInvalidThread(TRUE), m_vEventSetWorkItem(TRUE), m_vEventSetWorkDone(TRUE), m_vEventThreadWorkEnd(TRUE),
	m_bIsFlushing(FALSE), m_bIsWaitingWorkEnd(FALSE), m_nItemExecCount(0), m_hAsyncReadThread(NULL)
{
}
//デストラクタ
CDataAsyncReader::~CDataAsyncReader()
{
	BeginFlush();
	DisableAsyncRead();
}
//非同期動作を有効にする
BOOL CDataAsyncReader::EnableAsyncRead(void)
{
	unsigned int uiThreadID;
	if(m_hAsyncReadThread != NULL) return TRUE;
	m_vEventInvalidThread.Reset();
	m_hAsyncReadThread = (HANDLE)_beginthreadex(NULL,0,&CDataAsyncReader::StartAsyncReadThread,this,0,&uiThreadID);
	if(m_hAsyncReadThread == NULL) return FALSE;
	return TRUE;
}
//非同期動作を無効にする
BOOL CDataAsyncReader::DisableAsyncRead(void)
{
	if(m_hAsyncReadThread == NULL) return TRUE;
	m_vEventInvalidThread.Set();
	WaitForSingleObject(m_hAsyncReadThread,INFINITE);
	CloseHandle(m_hAsyncReadThread);
	m_hAsyncReadThread = NULL;
	return TRUE;
}

コンストラクタ、デストラクタそのものはとても簡単です。データを初期化しているだけですし。

なお、止めるときにはFlushの開始(残っているパケットの破棄)=>非同期読み取りの停止という手順をとります。

非同期読み取りの開始と終了はスレッドの作成とスレッドの終了で表すことができます。

このあたりから各同期イベントが出てきますが、それぞれの簡単な役割説明は宣言部で行っていますので参考に。

IAsyncReaderの動作実装部

といっても継承ではなく同じ関数プロトタイプであり、機能が同じ、というだけですが・・・。

//要求を投げる
HRESULT CDataAsyncReader::Request(LONGLONG llPos,long lLength,BOOL bIsAligned,void *lpBuffer,void *lpContext,DWORD_PTR dwUser)
{
	CDataRequestItem item;
	BOOL bRet;
	
	if(bIsAligned){ if(!IsAligned(llPos) || !IsAligned((LONG_PTR)lLength) || !IsAligned((LONG_PTR)lpBuffer)) return VFW_E_BADALIGN; }
	bRet = item.SetItem(this,m_lpDataStream,llPos,bIsAligned,lLength,lpBuffer,lpContext,dwUser);
	if(bRet) return PutWorkItem(item);
	else return E_FAIL;
}
//次の要求が終わるまで待つ
HRESULT CDataAsyncReader::WaitForNext(DWORD dwTimeOut,void **lplpContext,DWORD_PTR *lpdwUser,long *lplActualSize)
{
	CDataRequestItem item;
	LONGLONG llTotalSize;
	HRESULT hr;
	CheckPointer(lplpContext,E_POINTER); CheckPointer(lpdwUser,E_POINTER); CheckPointer(lplActualSize,E_POINTER);
	*lplpContext = NULL;
	while(1){
		if(!m_vEventSetWorkDone.Wait(dwTimeOut)) return VFW_E_TIMEOUT;
		hr = GetDoneItem(item);
		if(hr == S_OK){
			hr = item.GetResult();
			if(hr == S_FALSE){
				m_lpDataStream->GetSize(&llTotalSize);
				if(item.GetStart() + item.GetLength() == llTotalSize){ hr = S_OK; }
				else hr = E_FAIL;
			}
			*lplpContext = item.GetContextData(); *lpdwUser = item.GetUserData(); *lplActualSize = item.GetLength();
			return hr;
		}
		else{
			CLockObject lock(this);
			if(m_bIsFlushing && !m_bIsWaitingWorkEnd) return VFW_E_WRONG_STATE;
		}
	}
	return E_FAIL;
}
//アライメントを合わせて同期で読み込む
HRESULT CDataAsyncReader::SyncReadAligned(LONGLONG llPos,long lLength,void *lpBuffer,long *lplActualSize,void *lpContext)
{
	CDataRequestItem item; HRESULT hr;
	if(!IsAligned(llPos) || !IsAligned((LONG_PTR)lLength) || !IsAligned((LONG_PTR)lpBuffer)) return VFW_E_BADALIGN;
	if(!item.SetItem(this,m_lpDataStream,llPos,TRUE,lLength,lpBuffer,lpContext,0)) return E_FAIL;
	hr = item.CompleteRequest(); *lplActualSize = item.GetLength();
	return hr;
}
//同期で読み込む
HRESULT CDataAsyncReader::SyncRead(LONGLONG llPos,long lLength,void *lpBuffer)
{
	CDataRequestItem item; long lTemp;
	if(!IsAligned(llPos) && !IsAligned((LONG_PTR)lLength) && !IsAligned((LONG_PTR)lpBuffer)) return SyncReadAligned(llPos,lLength,lpBuffer,&lTemp,NULL);
	if(!item.SetItem(this,m_lpDataStream,llPos,FALSE,lLength,lpBuffer,NULL,0)) return E_FAIL;
	return item.CompleteRequest();
}
//サイズを取得する
HRESULT CDataAsyncReader::Length(LONGLONG *lpllTotalLength,LONGLONG *lpllAvailableLength)
{
	CheckPointer(lpllTotalLength,E_POINTER);
	return m_lpDataStream->GetSize(lpllTotalLength,lpllAvailableLength) ? S_OK : E_FAIL;
}
//アライメントを合わせる
HRESULT CDataAsyncReader::Alignment(long *lplAlignment)
{
	CheckPointer(lplAlignment,E_POINTER);
	*lplAlignment = m_lpDataStream->GetAlignment();
	return S_OK;
}
//フラッシュ処理を開始する
HRESULT CDataAsyncReader::BeginFlush(void)
{
	CDataRequestItem item;
	{
		CLockObject lock(this);
		m_bIsFlushing = TRUE;
		while(!m_dequeWorkItem.empty()){
			GetWorkItem(item);
			item.CancelRequest();
			PutDoneItem(item);
		}
		if(m_nItemExecCount > 0) m_bIsWaitingWorkEnd = TRUE;
		else{
			m_vEventSetWorkDone.Set();
			return S_OK;
		}
	}
	while(1){
		m_vEventThreadWorkEnd.Wait(INFINITE);
		{
			CLockObject lock(this);
			if(m_nItemExecCount == 0){
				m_bIsWaitingWorkEnd = FALSE;
				m_vEventSetWorkDone.Set();
				break;
			}
		}
	}
	return S_OK;
}
//フラッシュ処理を終了する
HRESULT CDataAsyncReader::EndFlush(void)
{
	m_bIsFlushing = FALSE;
	if(!m_dequeDoneItem.empty()) m_vEventSetWorkDone.Set();
	else m_vEventSetWorkDone.Reset();
	return S_OK;
}
//アライメントがあっているかどうか
BOOL CDataAsyncReader::IsAligned(LONG_PTR lSize)
{
	return (lSize & (m_lpDataStream->GetAlignment() - 1)) == 0;
}
#ifndef _WIN64
//アライメントがあっているかどうか(LONGLONGバージョン)
BOOL CDataAsyncReader::IsAligned(LONGLONG llSize)
{
	return IsAligned((LONG_PTR)llSize);
}
#endif //_WIN64

基本的には作業スレッドに読み取り処理を渡しているだけです。

なお、同期読み取り要求に関してもこちらで読み取りを書くのが面倒なので一時的に状態保持パケットを生成してそちらに命令を投げているのがまた。

処理完了待ちについては完了するまで待機してアイテムを返すだけですし、フラッシュ処理についてはフラッシュフラグを入れてキューからアイテムを破棄する動作を行います。

非同期処理本体

unsigned int __stdcall CDataAsyncReader::StartAsyncReadThread(void *lpContext)
{
	unsigned long ulRet;
	ulRet = ((CDataAsyncReader *)lpContext)->ThreadProc();
	return (unsigned int)ulRet;
}
//スレッドルーチン
unsigned long CDataAsyncReader::ThreadProc(void)
{
	HANDLE ahWaitObject[2]; DWORD dwWaitRet;
	ahWaitObject[0] = m_vEventInvalidThread.GetHandle();
	ahWaitObject[1] = m_vEventSetWorkItem.GetHandle();
	while(1){
		dwWaitRet = WaitForMultipleObjects(2,ahWaitObject,FALSE,INFINITE);
		if(dwWaitRet == WAIT_OBJECT_0 + 1) ProcessWork();
		else break;
	}
	return 0;
}
BOOL CDataAsyncReader::ProcessWork(void)
{
	CDataRequestItem item;
	{
		CLockObject lock(this);
		if(GetWorkItem(item) != S_OK) return TRUE;
		m_nItemExecCount++;
	}
	item.CompleteRequest();
	{
		CLockObject lock(this);
		PutDoneItem(item);
		if(--m_nItemExecCount == 0 && m_bIsWaitingWorkEnd){
			m_vEventThreadWorkEnd.Set();
		}
	}
	return TRUE;
}
//作業待ちアイテムを設定する
HRESULT CDataAsyncReader::PutWorkItem(const CDataRequestItem &item)
{
	HRESULT hr;
	CLockObject lock(this);
	if(m_bIsFlushing) hr = VFW_E_WRONG_STATE;
	else{ m_dequeWorkItem.push_back(item); m_vEventSetWorkItem.Set(); EnableAsyncRead(); hr = S_OK; }
	return hr;
}
//作業終了アイテムを設定する
HRESULT CDataAsyncReader::PutDoneItem(const CDataRequestItem &item)
{
	CLockObject lock(this);
	m_dequeDoneItem.push_back(item);
	m_vEventSetWorkDone.Set();
	return S_OK;
}
//作業待ちアイテムを取得する
HRESULT CDataAsyncReader::GetWorkItem(CDataRequestItem &item)
{
	CLockObject lock(this);
	if(m_dequeWorkItem.empty()) return E_FAIL;
	item = m_dequeWorkItem.front(); m_dequeWorkItem.pop_front();
	if(m_dequeWorkItem.empty()) m_vEventSetWorkItem.Reset();
	return S_OK;
}
//作業終了アイテムを取得する
HRESULT CDataAsyncReader::GetDoneItem(CDataRequestItem &item)
{
	CLockObject lock(this);
	if(m_dequeDoneItem.empty()) return E_FAIL;
	item = m_dequeDoneItem.front(); m_dequeDoneItem.pop_front();
	if(m_dequeWorkItem.empty() && (!m_bIsFlushing || m_bIsWaitingWorkEnd)) m_vEventSetWorkDone.Reset();
	return S_OK;
}

作業用スレッドの典型的なパターンです。イベント発行を待ってアイテムを処理しています。

イベントについては二種類(何か作業が存在するorスレッドを終了する必要がある)で待機して・・・というものですね。

キュー処理部分はそれぞれPut~ItemとGet~Itemで管理してますし、キューの同期が必要なときは自身をロックしています。

なお、作業用スレッドはこのクラスの作成時ではなくはじめて非同期処理を要求されたときに作成されています。

あとはDirectShowのフィルタ実装で面倒なピンとフィルタ本体の実装

といっても、baseclassesを使う限りはCBasePinやらCBaseFilterやらが使えるので非常に楽なんですよね。

これが自分で実装、とか言うと実装しなければ行けないインターフェイスやらが多すぎていやになってきます。