« WM用カスタムアロケーター | メイン | 標準ビデオレンダラの接続 »

2005年10月29日

吉里吉里 ムービー拡張日誌2:: シーク処理

    

シークの処理はレンダリングフィルタからアップストリームへ向かってシークを処理できるところまでやってくる。
ヘルプには、レンダリングフィルタが複数ある場合は、複数回コールされる可能性があるが、ソースフィルタは何度もコールされないように適切に処理する必要があると書かれている。
ソースフィルタでシーク処理を実装しやすくするために用意されている基底クラス( CSourceSeeking )では、複数の出力ピンを持ったものを考慮していない。

初期の実装では、シーク処理はソースフィルタでサポートし、複数回のコールについては何ら対処をしていなかった。( CSourceSeekingを使って実装していた )
そのため何度も同じメソッドがコールされていた。
ただ、何度コールされても問題ないはずなのだが、うまくシークされなかった。

また、フィルタグラフマネージャがどのような流れでシーク処理を行うのかがよくわからない。
ヘルプにはどのような順序でコールされていくのかといった記述は見当たらない。
デバッガで追ったところでは、まずフィルターのStopがコールされた後、実際のシーク処理であるSetPositionsがコールされ、フィルターのRunがコールされている。
『シーク処理後には、ストリーム タイムはゼロにリセットされる。』と言うのはこのためか。( Runがコールされたらストリーム タイムはゼロになる )
だとしたら、Stop、SetPositions、Runと言う流れだと言う認識はあながち間違いではなさそうだ。

他に、シーク直後にレンダリングフィルタへ送るサンプルのタイムスタンプは0にしないといけないようだ。
これはシーク後ストリーム タイムはゼロにリセットされるためだ。
レンダリングフィルタは、現在のストリーム タイムがサンプルのタイムスタンプの時間になったらレンダリングするようになっている。
WMリーダーから取得したデータには、そのサンプルのムービー内での時間が入っているため、この部分は変換を行う必要がある。
WM同期リーダー( IWMSyncReader)を使った場合、シーク処理はSetRangeを使って行う。
SetRangeは単純に再生範囲(開始時間と長さ)を指定するメソッドだ。
このメソッドをコール後、GetNextSampleを呼び出すとSetRangeで指定した範囲のサンプルが得られる。
つまり、SetRangeで指定した開始時間からサンプルが得られる。
ただし、ビデオのサンプルは指定した開始時間からではなく、指定した開始時間直前のキーフレームからサンプルが出てくる。
このため単純にGetNextSampleで得られたサンプルタイムスタンプから指定した開始時間を引いた場合は負の値となる。
現在、負の値になった場合は直ちにレンダリングされるように開始タイムと終了タイムに0を設定している。
すなわち、直前のキーフレームから指定した開始時間までのサンプルを出来るだけ速くレンダリングする。
言うまでもなくこれはかなり重い。
キーフレームと指定開始時間のギャップをうまく処理する必要がある。
とりあえず、負の値になった場合は指定開始時間を最初のサンプル時間へずらすようにした。
そのため、シーク処理は常にキーフレーム単位で行われることになる。
IMediaSeekingではシークをキーフレーム単位で行うかどうかの指定がある。
本来はこの指定に従ってシークするべきだろうが、現在はこの指定を見てない。


シーク処理はレンダリングフィルタからやってくるのは前述の通りだが、実際には出力ピンに対してシーク用インターフェイスの取得が行われ、そのインターフェイスに対して呼び出しが入る。(このインターフェイスの取得はレンダリングフィルタからやってくる)
出力ピンのシーク用インターフェイスは、ソースフィルタのシーク用インターフェイスに処理を委譲するような作りになっている。
このため、ビデオレンダリングフィルタとサウンドレンダリングフィルタをつないだ場合、2回同じメソッドがコールされるはずなのだが、なぜか3回もコールされていた。
デバッガで追いながら呼び出し履歴を見てみると、出力ピンからの呼び出しだけでなく、直接フィルタのシーク用インターフェイスへ呼び出しが行われていた。
つまり、出力ピンからの呼び出しでSet系のメソッドは無視してOKってことだろうか?
と言うか、シークの呼び出しはレンダリングフィルターからやってくるんじゃなかったのか?
ヘルプにはそのように書かれているが。
だけど、直接ソースフィルタのシーク用インターフェイスを呼び出したら、レンダリングフィルターからやってくる呼び出しの意味って……
ヘルプでCSourceSeeking::GetPositionsの解説を見ると、『フィルタグラフマネージャを使って現在位置の取得を行う場合はソースフィルタではなく、レンダリングフィルタから取得される』とある。
IMediaSeekingインターフェイスへの呼び出しすべてをソースフィルタが処理すると言うわけではないようだ。
推測の域をでないが、メソッドによってはダイレクトに呼び出す場合と、レンダリングフィルターからやってくる場合があるのだろう。
出力ピンが持つシーク用インターフェイスではSet系の呼び出しは無視することにした。


WMリーダーはシーク直後になぜかWM用アロケーターへサンプルサイズよりも大きいサイズのバッファを要求する時があるようだ。
IWMSyncReader::GetMaxOutputSampleSizeで最大サイズを取得すると、サンプルサイズの3倍のサイズが返ってきた。
サンプルサイズには、縦×横×1が格納されていた。
YV12フォーマットの場合、Yは画像サイズと同値で、UとVは1/4になるはず。
つまり、サイズとしては縦×横×1.5が格納されるはずだが、なぜか確定したメディアタイプのサイズは縦×横×1になっている。
よくわからない。
実際、アロケーターへのコールを見てみると、要求されているサイズは縦×横×1.5だった。
でも、普段は縦×横×1のサイズを返していたが、問題なく再生されていた。
が、シーク後は縦×横×1.5のサイズが必要なようで、その要求が通るまで情け容赦なくアロケートをコールするようだ。
で、そこで返って来なくなっていた。
そこで、GetMaxOutputSampleSizeで得られるサイズをアロケーターが返せるようにしたら、無限ループはなくなり再生が続けられるようになった。
ただし、GetNextSample 1回に対して、アロケーターを2回コールするのはなくならないようだ。
シーク後に動き補償などの関係で必要になることがあるのだろうか?
理由は良くわからないが、2回コールするのは間違いない。
もしかしたら、もっと多くコールされているかもしれないが、認識している範囲では2回だけだ。


まだいくつか不都合はあるが、ある程度シークが出来るようになった。
にしても、シーク処理はややこしいな。



投稿者 Takenori : 2005年10月29日 13:46




comments powered by Disqus
Total : Today : Yesterday : なかのひと