2013年12月19日木曜日

MPEG4コンテナのmoov/mvhdボックスについて

boxtype:mvhd
moovボックスの子階層であり必須フィールド。MoVie HeaDerの略。
moovボックスペイロードの先頭にあるべきだがmustではなく
間に何らかのデータがあっても読み込めないといけない。
ここには結構重要な情報が書いてある。

Syntaxは次の通り。
aligned(8) class MovieHeaderBox extends FullBox(‘mvhd’, version, 0) {
    if (version==1) {
        unsigned int(64) creation_time;
        unsigned int(64) modification_time;
        unsigned int(32) timescale;
        unsigned int(64) duration;
    } else { // version==0
        unsigned int(32) creation_time;
        unsigned int(32) modification_time;
        unsigned int(32) timescale;
        unsigned int(32) duration;
    }
    template int(32) rate = 0x00010000; // typically 1.0
    template int(16) volume = 0x0100; // typically, full volume
    const bit(16) reserved = 0;
    const unsigned int(32)[2] reserved = 0;
    template int(32)[9] matrix = { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
    // Unity matrix
    bit(32)[6] pre_defined = 0;
    unsigned int(32) next_track_ID;
}
creation_timeとmodification_timeはどうでもいいがtimescaleとdurationは重要。

続きは後日加筆します。

MPEG4コンテナのmoovボックスについて

boxtype:moov
必須フィールドであり、どんなデータがファイル中にどのように格納されているかを示すMPEG4コンテナ中で最も重要なボックスである。
MPEG2においてPAT→PMT→データと辿るように、MPEG4ではここからデータまで辿ることになる。

mdatボックス(動画データや音声データが格納されるボックス)の説明書のような役割をしておりmdatボックスの後に配置される事が多いようだ。
(データを一旦配置してみて、その後にオフセットを書いていくという処理がエンコーダーにとって都合がよいからだろう。)
仕様としてmdatボックスとの順序が規定されているわけではない。

moovボックスには階層の深い子ボックスがたくさんあり、他に比べれば少し複雑なツリーを構成する。ツリー構造は下記のようになる。
 moov(*)
├─mvhd(*)
├─trak(*)
│  ├─tkhd(*)
│  ├─tref
│  ├─edts
│  │  └─elst
│  └─mdia(*)
│      ├─mdhd(*)
│      ├─hdlr(*)
│      └─minf(*)
│           ├─vmhd
│           ├─smhd
│           ├─hmhd
│           ├─nmhd
│           ├─dinf(*)
│           │   └─dref(*)
│           └─stbl(*)
│                ├─stsd(*)
│                ├─stts(*)
│                ├─ctts
│                ├─stsc
│                ├─stsz
│                ├─stz2
│                ├─stco(*)
│                ├─co64
│                ├─stss
│                ├─stsh
│                ├─padb
│                ├─stdp
│                ├─sdtp
│                ├─sbgp
│                ├─sgpd
│                └─subs
├─mvex
│  ├─mehd
│  └─trex(*)
└─ipmc
trakボックスは複数定義することができる。通常は動画と音声一つずつの2トラックだろうが多国語音声など複数持たせても良い。
(*)を付けているのは必須ボックスである。

このエントリでは子階層ボックスの一つずつまでは説明しきれないが、重要なのはstbl以下の階層である。ここがmdatボックスの構造を定義する部分だからだ。
動画と音声を同期させるための時刻情報もここに含まれている。

moov/mvex/trexが仕様上は必須となっているがTMPGEncで出力したファイルを見ると付いていなかった。少し調べただけでは何故か分からなかったが必須ではないのが正しい気がする。
(親ボックスのmvexが必須とされていないのが説明できないので。見てる仕様書が古いのかな・・・)

moov子階層のボックスの順序はmust指定ではないがstrongly recommendでヘッダ類が最初に来るようにとされてるのでmvhdが先頭に来るだろう。
(mustじゃダメなのかな・・・recommendだと実装が面倒なんだけど)

MPEG4コンテナのftypボックスについて

boxtypeがftypでFile TYPeを意味する。
初めに断っておくが、必須フィールドである割にあまり重要なフィールドではない。
ファイル中に最初に出現するBoxである。
This box must be placed as early as possible in the file.
と定義されているのでBoxとして一番前に配置されるが「ファイルの先頭に来る」とは書いてないので注意。
もしftypボックスの前にゴミデータが付いていても読む側は読み飛ばさなければならない。
(MPEG4に限らずパーサーを作る際にファイル先頭にヘッダがあることを前提に組んじゃう人が結構いる。まあ殆どのケースではファイル先頭にあるんだろけど。)

aligned(8) class FileTypeBox extends Box(‘ftyp’) { 
    unsigned int(32) major_brand; 
    unsigned int(32) minor_version; 
    unsigned int(32) compatible_brands[]; // to end of the box 
}

major_brandは広範囲なMPEG4規格の中で例えばモバイル向けのファイルであること等をprofileとして知らせる識別子である。
別にここにデタラメを書いたところで読み込み側は処理を諦めるべきではないし、処理を切り替える必要も無い。
brandの一覧はこのサイトなどで見ることができるが大手ベンダーの自己アピールが鬱陶しいことが分かるくらいだ。
多くの場合はmp42とでもしておけばいいだろう。

minor_versionはこのフィールドを活用される事自体が無さそうだ。
現状全部0で埋めとけばいいだろう。

compatible_brandsは互換性のあるmajor_brandを複数並べられるが、
どうでもいい情報なので空欄でも構わないだろう。
TMPGEncに出力させた例ではmp42,isom,avc1の3つが定義されていた。

参考までにMP4 Readerでの解析例を載せておく。

MPEG4コンテナのBox構造(Atom)について

MPEG4はBoxと呼ばれる単位を入れ子にした構造で表現される。
QuickTime時代の名残でAtomと呼ばれることもある。

入れ子と言ってもファイル中に親子関係が記述されているわけではなく
解析する側が仕様から各Boxの関係を把握して解析する。
Boxのヘッダー構造は以下であるとISO/IEC 14496-12で定義されている。

aligned(8) class Box (unsigned int(32) boxtype, optional unsigned int(8)[16] extended_type) {
    unsigned int(32) size;
    unsigned int(32) type = boxtype;
    if (size==1) {
        unsigned int(64) largesize;
    } else if (size==0) {
        // box extends to end of file
    }
    if (boxtype==‘uuid’) {
        unsigned int(8)[16] usertype = extended_type;
    }
}
基本的には頭4バイトがサイズ情報(この4バイトも含むバイト数)、次の4バイトにBoxの種類を識別するためのboxtype、ヘッダーの後はペイロード部分となる。
The standard boxes all use compact types
(32-bit) and most boxes will use the compact (32-bit) size.
仕様には上記のように書かれているので基本的には32bit空間で表せるサイズがBoxサイズの上限である。 ただし
 Typically only the Media Data Box(es) need the 64-bit size.
動画データを格納するMedia Dataボックスに限ってはサイズが大きいこともあり64bit空間がサイズ上限となる。64bitに統一しても良かったんじゃ?と思わないでもない。サイズ情報はヘッダーも含んだサイズである。

boxtypeはftypボックスならASCIIコードで直接0x66(f),0x74(t),0x79(y),0x70(p)と表現される。バイナリエディタで見たときに分かりやすい。boxtype=uuidの場合はうんたらと書いてあるがuuidはベンダ固有のオプション定義に使うBoxなので読み飛ばして構わない。中身が独自定義なのだから部外者には把握しようがない。


MP4コンテナの中身を調べる

ISO/IEC 13818-1で定義される動画コンテナとしてのMPEG2はよく普及しており
日本語での資料も充実しているがISO/IEC 14496-14で定義されるMPEG4 Part14 コンテナは
情報が多いとは言えない。私もよく知らない。

そもそもはAppleのQuickTimeフォーマットがベースになっているMPEG4 Part12が
元に拡張したものであるらしい。Part14の仕様書はPart12からの拡張部分しか書いてない。
だからPart14の仕様書の前にPart12の仕様書を見ないと訳が分からない。
そもそもPart14での拡張部分なんぞ大半の人は使用しておらず
Part12互換のファイルを作っているんじゃなかろうか。
(調べ始めたばかりで適当な事言ってるので注意)

動画コンテナとしての役割は大きくは2つあると思う。
ストリーミングに関してはひとまず置いておく。
  1. 映像と音声をズレないよう同期させる
  2. ランダムアクセスを可能にする
 1.に関してMPEG2ではDTS、PTS、PCRといったタイムスタンプ情報が充実しており同期に関してこれを越えるフォーマットは存在しないと思われる。MPEG4ではDT(Decoding Time)、CT(Composition Time)というものがそれらに相当する。

2.に関してMPEG2システムでは上記時刻情報を元にシークするしかない。ファイルのお尻のほうのPCRから全体の時間を求め、ファイルポインタを大雑把に進めてから細かい探索を行う。大した処理ではないが非力な組み込み機器では問題になるかもしれない。MPEG4では基本的にオフセット情報で管理されているため直接目的の位置に飛べそうだ。実際に負荷がどれくらい減るのかは知らないが。

MP4コンテナにおける構成を最も大雑把な単位で書くと次のようになる。
その他のデータは読み飛ばしても問題ないようなゴミデータばかりだが先頭にftypボックスが存在することは必須とされている。
ftypも再生に必要なわけではないが多機能すぎるMPEG4システムの内、どこまでを使ったものなのか知るには意味があるかもしれない。
  • moovボックス(メタデータ)
  • mdatボックス(動画・音声データ)
mdatはMedia Dataを指すが、この中には動画・音声データを比較的自由に格納できる。mdatにどのようにデータを格納したかをmoovボックスに記述することで最終的にはアクセスできるようになる。よって動画データ丸ごとの後に音声データを丸ごと繋げる単純な構造でも良いし、それぞれを細切れにして交互に配置しても構わない。

MPEG4の内部を解析するツールとしてWindowsではMP4 Readerが最も便利だった。
下図はTMPGEnc 5から出力したMPEG4(H.264,LC-AAC)ファイルの解析結果である。

moovボックスの下に2つのtrakボックスがあるのが分かる。それぞれが動画と音声トラックを示している。moovボックス直下にmvhdボックスがあり、図の右側に出ているがここにファイル全体の時間の長さが書いてある。一分の長さのコンテンツなのでduration=60714(ミリ秒)と出ている。こんなに浅い場所を調べるだけで長さが分かるのはMPEG2から比べれば格段に楽だ。MPEG2ではファイルの先頭と最後のタイムスタンプの差分から推測するしかなかった。タイムスタンプは27MHz周期で24時間弱程度で一周してしまう点もやっかいだった。MPEG2-PSはMPEG2-TSとの互換性の問題で長さのフィールドが無いのかもしれないが。

再生にあたって重要な情報はstts/ctts/stsc/stsz/stcoだ。これらがmdatボックスのどの位置に何の情報が書かれているかと時刻情報が書かれている。時刻情報といってもMPEG2のように基準時刻からの経過時間が書かれているのではなく前フレームからのオフセット時間だけが書かれているようだ。sttsがDecode Timeのテーブルになるが、このファイルの場合は「全フレームの間隔は2000」と書いていただけだった。可変フレームレートだともっとややこしい表になるかもしれない。
cttsはフレームの並び替えの問題もあると思うのだがもっと複雑だった。

とりあえず今回はここまで。
機能てんこもりにした挙句に一部しか使われてないから
わざわざMPEG2システムから乗り換えるほどのメリットがあるかは疑問だ。
地デジにしてもAVCHDにしてもMPEG2-TSなのは互換性の問題だけではないだろう。