こんにちは、クラウド推進技術部開発第1グループの石川です。
突然ですが、トランスマックス (Transmux) とはなんでしょうか?
動画は内容を表すコーデック (H.264, H.265, mp3, AAC, ...) と、コーデックを多重化するコンテナ (MPEG-TS, fmp4, FLV, ...) で構成されています。
トランスマックスは、コーデックはそのまま、コンテナを移し替えることを指します。
ここまでの話だと、全然フロントエンドと関係ないと思われるかもしれませんが、実はブラウザでのメディア再生には欠かせない技術になります。 何故かというと、ブラウザでの視聴の際に、非対応なコンテナからの変換で fmp4 へ JavaScript 上でトランスマックスしているからです。
今回は、実はよく使われているが説明の少ない、トランスマックスの世界を説明します。
コンテナについて
トランスマックスを説明する前に、まず動画のコンテナ自体について説明します。
よく使われる配信系のコンテナとしては、 MPEG-TS, fmp4, FLV が有名どころですので、それぞれ簡単に解説します。
MPEG-TS
MPEG-TS は 188 バイト (ヘッダ 4 バイト) で構成される TS パケットで多重化します。
MPEG が策定した MPEG-2 System の中の トランポートストリーム (TS) として定義されているため、略称として MPEG-TS が使われます。
特徴としては、金太郎飴みたいな 188 バイトのパケットで構成されているため、伝送路で誤りや欠落が生じるような場合でも、検知、回復した際の復帰ができます。
また、ユーザ定義 (放送系に特に多い) のメタデータが多く、メタデータも豊富なコンテナです。
デジタルテレビなどの放送や、HLS での配信でよく使われています。
fmp4
fmp4 は mp4 (ISOBMFF) コンテナを フラグメント化 (fragmented) して多重化します。
元々の mp4 は MPEG-4 ISO base Media File Format で策定されたコンテナであり、Box を入れ子にした構造によって、各メディアを多重化します。
fmp4 は mp4 では 動画の一部分だけを利用する、ということが難しいために、フラグメント化 (fragmented) して分割し、細切れに利用しやすくしたものです。
実際の配信では、派生した CMAF (ISO/IEC 23000-19) を用いることが多いですが、それらを総称して fmp4 と呼びます。
特徴としては、mp4 (ISOBMFF) は、QTFF (QuickTime File Format) が元であり、QTFF 自体が編集用フォーマットとして生まれたものです。
ですので、コーデックに依存せず編集するために、映像/音声に関する様々なメタ情報が充実しています。
配信では、上述した CMAF による HLS や MPEG-DASH での配信でのコンテナとしてよく使われています。 *1
FLV
FLV は FLVヘッダ(FLVの構成を示す固定長) と FLVタグ(可変長) で構成されており、FLV タグによって、映像や音声、スクリプトデータなどを多重化します。
FLV は Flash Video の略で、Flash のストリーミング用フォーマットであり、現時点では Adobe のプロプライエタリなコンテナです。
データを ECMAScript オブジェクトをシリアライズした、AMF (Action Message Format) として送る点が非常にユニークです。
現時点でも RTMP での動画コンテナとしてよく使われているコンテナになります。
なぜブラウザ内で fmp4 に変換するの?
動画配信では、ライブで動画をどんどん追加していく、巻き戻って最初から見る、などの操作をする事があります。
この際に、動的なバッファ管理が必要になりますが、それを可能にする API が MSE (Media Source Extension) です。
MSE で扱いやすい形式が fmp4 と webm なのですが、 webm は H.264, H.265, AAC には対応していません。
このため、基本的に配信で使われる H.264/AAC の組み合わせは、fmp4 へとトランスマックスされます。
MSE (Media Source Extension)
MSE (Media Source Extension) は、以下の 2 つのクラスで構成されています
- MediaSource: 動的なバッファを管理して仲介するクラス
- SourceBuffer: バッファ自体に相当するクラス
基本的に SourceBuffer で受け付けるコンテナは主に webm, fmp4 が主流です。
しかし、上述の通り、トランスマックスする際には、コンテナ自体の対応フォーマットの多さから fmp4 にする事が多いです。
余談ですが、Chrome 108 からは、WebWorker 内で MSE の処理ができるようになり、バッファ管理の処理を全て WebWorker 側に記載できるようになりました。
H.264 と AAC の構成
配信で使われる主要なコーデックは H.264 (映像) と AAC (音声) です。
H.264, AAC 共にトランスマックスで大きく形式が変わるため、これらも説明します。
H.264 の構成について
H.264 は NAL*2ユニット で構成されています。
その NALユニット のコンテナへの格納の仕方は、以下の 2 種類となります。
- NAL File Format: FLV, fmp4 (ISOBMFF) で使われている形式です
- Byte Stream Format (AnnexB): MPEG-TS で使われている形式です
Byte Stream Format で送られた場合には、MSE で管理するために NAL File Format に変換する必要があります。
ここから 2 種類の格納の仕方について説明します。
NAL File Format
各 NAL ユニット (EBSP *3 ) の先頭に、別途記載されたバイト数で EBSP の長さを Big Endian で記載するフォーマットです。
例えば、mp4 (ISOBMFF) や FLV では、AVCDecoderConfigurationRecord に記載する lengthSizeMinusOne から取得します。
Byte Stream Format (AnnexB)
各 NAL ユニット (EBSP) の先頭に、スタートコード (0x00000001 or 0x000001) を目標として記載するフォーマットです。
ランダムアクセス可能な先頭の NALu (AUD: Access Unit Delimiter など) では 0x00000001 の 4 バイト、
それ以外の時には 0x000001 の 3 バイトのスタートコードを付加します。
AAC の構成について
AAC のコンテナへの格納の仕方は、主流なのは以下の 3 種類となります。
- RAW: FLV, fmp4 (ISOBMFF) で使われています
- ADTS: MPEG-TS でよく使われています
- LATM/LOAS: 次世代系の放送で使われる事があります
さらっと言うと、生の AAC を別の構成情報と共に送る方式と、ADTS ヘッダ (固定長) を付加する方式と、可変長のヘッダを付加する形式です。
これも ADTS や LATM/LOAS で送られた場合には、MSE で管理するため RAW に変換する必要があります。
それぞれについて、以下で説明します。
RAW
ADTS が付加されていない、生の AAC ペイロードで構成されるフォーマットです。
例えば、mp4 (ISOBMFF) や FLV では、別途 AudioSpecificConfig に構成情報を記載します。 *4
このフォーマット単体では再生はできません。
ADTS (Audio Data Transport Stream)
AAC ペイロードに ADTS (Audio Data Transport Stream) ヘッダがついたフォーマットです。
ADTS ヘッダに音声の構成情報が載っているため、ADTS フォーマットであれば再生が可能です。
LATM/LOAS (Low-overhead MPEG-4 Audio Transport Multiplex / Low Overhead Audio Stream)
AAC ペイロードを可変長のヘッダを持つ LATM で多重化し、同期のために LOAS を使う形式です。
LATM/LOAS は MPEG4 Audio で規定されているため、 AudioSpecificConfig を付けた上でのストリーム伝送ができます。
ADTS は MPEG2 で規定されているため、マルチチャンネルオーディオのチャンネル数で対応していないものもあります。
そういった、新しい規格を使う必要がある場合に、LATM/LOAS が使われます。
このため、次世代の配信において使われることがありますが、ここでは詳しくは説明しません。
トランスマックス
本題となる、コンテナのトランスマックスの話にようやく入ります。
ここでは、MPEG-TS に入っている H.264/AAC を fmp4 に入る形式に移し替える、という例で説明します。
基本的な流れは、MPEG-TS の PAT *5, PMT *6 から対象のコーデックの PES を探し、そこからコーデックの Access Unit を取り出します。
そこで取得したコーデックの内容を変換し、fmp4 へと詰め直す作業を行います。
fmp4 に詰め直す部分コードは、OSS でよく使われている実装として hls.js/mp4-generator.ts や mux.js/mp4-generator.js があります。
ここでは、上記のコードの補完の説明にとどめ、各コンテナの詳細なコーデック情報の取得、構成方法については省略し、コーデックの変換部分について説明します。
H.264 の変換
MPEG-TS では Byte Stream Format で H.264 のデータが伝送されるため、NAL File Format に変換するという作業が必要です。
このために、スタートコードがあるかどうかを 1 バイトづつ見て、NAL ユニットを分離します。
NAL File Format で必要な AVCDecoderConfigurationRecord は Byte Stream Format で送られる SPS, PPS の情報から作成できます。
作成した AVCDecoderConfigurationRecord は fmp4 では Initialization Segment の avcC
に記載します。
mp4dump での表記で示すと moov/trak/mdia/minf/stbl/stsd/avc1/avcC
という階層で avcC を配置します。
AAC の変換
MPEG-TS では ADTS で AAC のデータが伝送されるため、AudioSpecificConfig + RAW AAC に変換する作業が必要です。
ADTS なので、同期信号を元に先頭を特定して、そこからヘッダ情報に従って RAW AAC を分離します。
RAW AAC での再生に必要な AudioSpecificConfig は ADTS ヘッダの情報から構成します。
作成した AudioSpecificConfig は fmp4 では esds
内の DS Descriptor に記載します。
mp4dump での表記で示すと moov/trak/mdia/minf/stbl/stsd/mp4a/esds
になります。
おわりに
トランスマックス周りの前提知識と、MPEG-TS での H.264/AAC 配信のコンテナを fmp4 へトランスマックスする際の処理を紹介しました。
hls.js や mux.js などで行う MPEG-TS の動画を再生する際の変換は、大雑把にいうと、こんなものになります。
ちなみに、トランスマックスのボトルネックは H.264 の Byte Stream Format を NAL File Format へと変換する部分になります。
何故かというと、ビットレートのほとんどが映像である上に、1バイトずつスキャンするためビットレートに比例する計算量があるためです。
高画質の場合に CPU 負荷が高くなる原因は、この部分の変換がほぼ支配的になります。
最初から fmp4 であったり、NAL File Format であると、処理の計算量は大幅に下がります。
また、MSE for WebCodecs によってコーデックが直接入れられるようになれば、コンテナ変換について考えなくても良くなります。
WebCodecs 共々、今後が楽しみで期待が膨らみますね。
今回は、ほんのさわりであるため、各コンテナの詳細、構成方法についてなど、省略した部分が多数あります。
実際に fmp4 を作るなど、実践的な内容は、機会があったら少しづつ書いていきたいところです。
*1:実は HLS では CMAF 準拠ではない fmp4(ISOBMFF) も再生可能でして、mediastreamsegmenter にも ISO/CMAF の区別があります
*2:Network Abstraction Layer の略で伝送プロトコルによらないデータ伝送部分の決め事の部分です
*3:Encapsulate Byte Sequence Payload の略でNALuのバイト列がエスケープされたものです
*4:主に使われているのは MPEG4 Audio 扱いですので AudioSpecificConfig と説明しています
*5:Program Association Table の略で、各 Program の PMT の PID を保持するテーブルです
*6:Program Mapping Table の略で、各 Program を構成する ES の PID を保持するテーブルです