せめて200pv/dayくらいあれば広告でも入れる価値が・・・とかいっている問題じゃなくて・・・。
というわけでVMR9実装の二回目です。といっても、今回は実装ではなく宣言と呼び出しの流れを見てみたいと思います。
VMR9のRenderlessにはIVMRSurfaceAllocator9とIVMRImagePresenter9を取得できるオブジェクトを実装してそれを渡す必要があります。
実装するクラスは前回例として出したあの宣言をそのまま使います。
なお、今回以降CUnknown系の実装も入りますのでCUnknownの実装例を先に見ておくことをおすすめします。
オブジェクト宣言部
class CVMRSurfaceAllocator : public CUnknown, public IVMRSurfaceAllocator9, public IVMRImagePresenter9{ public: CVMRSurfaceAllocator(IDirect3DDevice9 far *lpDirect3DDevice,HRESULT *lphr); //コンストラクタ virtual ~CVMRSurfaceAllocator(); //デストラクタ DECLARE_IUNKNOWN; //IUnknownに必要な関数を宣言するマクロ STDMETHODIMP NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface); //QueryInterfaceの実装 //IVMRSurfaceAllocator9の実装 STDMETHODIMP InitializeDevice(DWORD_PTR dwUserID,VMR9AllocationInfo *lpAllocInfo,DWORD *lpNumBuffers); STDMETHODIMP TerminateDevice(DWORD_PTR dwUserID); STDMETHODIMP GetSurface(DWORD_PTR dwUserID,DWORD dwSurfaceIndex,DWORD dwSurfaceFlags,IDirect3DSurface9 far **lplpSurface); STDMETHODIMP AdviseNotify(IVMRSurfaceAllocatorNotify9 *lpVMRSurfaceAllocNotify); //IVMRImagePresenter9の実装 STDMETHODIMP StartPresenting(DWORD_PTR dwUserID); STDMETHODIMP StopPresenting(DWORD_PTR dwUserID); STDMETHODIMP PresentImage(DWORD_PTR dwUserID,VMR9PresentationInfo *lpPresInfo); //デバイスのリセット処理 HRESULT OnLostDevice(void); HRESULT OnResetDevice(void); private: ... //変数宣言がくるが、実装中に説明する };
必要とされるそれぞれのインターフェイスの実装が含まれています。
あと、IDirect3DDevice9を使う関係上、リセットが必要となるときのためにリセット処理をこのオブジェクト上で処理できるように処理命令を宣言しておきます。
初期化~解放での流れ
このオブジェクトはVMR9に組み込まれたとき、どうやって動くのでしょうか?
COMがわかりにくい理由として、「それぞれが呼ばれるタイミングがいつなのか(初期化が終わっているなどの状態)がよくわからない」という
いわゆる処理のブラックボックス化により、実装すべきコードがどういうものかわからなくなる、というものがあります。
なので、今回はこれを先に紹介しておきましょう。
前回の例からいうと、初期化時には以下の流れを行います。IUnknown系の処理は無視しますよ。
- コンストラクタの呼び出し
- AdviseNotifyが呼び出され、VMR9フィルタからIVMRSurfaceAllocatorNotify9を渡される
解放時(Releaseを発行して解放されるとき)は以下の流れを行います。
- デストラクタの呼び出し
・・・あれ?InitializeDeviceとか呼ばれてないんですが・・・とか思っていません?
フィルタ接続~切り離しでの流れ
それが呼ばれるのはVMR9がDirectShowの中でフィルタとして動作するときの流れです。
VMR9がビデオラインの出力先となろうとしてるとき(RenderFileなどでグラフが作られるとき)には以下の流れを行います。
- InitializeDeviceが呼び出され、デバイスの初期化処理を行う
・・・流れじゃね~。これじゃ一つだけだよ~。
で、フィルタが切り離される(ビデオを解放する)時には以下の流れですね。
- TerminateDeviceが呼び出され、デバイスの解放処理を行う
ちなみに、以降の処理で明らかになりますが、TerminateDeviceがよばれても即座にReleaseが呼ばれてデストラクタ、とはいきません。
その間に微妙な処理が入ることもありますので、Releaseでやるべき処理をTerminateDeviceでやってはいけません。
レンダリングの処理
レンダリングの処理はちょっと複雑です。このときに限りIVMRSurfaceAllocator9とIVMRImagePresenter9が協調して動くことになります。
流れとしてはこんな感じです。
- m_lpMediaControl->Runによってグラフを再生状態にしようとする
- StartPresentingが呼び出されてグラフが再生状態となったことが通知される
- (m_lpMediaControl->Runの処理が完了してコードに復帰する)
- GetSurfaceが呼び出されてビデオのレンダリングに必要なサーフェイス(あるいはテクスチャ)をVMR9に貸し出す
- PresentImageが呼び出されて1フレーム目のレンダリングが完了したことが通知される。
- フレーム待機時間が経過する(フレームレートの調整時間)
- GetSurfaceが呼び出されてビデオのレンダリングに必要なサーフェイスをVMR9に貸し出す
- PresentImageが呼び出されて2フレーム目のレンダリングが完了したことが通知される。
- フレーム待機時間が経過する
……
m+1.GetSurfaceが呼び出されてビデオのレンダリングに必要なサーフェイスをVMR9に貸し出す
m+2.PresentImageが呼び出されてnフレーム目のレンダリングが完了したことが通知される。
m+3.フレーム待機時間が経過する
……
こんな感じです。後でこの処理の流れをうまく使うことになります。
この処理で重要なのは、GetSurface=>PresentImageがこの流れで常に発生するということです。
レンダリングの終了はそれほど難しくありません。
- m_lpMediaControl->Stopを呼び出してグラフが停止状態にしようとする
- StopPresentingが呼び出されてグラフが停止状態となったことが通知される
- (m_lpMediaControl->Stopの処理が完了してコードに復帰する)
というわけで、完全な全体の流れ
今のを総合して、以下のような流れになります。
- VMR9の状態初期化処理を行う
- グラフの処理を行いVMR9のデバイスの初期化処理を行い、再生が可能な状態に置く
- グラフを再生状態にしてVMR9が描画処理を行う
- グラフを停止状態にして停止処理を行う
- グラフが切断されるとき、VMR9のデバイスの解放処理を行う
- VMR9のフィルタのメモリを解放する
細かいところはそれぞれを代入していってください。
紛れ込む要素としては、
- VMR9で再生中にIDirect3DDevice9がリセット処理を要求することになったときにサーフェイスやテクスチャはどうなるのか?
- ビデオのレンダリング時IDirect3DDevice9の状態はどうなるのか?
だとおもいます。これらは次回に説明します。
それでは~。