[動画] AVI ファイルの話し : してログ

[動画] AVI ファイルの話し

動画 20141130

訳あって AVI コンテナの内部構造について勉強していたところ、大きいデータにスケールアップできなくて利用を断念したというお話し。 Time Lapse で大量に撮った JPG の格納に、ZIP では無く動画ファイルを活用しようと考えました。 ZIP では、そのまま動画編集ソフトで扱えないし、動画ファイルにしてしまうと、EXIF などの情報が失われたり、再エンコードで劣化してしまうからです。 しかし、単純に jpeg をフレーム毎に記録しているだけの mjpeg ならば、目的を達成できるのでは無いかと言うわけです。 試しに、ffmpg で -qscale 0(-sameq の代わり)を付けて mjpeg に出力してみたところ、容量が大幅に減ってしまうことから、EXIF や品質を維持していないように見えます。 このような状況から、自前で動画フレームを結合し、AVI コンテナに収めるようなツールを作ることにしました。

AVI ファイルの構造

AVI ファイルは、RIFF という形式で表され、チャンクとリストにより構成されています。 それらを組み合わせて、AVI ヘッダーやストリームを格納する仕組みです。 詳しい仕様は、AVI ファイル フォーマット を参照してください。 これによると、Motion JPEG を格納するには、AVIメインヘッダー、ストリームヘッダー、ストリームフォーマット、ストリームデータ、インデックスエントリの 5 つのチャンクを出力すれば良いことになります。 この時ストリームデータは、JPEGファイルをそのまま格納できるので、EXIF や品質を落とさないで保存することができます。

これらの情報から、連番 JPEG 画像を AVI コンテナに収めるツールを作成しました。 基本的には、下記のサイトや仕様通りに作成すれば良いのですが、いくつかポイントがありましたので列挙しておきます。

補足情報
AVIヘッダーの dwFlags ビット配置は?
ドキュメントでは曖昧な記述でどのようなビットをセットするのか分かりません。これは、Aviriff.h に定数定義があります。定数の中身については、Microsoft ライブラリのソースコードを見ないと分からない場合があるので、異なる言語で実装する場合でも、ソースコードを取得して読んでみることをお勧めします。
ストリームデータの記述方法
JPEG ファイルを羅列するものと思いましたが、各 JPEG ファイルを 00dc チャンクで包むようです。ひとつの 00dc チャンク内に JPEG を羅列した場合、エクスプローラのプロパティで AVI ヘッダを認識せず、サムネイルも表示されません。ただし、VCL media player や TMPGEnc などで再生または認識できました。恐らく AVI2.0 のオーバーヘッド節約モードとかが関係していると思われます。
JUNK チャンクを挿入して 2048 バイト境界に配置する?
そのような説明がされているサイトがありましたが、特に配慮する必要は無いように思います。
AVI は 2GB 以下の容量しか対応しない?
AVI には、Video for Windows(AVI1.0) の時代のものと、OpenDML AVI File Format Extensions(AVI2.0) の拡張仕様の2種類があり、AVI1.0 は 2GB まで、AVI2.0 は無制限(ファイルシステムの限界より大きい)のようです。なお、AVI1.0 ではチャンクサイズやオフセット値などが DWORD 型になっているので、4GB までは行けそうな気もします。
dwMicroSecPerFrame には何を入れたらいい?
フレームあたりのマイクロ秒を入れます。下記の計算式に当てはめれば良いです。60fps の場合は、16666 になります。
1 / fps * 1000 * 1000
dwMaxBytesPerSec には何を入れたらいい?
秒あたりの最大バイト数を入れます。ドット数とビット深度、及びフレームレートから、「width * height * 3 * fps」の計算式でしょうか? しかし、これだとかなり大きくなるし、ffmpg で作った似たような AVI では 2500 という比較的小さな値が入っていました。
dwSuggestedBufferSize には何を入れたらいい?
推奨バッファサイズを入れます。dwMaxBytesPerSec の数十倍程度を入れておくべき? よく分かりませんが、ffmpg で作ったものには 0x100000 が入っていました。
AVI 出力ツールの構造

基本的にはチャンクとリストを単位に順番に出力していく構造になります。 各チャンクまたはリストの出力は、下記のような手順が単純で効率が良いです。

  1. FOURCC を出力する
  2. ファイルポインタを記憶する
  3. チャンクサイズを 0 で出力する
  4. データを出力する
  5. (2)の位置との差からチャンクサイズを計算し、正しいサイズに(3)を書き換える
容量限界が 2GB なわけ

AVI(1.0)が 2GB までなのは、チャンクサイズを 4 バイトで表すためでした。 4 バイトで表すことのできるサイズは、符号ありで 2GB、符号なしで 4GB になると思います。 また、実装上 fseek など使う場合は、引数の long int 型が 2GB の制限を生んでしまいます。 従って、AVI を扱うソフトウェアはフォーマットの上でも、実装の上でも 2GB に制限されていると思われます。

OpenDML AVI File Format Extensions(AVI2.0)

仕様をざっくり読むと、1GB 以下では単一の RIFF AVI チャンクを持つ AVI ファイルと同じようです。 1GB 以上では RIFF AVI チャンクと、複数の RIFF AVIX チャンクで構成されるようです。 基本的には、1GB 以下になるように、複数の RIFF に分けて出力すれば良さそうです。 なぜ、1GB 以下なのかはナゾですが、実装としては 1GB 以下で RIFF ファイルを分割して出力し、最後に1本のファイルに結合するのが楽そうです。

0 件のコメント
名前:
コメント: