だから日本語の資料が少なすぎる上にDirectShowにおけるbaseclassesに相当するサポートクラス群がないものだから動きが全く分からないのなんの。これじゃDirectShowの発展系としてMediaFoundationで実装する人って出ないのではないでしょうか?と思えるほどの状態です。それでも挑んでみるのが私なのでやってみるだけやってみました。
DirectShowにおける「CBaseVideoRendererを使ったレンダリング」に近いことができればとりあえずOK
これが目標です。というのも、MovieLayerPlayerで描画を行うためにはウィンドウ経由の描画に頼らないで描画する必要があるからです。そのため、いったんプログラム側で管理しているサーフェイスなりテクスチャなりに一度転送してそこから・・・というシーケンスになります。
今回はこれを実現しそうなサンプルとして「EVRPresenter」を選択してこれを発展させる形で実装に挑んでみました。この選択が吉と出るか凶と出るか、時間の無駄と出るか・・・。
EVRPresenterとは
通常、MediaFoundationでウィンドウへの描画には「Enhanced Video Renderer」を使うことになります。これはWindowsVista以降で使用可能となるレンダラで、DirectShowのVideoMixingRenderer系を拡張したレンダラになっています。また、WindowsVista以降であればDirectShow側でもフィルタとして登録されていますのでこれを使って描画することができます。使い勝手自体はVideoMixingRendererよりたぶんいいとは思いますが、WindowsXP以前では使用できませんので(今となっては余り意味がありませんが)注意です。
で、このEnhanced Video Rendererの「プレゼント処理」を乗っ取る形で自前実装を行っているサンプルがEVRPresenterになります。その前の色変換処理などはすべてレンダラに任せて最終結果だけを得られる形になっています。ただし、元々Enhanced VideoRendererがVideoMixingRendererの考え方を踏襲して作られたような格好をしているので動きそのものはVideoMixingRenderer9とかなり似ていますし、Direct3D9(かそれ以上)が必要となるのでそれがない時は基本的に使えません。この制約が最終的に効いてきます。
で、実装してみた
基本的にはサンプルを見ながら必要な動きを奪ってきて自分が持っている各インターフェイスに振り分ける作業をしています。実装に必要になるのは主に
- サンプルプール(ビデオイメージを入れておくサーフェイスのバッファ処理)
- スケジューラ(指定時間でサンプルを描画するように指示する)
- プレゼンタ(EnhancedVideoRendererの描画結果を受け取る)
の3つとなります。最後のが大きすぎる(EVRに連結させるためにかなりの数のインターフェイスが必要になるため)のが厳しいところです。このあたりはMediaFoundationのインターフェイスを実装したい場合のライブラリを公開していないから、というのが大きいです。実際、DirectShowではサンプルプールやスケジューラやプレゼンタの大多数の機能はbaseclasses経由で公開されているのでCBaseVideoRendererのように簡単に使えるのが大きいです。
今回はあくまで「サーフェイスに描画するところまで」とするのが目的なのでプレゼント処理は行いません。これでもそれなりに動くようになるまではかなり苦労を強いられました。一部の機能を実現するために下位機能の呼び出しに制約がありますのでその辺を中心にちょこっと。
内部で使用するDirectXGraphicsはDirect3D9Ex以上でないと大変なことに
元からMediaFoundationはWindowsVista以上(つまりDesktopWindowManager下で動作すること)を前提としているのでDirectXGraphics側もDirect3D9Exの状態でないとかなり困る事態が発生しやすいです。たとえばEVRの出力サーフェイスはレンダーターゲットでなければならない(サンプルではスワップチェーンのサーフェイスを指していますが今回はサーフェイスでないと意味がない)ので再生中に何らかの都合でデバイスをリセットしたくてもDirect3D9のままではD3DPOOL_DEFAULTのサーフェイスを取られたままになるためリセットできません。これはちょっと痛すぎです。Direct3D9Exであればこの制約がないので突破できたり。VideoMixingRenderer9時代にも似たような現象がありましたがここにも現れるのか・・・というところです。
また、EVRのフィルタはIDirect3DDeviceManager9(もしくはIMFDXGIDeviceManager)を返さないと動作しないのでこのインターフェイスを作成しないとだめなのですが、この処理がまた渋いのなんの。Direct3DDeviceを複数またいで使用させる時に妙なロックが必要になったり、これのせいでMovieLayerPlayer側に変なデッドロックを作ってしまったような感じで。DirectXをビデオデコーダーのアクセラレーションに使うのは分かりますが、ちょっと厳しいのではないですか?と。
おかげで自分が持っているDirect3D9を使った描画ライブラリにDirect3D9Exも含めて実装するようにしました。
スケジューラの実装が不安定なような・・・
気がするんです。テストで作ったプログラムでは発生しないのですが、MovieLayerPlayerへの組み込みテストではかなりの確率(50%に近いくらい)で読み込み=>再生を行うとフレーム遅延が大量に発生する描画となってしまいます。しかもこのときはウィンドウサイズの変更を行うと遅延していたはずのフレームが一気に描画されて再生位置が追いつく、という現象が現れていまいち。ウィンドウサイズの変更でDirect3D9Exもリセットがかかるのでその辺との兼ね合いの可能性も大きいような気がしますが・・・。
この現象についてどうにかしないとさすがに公開レベルにはならない、ということで今のところ内部テストだけになっています。スケジューラだけをデバッグすればいいはずなのですが、その場合はバグっている様子がなく。やっかいですね。
そして各インターフェイス間の動きが分かりづらい
DirectShowのフィルタ実装の時もたぶん同じことを書いたり思ったりしています。何がどこでどういう順序で呼ばれるのかがわかりづらい。実装して動かしてみることで何となく動きはつかめてきましたし、インターフェイスが何のためにあるのかもようやく分かり始めてきたかな、というレベルです。このあたりも英語の資料しかないのがかなり効いています。まあいまさらWindowsのプログラムだけにこだわる意味なんてありませんからね、というところでしょうか。
ちなみにEVRPresenterを使ってMediaFoundationで直接再生するのも手順が分かりづらいです。DirectShowのように「AddFilterでフィルタを追加してRenderFile」とは行かないですしね・・・。このあたりも一応解説しておきましょう。EVRPresenterクラスをプログラム内で直接使うには以下のようにします。blog内の記事から管理クラス内部実装その3からビデオレンダラを作成しているところに処理を突っ込みます。
- EVRPresenterのインスタンスを作成
- MFCreateVideoRendererでEVRを含んだレンダラをIMFMediaSinkとして作成
- IMFVideoRendererをIMFMediaSinkから取り出してEVRPresenterを設定
- IMFMediaSinkからGetStreamSinkByIndexでIMFStreamSinkを取得
- IMFStreamSinkはIMFActivateと並んでIMFTopologyNodeで接続できるオブジェクトなのでこれをSetObjectで設定
となります。これ、どうやって接続させればよいか悩んだんですよね・・・。SetObjectで設定できるのはIMFActivateとIMFStreamSinkなのは分かっていましたがそれがどこから手に入るのかやっぱり分からない、というのは厳しすぎです。
ちなみにこのシーケンスのおかげでIMFMediaSinkがDirectShowのIBaseFilter、IMFStreamSinkがDirectShowのIPinに近い役目を持っていることが分かったので次に完全自前実装をやろうとした時にかなり役立つ情報となってくれるはずです。
初期化のタイミングがロード直後でないのが使いづらい
MediaFoundationの実装時にも示しましたが、ロードが完了した直後は再生できる状態ではありません。その後非同期で各接続を行うためで、これが非常にやっかいです。逆に言うとその処理が終わるまで正確な情報が分からない(たとえばビデオの画像サイズが不明)のでその情報が分かるまで待機しなければならない、とかMediaFoundationで各インターフェイスを作って接続をしようとしたらその段階でエラーとなり再生できない、などがあります。(実際、AdvancedCodecsを入れてある私の環境でmp4を再生させようとしたらMediaFoundation経由だと読み込んだ後接続できない状態に。入っていないなら普通に成功したのですが)
この辺も示したサンプルからかなり修正を加えてロード完了通知を送るようにしたりと工夫することになりました。
でも描画がバグったら使い物にならないだろう
結局「一定確率でフレームがどんどん遅延していく」現象の謎が未だ解けずお蔵入りしそうな気配となっています。おそらくスケジューラの状態がおかしいかEVRとプログラム本体側のDirect3DDevice9との兼ね合いが良くないのでしょうね。DirectShowの時も結局VideoMixingRenderer9を使う実装をしてみたものの不安定すぎてお蔵入りにしたで似ていると言えば似ている末路になりそうです。
だからといってCBaseVideoRenderer並の実装はさすがに面倒です。この実装のおかげでたぶん今であれば一週間がんばれば実装できるとは思いますが趣味レベルでこれをやるのはちょっと躊躇するところです。他の人が使うので仕事として依頼、くらいならやる価値は十分にあると思います。これができればWindowsのADVエンジンでDirectShowからMediaFoundationへの移行が十分視野に入ると思います。
ちなみにどうでもいいこととしてDirect3D9Exを使うと案外簡単にウィンドウサイズ変更が実装できるのでもしかするとWindowModePatchにウィンドウサイズの自由変更を実装するかもしれません。暇だったらですが。