DirectShow でメモリデータを再生

ここに、メモリの中に展開した mp3 とかのデータがあるとしましょう。 これを DirectShow で演奏させたい。どうすればいいか。

グーグル先生に訊くと、「一旦テンポラリファイルに書きだして RenderFile」という しょうもない答えが返ってきます。

というわけでここでは、テンポラリファイルを使わずに メモリのデータを DirectShow で演奏する方法を説明しましょう。

方針

GraphStudio (DirectShow のグラフを見れるソフト) などを使って、 普段再生しているファイルがどういうグラフで再生されているのか 見てみましょう。すると、File Source (Async)というフィルタが 一番上流に来ていると思います。

これは、MSDN の DirectShow の項では ファイルソース (非同期) フィルタ CLSID_AsyncReader のことです。 これと同じ動作をするようなメモリソースフィルタを作ればよいのです。

「えーフィルタとか作ったらレジストリいじらないといけないんじゃないのー」

そんなことはないです。メモリソースフィルタは、 自分のプログラムの中で new すればよいので、 レジストリに登録する必要はありません。

さて、MSDN のファイル ソース (非同期) フィルタの項を見ると、 そのインターフェースはこのようになっています:

フィルタ・インターフェースIBaseFilter, IFileSourceFilter
出力ピン・インターフェースIAMAsyncReaderTimestampScaling, IAsyncReader, IPin

このうち、IFileSourceFilter はファイルのパスを指定するためのインターフェースです。 僕たちは今メモリ上にあるデータを演奏したいわけですから、こんなインターフェースは不要です。 結局僕たちが作らなくてはいけないのは:

フィルタ・インターフェースIBaseFilter
出力ピン・インターフェースIAMAsyncReaderTimestampScaling, IAsyncReader, IPin

ということになります。

実装しなければいけないもの

まず MSDN Library の見方がわからない方のために、ここでそれを説明しておきましょう。

MSDN は、できれば英語版 (en-us) を見てください。 日本語版は重要な情報が訳から抜けていたり、ページそのものがなかったり、 訳文が機械翻訳のままだったりとクオリティが低いです。

新しい Windows がリリースされる度に構成がほんの少し変わりますが、 130525 現在では、

MSDN Library → Windows Desktop App Development → Develop → Desktop App Technologies → Audio and Video → DirectShow → DirectShow Reference → DirectShow Filters → File Source (Async) Filter

とたどった先に、ファイル ソース (非同期) フィルタの説明があります。

さて、IBaseFilter とかのインターフェースのリンクを踏むと、 そのインターフェースがさらにたくさんのインターフェースを使っていることがわかります。

そのインターフェースを実装したデフォルトのオブジェクトがなければ、 僕たちは、それも併せて作らなければいけません。

実装すべきインターフェースは、結局全部挙げると以下です:

意外と少ないですね (皮肉な笑い)。 僕は最初、IMemAllocator とかも自作していたので もっとたくさんのコードを書かなくてはいけませんでしたが、 実は IMemAllocator は CLSID_MemoryAllocator を使えばよいので、 自分で実装する必要はありません。

いざ実装

実装したものは、拙作 Gandharva のソース (MemSourceFilter.cpp) にありますので見てください。ここではいくつか注意点をば。

IPin::Connect

これの正しい実装方法は、MSDN を目を皿にして探すと見つかります。 探してみてください。(How Filters Connect / Negotiating Media Types)

また、IAsyncReader の項にちょろっと書かれていて見落としがちですが、 「『下流フィルタが ReceiveConnect 中に QueryInterface で IAsyncReader を取得したか』どうかを 監視し、もし取得されなかったならば、接続に失敗する」必要があります。

これをしないとどうなるかというと、受動的な入力ピンと繋がってしまいます。

接続には二種類あって、出力ピンが能動的に攻めるものと、 入力ピンが能動的に受けるものがあります。 IAsyncReader は、相手側 (入力ピン) が能動的にこちらにデータを要求してこない限り動きません。

一方、受動的な入力ピンは、出力ピンのほうが能動的にデータを入れてくることを期待して待機します。

というわけで、受動的なピンが二つ繋がっては不味いのです。

IPin::EnumMediaTypes

GraphStudio で CLSID_AsyncReader の挙動を観察するとわかることですが、 CLSID_AsyncReader はファイル形式を認識します。 MP3 だったら、「これは MP3 だよ~」と、EnumMediaTypes を呼んだ人に伝えます。

面倒ですね。

これはどうやるのかと言うと、(多分ですが) HKCR\Media Type\{MajorType}\{SubType} を見ます。 少なくとも wine はそうしているようです。ただし、僕の環境では、 CLSID_AsyncReader が判別できないデータもこれで判別できてしまうことがあります。 ま、実用上は問題ないです。

このレジストリの読み方は、Registering a Custom File Type という MSDN の項に書かれています。

なお蛇足ですが、regedit で見たレジストリの内容と、デバッガで実行中のプログラムが見たレジストリの内容が 違うことがあります。驚かないでください。それは、regedit が 64bit アプリであって、 実行中のプログラムは 32bit アプリだからです。両者の見ているレジストリは違います。 32bit 版の regedit を使うと、32bit 用のレジストリが見えます。

IAsyncReader::RequestAllocator

これが返却する IMemAllocator のインスタンスは、 この関数の中でSetPropertiesを呼んでおく必要があります。 また、SetProperties する際には、ALLOCATOR_PROPERTIES 中でゼロになってはいけないメンバを ゼロでない値にセットしなければいけません。たとえばアラインメントとか。

RequestAllocator が受け取った ALLOCATOR_PROPERTIES をそのまま SetProperties に渡すとどうなるか。 たちのいいフィルタは、アロケータにあまり要求がありませんから、そのメンバをゼロにしています。 すると、IMemAllocator::SetProperties は失敗します。 ですので、IPin::ReceiveConnect の結果が INVALID ARGUMENT とかいうワケワカラン返却値になります (その理由は SetProperties の実引数がおかしいからです)。 もちろん接続には失敗します。

完成したら

完成したら使いたいですよね。どう使うかというと、RenderFile する代わりに メモリソースフィルタのピンを Render します。それだけです。 なお、Render するピンを持ったフィルタのインスタンスは、 あらかじめフィルタグラフのなかに追加されている必要があります:

//
// gandharva/FolderZip.cpp の ZipSourceRenderer より
//
virtual HRESULT Render(IGraphBuilder* pGraph) const {
    // CoCreateInstance じゃなくて自分で new する
    auto pFilter = make_comptr<MemSourceFilter>();
    if(! pFilter) return E_FAIL;

    // Render する前に AddFilter する
    auto hr = pGraph->AddFilter(pFilter.get(), L"MemSource");
    if(FAILED(hr)) return hr;

    // 演奏するメモリをセットして
    pFilter->SetMemory(pArc_->GetData(sFileSpec_));

    // 自作フィルタのピンを取り出し
    comptr<IPin> pPin(pFilter->GetOutputPin());

    // それを Render
    return pGraph->Render(pPin.get());
}

戻る

inserted by FC2 system