DivX/XviD の B 対応について、デコード時の挙動についても書いてみようという話。どうせならまとまっていた方がいいかと思っていたのだけど、月境界で分断されてしまった。気にしても仕方がないのだが。
というわけで、packed bitstream なエンコード済みデータ (0x7f は除いて AVI に格納) を渡された CODEC がどんな順番でデコード済みフレームを返すかを示したのが上の図。見れば判るよね。以上。
流石に手抜きが過ぎたかと後悔したので、も少し言葉を追加してみる。この図は左から右に進んでいる。
まず 1st ステップ、アプリケーションが AVI に格納されている先頭フレームの I [0] ピクチャデータを CODEC に渡すと、CODEC は直ちに Frame [0] のデコード結果を返す。ここで、図では省略しているけども、Frame [0] のデコード結果は内部的に続く P / B ピクチャをデコードする際に予測元画像として使われる。
次に 2nd ステップ、アプリケーションは AVI に格納されている第2フレームの P [2] + B [1] ピクチャデータを CODEC に渡すと、CODEC は Frame [0] と P [2] から Frame [2] をデコードし、続いて Frame [0] と Frame [2] と B [1] から Frame [1] をデコードする。ここでデコードされた Frame [1] と Frame [2] のうち、Frame [1] は直ちにアプリケーションにデコード結果として返されて、Frame [2] は CODEC 内部にバッファされる。同じく図からは省略しているけど、Frame [0] と Frame [2] は予測元画像として使うためにまだ CODEC に保持されている。
さらに 3rd ステップ、アプリケーションが AVI に格納されている第3フレームの P (NC) ピクチャデータを CODEC に渡すと、CODEC は内部バッファに保持していた Frame [2] をデコード結果として返す。このとき、今まで予測元画像として保持されてきた Frame [0] は破棄される。ただし、Frame [2] はまだ保持されている。
そして 4th ステップ、アプリケーションが AVI に格納されている第4フレームの P [5] + B [3] ピクチャデータを CODEC に渡すと、CODEC は Frame [2] と P [5] から Frame [5] をデコードし、続いて Frame [2] と Frame [5] と B [3] から Frame [3] をデコードする。ここでデコードされた Frame [3] と Frame [5] のうち、Frame [3] は直ちにアプリケーションにデコード結果として返されて、Frame [5] は CODEC 内部にバッファされる。図から省略してるけど、Frame [2] と Frame [5] が予測元画像として保持されるのは 2nd ステップと同じ。
それから 5th ステップ、アプリケーションが AVI に格納されている第5フレームの B [4] ピクチャデータを CODEC に渡すと、CODEC は Frame [2] と Frame [5] と B [4] から Frame [4] をデコードして、Frame [4] を直ちに結果として返す。
ついでに 6th ステップ、アプリケーションが AVI に格納されている第6フレームの P (NC) ピクチャデータを CODEC に渡すと、CODEC は内部バッファに保持していた Frame [5] をデコード結果として返す。図から省略している部分では、Frame [2] がこれ以降予測元に使用されなくなるので破棄される。
とまあこんな感じで、以降のデコードは 4th 〜 6th の繰り返しみたいな形で進んでいく。以上、連続 B ピクチャカウント (Max Consective BVOPs) を 2 に設定した場合に出力されるデータのデコード時挙動についての解説。
ポイントは、AVI に格納されてる X フレームを CODEC に突っ込んだ時に、出力されるデコード結果も X フレームになるというあたり。それを実現するためにはアプリケーション側でエンコード時に変態的で邪悪な処理を追加してやらなければいけないのだけど…… packed bitstream ON に関しては比較的マシな方かと考えている。明日辺り、packed bitstream OFF の場合のデコード時挙動とかを書いてみる予定。
というわけで、packed bitstream : off 時のデータに対するデコーダの挙動。とりあえず恒例の画像から。
一段目。アプリケーションから渡された I [0] フレームを、CODEC は Frame [0] としてデコードし、後方参照フレームに保持。Frame [0] をデコード結果として返す。
二段目。アプリケーションから渡された P [2] フレームを、CODEC は Frame [2] としてデコードし、後方参照フレームに保持。今まで後方参照フレームにあった Frame [0] は前方参照フレームに移動する。ここでは Frame [0] をデコード結果として返す。これは、次に続く B ピクチャのデコード結果を P ピクチャのデコード結果よりも先に返さなければいけないため。結果を返さなければいいのではという意見もあるかもしれないけど、VfW の API では、入力と出力は 1:1 で対応することになっているので、そんなことはできない。
三段目。アプリケーションから渡された B [1] フレームを、CODEC は前方参照フレーム [0] と後方参照フレーム [2] を利用して Frame [1] としてデコード。そのまま Frame [1] をデコード結果として返す。
四段目。アプリケーションから渡された P [5] フレームを、CODEC は Frame [5] としてデコードし、後方参照フレームに保持。今まで前方参照フレームにあった Frame [1] は破棄され、後方参照フレームにあった Frame [2] が前方参照フレームに移動する。ここでは、前方参照フレームに移動してきた Frame [2] がデコード結果として出力される。
五段目。アプリケーションから渡された B [3] フレームを、CODEC は前方参照フレーム [2] と後方参照フレーム [5] を利用して Frame [3] としてデコード。そのまま Frame [3] をデコード結果として返す。
六段目。アプリケーションから渡された B [4] フレームを、CODEC は前方参照フレーム [2] と後方参照フレーム [5] を利用して Frame [4] としてデコード。そのまま Frame [4] をデコード結果として返す。
この先については、4段目から6段目と同様の繰り返しになる。
これから判るのは、packed bitstream : off の状態だと、1 フレーム分のディレイが VfW では発生してしまうということと、最後の 1 フレームは絶対に出力されないということ。
というわけで、packed bitstream は常に on を推奨したい。対応してないデコーダなんて捨てちまえ。つーか再生互換性が重要だとか言うならそもそも AVI で双方向予測なんて使うな。素直に WMV でも使っとけ。
packed bitstream は XviD の用語なのだけど、これは DivX 用語 (5.2.1 日本語版) だと「アダプティブ シングル コンセキュティブ」とかいうステキな呼び名になっている。packed bitstream : off に相当するのは「アダプティブ マルチプル コンセキュティブ」という選択肢のほう。
上の図の挙動は DivX の方で確認した結果。2004, 11/26 頃の CVS からブランチ指定なしで取得した XviD ソースをビルドしたものではウザイオプションを出してくれる上に、シーク時にステキな挙動を示してくれるので。
舞台裏。0x7f の数だけディレイを入れて NULL フレーム追加制御しとけば済むと思っていたのに……非 packed bitstream 時にはデコード時ディレイ対処の為に追加で 1 フレーム分遅延を入れなければいけないなんてー。嗚呼、後は VCM に flush 処理を追加するだけだとか思っていたのに……。
source.lzh (LH5 圧縮 138,319 byte) を使ってデバッグ中。このアーカイブの中には 1 フレームごとに数字がカウントアップされていく YUY2 形式の AVI (sample.avi) が入ってる。
とりあえず、packed bitstream に関しては何とかなりそう (邪悪で変態的だが) なのだけど、非 packed bitstream に関しては諦めるしかないかなぁという感触。
なるべく問題が出ないようにすることは……努力だけはするけど、それでも分割エンコード後に結合とかされると、結合点で不幸なことになるのだろーなーという自信がある。まあいいや、今日帰宅してから一通りテストして問題がでなければそれで公開することにしよう。
CODEC 仙人へのクラスチェンジイベントを止めることに成功……したのかなぁ。まだ微妙な感じだけど、とりあえず今後 3 ヶ月程度は CODEC を仕事で作るとか直すとか高速化とかせずに済みそう。
公開。更新内容は以下のとおり。
今後は AVI 2.0 対応作業を進める予定。とりあえず非圧縮出力に対応してしまった以上 AVI 2.0 をやらないわけには行かないだろう。希望的観測ではもう VCM 内の修正はしなくても済むものと考えている。んだけど、バグが見つかった場合はその限りではないということでー。
あー 12/2 の DivX での設定名が逆だった。シングルの方が packed bitstream : on でマルチプルの方が packed bitstream : off だった。今は修正済み。
バグってるー。非圧縮出力時にアクセス違反で落ちてる。そーいや B 対応の後でテストするのを忘れていた。0.2.2 で修正済み。
11/15 から予約を入れていたにもかかわらず、12/4 になっても発送通知メールが届かなかったので ローゼンメイデン DVD 1巻 の注文キャンセル処理を行う。WEB 上でのうわさを信じるならば、これは敗北パターン (いたずらに予定配送開始日のみが伸びていき、商品は一向に到着しない) のようなので。
というわけで実店舗の在庫を求めて新宿をさ迷い歩き、西口 (ヨドバシ・ソフマップ・ビックカメラ・さくらや)、東口 (TSUTAYA・紀伊国屋書店本店・さくらやホビー館) と空振りを続けながら、南口 (紀伊国屋書店タイムズスクエア店) でようやく目的商品を発見。
amazon もせめて予約分ぐらいしっかり確保しといて欲しいよなー。既に注文済みの 2巻 3巻 ではキャンセル&在庫求めての彷徨をせずに済むものと希望したい。
というわけで入手に成功した DVD をバックグラウンドで流しながら、AVI 2.0 の仕様調査中。OpenDML AVI File Format Extension [Matrox] を読んだり、GraphEdit から作成した AVI 2.0 ファイルを riffwalk で眺めたり。
オーディオ・ビデオがそれぞれ 1 ストリームずつな AVI 2.0 ファイルを作るのに足るだけの仕様は理解できたので、実装に移ったところ。便利な API は無いと前の調査時に判っているので、諦めてファイルを _open/_write/_seeki64/_telli64 で直接操作する方向でペチペチとコードを書いている。作業量的には、週末には完了して拡張 AVI 出力 0.3.0 として公開できそうな雰囲気。
えーと、拡張 AVI 出力の 0.2.x 系ですが、インタリーブ 1 で利用した場合、奇妙な AVI ファイルを出力します。0.3.0 と同時に直す予定なので、それまではインタリーブに 出力 FPS / 10 ぐらいの値を指定して運用してください。そもそもインタリーブ 1 の場合だとオーディオが細かく分けられすぎてシークが多発し、再生が不利になるようですし。
AVI 2.0 出力では自前で全部の面倒を見ることを決心したわけで、各種ヘッダ情報の詳細とかも必要になるわけなのだ。それにつけても、完璧な仕様ドキュメントが見つからないことにはどうしたらよいものやら。とりあえず今までに見つけた参考になりそうなもの一覧
ざっとみたところ、一番役に立ちそうなのは最後に上げた "AVI file format documentation" で、一番役に立たないのが "OpenDML AVI File Format Extension" そこそこ役に立ちそうなのが "aviriff.h" という悲しい状況。
つーか、AVIMAINHEADER::dwPaddingGranularity が DirectX 8.0 のドキュメント内では dwReserved1 になってて 0 を設定することとか記述されてたり、DirectX 9.0 のドキュメントでは dwPaddingGranularity に戻っていたりと、MS の文書にすら混乱が見られる現状で、何を信じればいいのやら。
あー来年の NAB までならまだ 4 ヶ月以上あるし、InterBEE の 3 ヶ月前になってから急に言われた今年の夏と比べればまだマシなほうなのかなぁ。まー心の健康を保つためにも「無理はしない」「できることしかできない」をモットーに。
むー相場って難しい。てっきり今週中に 100 円台突入かと予想してたのに、まさか 1 ドル 104 円台まで戻るとは思っていなかった。まープロの人も予想レンジから外している [為替を読む 12/6 | 読売新聞] みたいだから仕方がないか。
今のところ眺めているだけの人なので気楽だけど、張ってる人たちは大変だろうなぁ。
昨晩はローゼンメイデン待ちの間に作業が進み、今日中にはデバッグに入れそうな気配。後 2 話で終わってしまうのが正直惜しいなぁ。
AVI 2.0 対応の方はコードを書きながら設計の失敗をひしひしと感じてしまうのが悲しいところだったり。一つのファイルで済ませようとしたために、色々と面倒な作業が必要になっている。
気にせずそのまま公開して気が向いたら直す方針で突き進むべきか、今週末の公開は諦めて mux.c を低水準と高水準の2層に切り分けるべきか。今後、某お仕事が忙しくなりそうなので、気にせずそのまま公開に心が傾いている。
とりあえず AVI ファイルは出力されるようになったものの、再生不可の状態なのでデバッグ中。どうやらチャンクサイズが不正だとかヘッダに必要な情報が欠けているとかインデックスでのオフセットが間違っているとからしい。
というわけで riffwalk ならびに AVIMaster でデバッグ中。今日中に何とかしたいところなのだけど、何とかなってくれるかなー。
mux 単体のテストでは、1G 以下のファイルで見る限り問題のないレベルまで到達。後は auo に組み込んで、3G 超のファイルに挑戦……と。さて無事に動いてくれるかどうか……。
riffwalk は 2G 以上のファイルに対応していないし、AVIMaster は dwPaddingGranularity=0 での AVIOLDINDEX 解釈が怪しいしで原因追求に手間取ってしまった。とりあえず、AVISTREAMHEADER の dwLength に RIFF('AVI ') のみの長さではなく、ファイル全体 [RIFF('AVIX') の長さも含めて全て足したもの] を入れるようにしたところ、先頭 1G 分しか再生されなかった問題は解決。というわけで、exavi 0.3.0 を公開。
しっかしどうしてこんな仕様になっているのやら。これじゃ PCM オーディオで 4G サンプルを超えるようなことがあったら……それでも 13 時間は持つのか。通常なら問題は出なさそうだな。とすると、問題は AVI 1.0 との互換性が怪しくなるぐらいか。
拡張 AVI 出力 0.3.0 で対応した AVI 2.0 の話なのだが、結局、仕様書よりも実装の方が優先になってしまうのだろうか。例えば、次のような話。
OpenDML AVI File Format Extension には、AVIMAINHEADER::dwTotalFrames に先頭の RIFF('AVI ') に格納されているフレームの数を格納するようにと書かれているし、aviriff.h にも "DWORD dwTotalFrames; // # frames in first movi list" という行があるのだけど、Microsoft 謹製の AVI Mux フィルタはそんなものの存在を気に留めずに、ファイル全体でのフレーム数をこれに入れてくれる。
これだけならば、元々利用されていないヘッダのようなのでどうでもいいのだけど、仕様には明確に記述されていなかった AVISTREAMHEADER::dwLength の方では困ってしまった。
AVIMAINHEADER::dwTotalFrames 同様に、AVI 1.0 と共通のこのヘッダでは先頭 RIFF('AVI ') でのストリーム長のみを入れておき、ファイル全体でのストリームの長さは AVISUPERINDEX::aIndex[x].dwDuration を足し合わせることで算出するのだろうとか考えていたのだけど、Microsoft 謹製の標準 AVI Splitter フィルタでは、AVISTREAMHEADER::dwLength に書かれた長さしか再生してくれないので、ファイル全体での長さを記録することになる。
さらに Alex' video stuff 内の "AVI file format documentation" にも書かれていることだけど、AVISTREAMHEADER::dwSuggestedBufferSize には、仕様では 0 を指定できることになっているにも関わらず、ストリームのチャンクの中で、最大のもののサイズを指定しなければ AVI Splitter フィルタが発狂するとか……。
ついでに VirtualDub では AVIMAINHEADER::dwMaxBytesPerSec を、利用してないなら何を入れても構わないだろうといわんばかりに雄雄しく 0 を設定して放置してるし……。
完全な仕様書が無いのも嫌だけど、仕様に書かれていることを守っていない実装ばかりというのも嫌になる。当然のように拡張 AVI 出力の出力結果も決して仕様どおりではない。つーか仕様どおりに作ると動かないんだから仕方がないよね。
かぼちゃ 1/2 を 16 分割しただけで筋肉痛になる軟弱な腕を何とかしたいところ。日頃包丁を使い慣れてないからなー。
それはさておきアニヲタの主張。インタレース YUV 4:2:0 で色差の縦方向補間を行わないなんて信じられん。下の画像は NHK 教育で再放送中の「カードキャプターさくら」OP 映像 (CoCoon で 15Mbps 録画の TS データ) より一部を切り出して拡大したもの。
補間無し | |
補間あり |
長音記号の上下に背景の青が 1 ラインだけ侵食していたり、斜め部分がガタついていたりと、正直見るに耐えない絵になっていると思うのだが……。4:2:0 を 4:2:0 のまま扱いたいという価値観を否定するつもりはないけど、副作用も説明せずに薦めるのはどーかと思う。教えてクン向けに黒ベタフィルタ作ろうと妄想したり、AS テクノロジによる HT 対応を夢想した人間の言うことではないかもしれないが。
Kiraru2002 さんに感謝。VfW での上書きサイズ対策、mp3info 対策、huffyuv/PicVideo 対策の 3 点は 0.3.1 に入れます。WMV9VCM での AVI2.0 出力バグについては帰宅後に調べます。
というわけで修正。拡張 AVI 出力 0.3.1 公開。WMV9VCM の問題は、WMV9 が ICCompress 時に 0x2 というフラグ (詳細不明) を常に出力するため、イコールでの比較ではキーフレームを検出できなくなっていたのが原因だった。
何故だ。何故、自分で作ったプログラムのドキュメントすら書きたくない人間 が、他人の作ったプログラムのドキュメントを書く仕事をする羽目になっているのだ。
この作業は、さらに別の人にメンテナンスをパススルーするために必要なのだと自分を騙そうとしているのだけど……狙い通りにいってくれるかなぁ。なんだか裏目にでそうな気配が濃厚なんだよなぁ。
あー確かに見落としてました。そうか 1000*1000 掛けてるから 2147 超えた辺りでオーバーフローするのか。m2v.aui 読み込みだと 24fps 化しても 2002 が最大なので問題がでずに気が付いてませんでした。AVIMAINHEADER::dwMicroSecPerFrame の算出部分、帰宅後に修正して 0.3.2 を公開します。
当初は 1 行なおすだけーと甘く見ていたのですが、WMV9VCM でテストしてみるとポロポロとバグが見つかり、修正に時間がかかってしまいました。修正点は以下の 3 点です。
特に致命的なのが二つ目の NULL フレーム増加バグです。私がテストした 3.33 sec の 320x240 MPEG-1 某コンテンツは、WMV9VCM 出力で 5.25 sec にまでビデオの長さが増えてくれやがりました。
えーと、とりあえず B VOP なんて嫌いだと八つ当たりしておきます。おやすみなさい。
いい加減 AUF 以下のアレさが気になってきたので、ページ作り直し。途中までは CSS の float を使って列組を実現しようとか努力していたのだけど、IE6 で水平スクロールバーが発生してしまう問題を解決できなったので <table> を使う形に変更。
まぁ列組をするならテーブルの方が素直だしマシだと思うことにしよう。一応 float 版。Firefox 1.0 では問題なく動いてくれたのになーっと。
VFR に関してあれこれ。
AviUtl 上で自動化実装を行う際の実装手段
インタレース解除プラグイン型実装の問題点
入力プラグイン型実装の問題点
というわけで前日のタイトルを「妄想 [1]」に変更。こーゆー話をする時は wiki が便利なんだよなぁ。pukiwiki の設置を考えるべきだろうか。あっという間にアラシで埋まりそうな予感もするけど。
インタレース解除プラグインの func_proc() での fpip->frame が編集時の通し番号で、選択範囲削除等では、AVI との対応関係調整で対処されるため、フレームレート変更が「なし」設定ならば対応関係は簡単に知ることができると判ったので。インタレース解除プラグイン側実装をもう少し詳細な形にしてみる。
AviUtl 本体に VFR 対応してもらうとしたら……
仕事でくたばっているので妄想は一回休み。17 日 に書いた CSS のトラブルの解決編。実は土曜のうちにメールをもらって解決していたのだけど、今まで書きそびれていた。
問題は float を使うと、ウィンドウ領域の外側に奇妙なマージンが発生してしまい、水平スクロールバーが表示されてしまうことだった。どうやらこれは IE が <table> の幅指定の基準として、<div> のマージンを考慮しない BOX 幅を採用していたためらしい。以下図示。
<table> に幅指定して、マージンをトータルで 100% になるように左右に割り当ててセンタリングとか考えていたのだけど、<div> のマージン分 <table> の表示が右にズレて、はみ出した右マージンの分だけ水平スクロールバーが発生してしまったということらしい。
Firefox では外部 BOX のコンテンツ幅を内部の BOX の 100% 幅として扱ってくれるので、問題がでなかったらしい。float ではなく <table> で列組みを行った場合に、何故スクロールバーが発生しなかったのかが謎だけど、とりあえず左右のマージンに auto を指定することで解決したので気にしないことにする。
バグ修正ーバグ修正ー。
以上。
バグ修正。
以上。サンプルを送ってくれた方に感謝。
年末なのでごろごろしている。実際は 23 日辺りからずっとごろごろとしているのだけど、それはそれということで。年明けは 5 日まで休むことにしたので、後1週間程度はごろごろする予定。
拡張 AVI 出力に一件バグレポートが入っている (ver. 0.1.9 までは普通に出力できていたけど、ver. 0.3.3 では DivX 5.1.1 で出力すると絵が崩れるという内容) のだけど、再現できないので困っていたり。
そもそも DivX 5.1.1 自体が今では正規の手段では入手不能だし、2ch の DivX スレッド経由で入手した 5.1.1 をインストールしてみても再現しなかったし。もらったファイルを見る限りでは YUY2 でのデータ受け渡しでの問題のようなのだけど……。同じような現象にあってる方がいたら、情報提供をお願いしたいところ。報告主にも「もう少し情報ください」メールを返信してるのだけど、返事が来ないので。