DirectShow」カテゴリーアーカイブ

DirectShow Extend Filter Library 0.16公開

ある意味大変な間違いをしていたためにlibvpxのコンパイルが通らなかったことがわかったので改めてビルドし直しました…。

 

x86用のプロジェクトを作製した後のmake cleanを忘れるとこうなるのか…

というわけで真相がこれ。x86用の環境とx86-64用の環境はほぼ同一で作るようにしていたのでconfigureをx86用で通してmakeしたものをコピーしてlibvpxのライブラリを作成してそのままconfigureでx86-64用の設定をしてmakeをして…と思っていたのですが、どうもこれがまずかったようです。

x86用の設定が残ったままとなり、x86-64用の各種定義やらアセンブラのコンパイル環境がうまく設定されなかった結果、x86-64用のライブラリの作成に失敗していた、が真相だったようです。ちなみにmakeでその手のファイルも作り直されるのを失念していたのでただの間抜けですね。vcprojをコピーしただけではその辺の設定が正しく通らないので注意しましょう。

 

x86用のプロジェクトとx86-64用のプロジェクトを「ディレクトリ単位で」分けることで解決

ちょっとデータ容量がもったいないですが、ビルドできないよりましかな、という結論です。そのデータ量を惜しむならばハードリンクでもすればいいのですが、さすがにそちらの方が作業の手間なのでこういう結論になりました。こんなことで悩む人は多くないと思いますが、気をつけるべき点ですね。

 

Vectorのライブラリも更新

こっちはこっちで一体何年ぶりなのでしょうかね。毎月通知だけは受け取っていたのですが。しばらくたったら正式公開されると思いますのでそちらからダウンロードしたい人はちょっと待っていてくださいね。

 

DirectShow Extend Filter Library 0.15公開(ただしx86版のみ)

ただし書きが悲しいですよね…。x86版の方は形になりましたのでここまででいったん公開です。

 

放置しすぎたためにGUIDが違うわライブラリがビルドできないわ…

発端は「使ったときにWMVやWebMがうまく再生できない」というメールでした。少しだけ時間の余裕ができたので調べてみようと思いビルド環境を組み上げ…ることがなんと至難なことか。

まあ、わかりやすく言うとさすがに10年ほど前にビルドしたものなので環境が違いすぎること。特に

  • VisualStudioのバージョンを2010から2017への変更
  • WebM系のライブラリをできる限り最新環境へとアップデート

の2点を行ったのですが、特に後者がかなり難しかったというのが大変だった理由で、今回x64版がビルドできなかった理由だったりします。

 

webmdshow(WebMをDirectShow経由で再生するライブラリ)が2015年以降ほぼ更新されていない!

そう、今回の最大の問題点がこれだったけです。つまりVisualStudio2017(やそれよりも新しいVisualStudio)を前提にしていないコードのままになっていた、というのが問題だったわけです。特に

  • DirectShowの基本ライブラリ(BaseClasses)と同じ名前を使っているために名前空間の分離が必要(これは前から)
  • オーディオの読み取り部分でVorbisを読み込むパターンしかなく、Opusを読み込むパターンが用意されていない
  • 付属のlibvpxが古いため最新のlibvpxをビルドする必要がある

の3点が問題になりました。1点目は根性で相変わらずカバーです。2点目については今回Opusに関しては元から変換フィルタだけは用意されていたのでwebmdshow側でOpusが来たときにフォーマットを認識するコードを追加することでこれはなんとか回避しました。ところが3点目についてlibvpxはx86版はビルドできたのですが、x64版はアセンブラがコンパイルできないためにライブラリの生成に失敗する、という現象でストップしてしまいました。ソリューションの作成はLinux側で作ることで回避できるのですが、アセンブラがコンパイルできない問題はさすがに予定外です。何が悪いのか今現在はさっぱりわからないのでこちらは保留状態になっています。

 

Visual Studio 2017に対応していないライブラリ群は除外することに

一部のライブラリが古すぎてコンパイラのコード生成に失敗したりライブラリのリンクに失敗するという問題がありましたので0.15の段階でサポートを外すことにしました。もし使いたい場合は一応0.14版も残しておきますのでそちらを利用してみてください。何のサポートがなくなったはソフトウェアのページで確認してみてください。

 

開発環境移行 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の動作構造が「フィルタ」と「ピン」で汎用的に書かれているためですね。

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