webmを業務で触る時に、初心者向けの資料を見つけられなくて困ったので学習メモを公開します。webmについてもっと知見が出てきてほしい
webmの概要
webmはGoogleが策定した規格で、matroska(.mkv)のサブセットです。
映像の再生に関してはmatroskaとほぼ同じなのでwebの仕様について学ぶときはmatroskaの規格を読むことになります。
見ておくと良いwebm(とmatroska)の資料
- matroskaの仕様 タグごとにwebmで使えるかどうかが書いてあるので便利です。
- 各要素のダイアグラム 要素の構成を図で理解することが出来ます。
- w3cで定義されているwebmのストリームフォーマット webmをMedia Source Extensionsでブラウザに渡す際のフォーマットが定義されています。
- webmコンテナ開発者のためのガイドライン ブラウザによってうまく動かないというときはこの資料を当たると解決することが多いです。
EBML
EBMLはmatroskaのために開発されたバイナリフォーマットです。XMLによく似ていて要素を入れ子にすることができます。
EBML Element
一つの要素(Element)は[要素ID + データの長さ(VINTエンコードされた数値) + データ]から成り立ちます。
<EBML>
<EBMLVersion>1</EBMLVersion>
</EBML>
という要素の場合、
[0x1A,0x45,0xDF,0xA3][0x84][0x42,0x86][0x81][0x01]
=> [EBML要素のID][(子要素を含めた)データの長さ=4byte(VINT encoded)][EBMLVersion要素のID][データの長さ=1byte(VINT encoded)][1(EBMLVersion)]
と表現されます。
VINT(Variable size integer)
可変長の数値を圧縮しつつ扱うためのもので、RFCではVINTと呼ばれているエンコード方式です。
matroska specificationでは"coded with an UTF-8 like system"や"EBML like format"と呼ばれたりしています。
Big endianで先頭の1が何ビット目に立っているかが数値の長さを表現します。
0 to 2^7の場合は1byteなので1byte目に1が立ちます。
1xxx xxxx
実データは7bit
xxx xxxx
4byteまでの例は以下の通りです。(RFCより引用)
width Size Representation
1 2^7 1xxx xxxx
2 2^14 01xx xxxx xxxx xxxx
3 2^21 001x xxxx xxxx xxxx xxxx xxxx
4 2^28 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx
長さ不定の場合
ライブストリームの場合は要素の長さが確定しないままメディアデータを構築する必要があります。そのためVINTでは0x01FFFFFFFFFFFFFF
が「長さ不定」を表す予約定数になっています。
Element ID自体もVINTエンコードされている
ちなみにElement IDの値自体もVINTエンコードされています。デコーダーを書くときに便利な仕様ですね。
EBML要素のIDの場合(0x1A45DFA3)
0001 1010 0100 0101 1101 1111 1010 0011
ライブストリーム向けの構成
ブラウザでライブストリーム向けの映像を作る場合は以下のタグだけで再生できます。
映像と音声のコーデック・トラック数・サイズが固定の場合はClusterまでの要素はストリームの内容にかかわらず一定になります。 ですので映像と音声を受け取ってwebmに詰める処理を書く場合はClusterだけ作っていけば良いことになります。
XML風に表現した例:
<EBML>
<EBMLVersion>1</EBMLVersion>
<EBMLReadVersion>1</EBMLReadVersion>
<EBMLMaxIDLength>4</EBMLMaxIDLength>
<EBMLMaxSizeLength>8</EBMLMaxSizeLength>
<DocType>webm</DocType>
<DocTypeVersion>4</DocTypeVersion>
<DocTypeReadVersion>2</DocTypeReadVersion>
</EBML>
<Segment>
<Info>
<TimecodeScale>1000000</TimecodeScale>
<MuxingApp>ttLibC</MuxingApp>
<WritingApp>ttLibC</WritingApp>
</Info>
<Tracks>
<TrackEntry>
<TrackNumber>1</TrackNumber>
<TrackUID>1</TrackUID>
<CodecID>V_VP8</CodecID>
<TrackType>1</TrackType>
<Video>
<PixelWidth>640</PixelWidth>
<PixelHeight>360</PixelHeight>
</Video>
</TrackEntry>
<TrackEntry>
<TrackNumber>2</TrackNumber>
<TrackUID>2</TrackUID>
<CodecID>A_OPUS</CodecID>
<TrackType>2</TrackType>
<Audio>
<SamplingFrequency>48000.0</SamplingFrequency>
<Channels>2</Channels>
</Audio>
<CodecPrivate>4f7075734865616401020000bb800000000000</CodecPrivate>
</TrackEntry>
</Tracks>
<!-- ↑ここまで決め打ちで良い. -->
<Cluster> <!-- 長さは無限大に -->
<timecode>0</timecode>
<SimpleBlock>
<track>2</track>
<timecode>0.0</timecode>
<keyframe/>
<data>
<!-- データのバイナリ -->
</data>
</SimpleBlock>
<!-- 以下SimpleBlock timecodeが16bitを超えない範囲で続く -->
</Cluster>
<Cluster>
<!-- .... -->
</Cluseter>
</Segment>
各要素の詳細は仕様を見てもらうとして、重要な要素について簡単に解説します。
Cluster
ClusterはBlockのコンテナ要素です。
<Cluster>
<timecode></timecode>
<SimpleBlock />
<SimpleBlock />
<!-- ... -->
</Cluster>
サイズ
例えばライブストリーミングなどでSimpleBlock単位でサーバーから受け取る場合、Clusterのサイズは「長さ不定」にします。
後述するとおりSimpleBlockのtimecodeがsint16を超えるまではいくつでもSimpleBlockをClusterに詰めることができます。
timecode
クラスターを再生する時刻を映像の再生開始からの相対位置で表します。数値の単位はEBMLヘッダで定義されます。
SimpleBlock
SimpleBlockは映像・音声の各フレームを表現する要素です。
track number
トラックナンバーです。 VINTでエンコードする必要があります。
timecode
SimpleBlockの再生時刻をクラスターの開始からの相対時刻で表した数値です。単位はEBMLヘッダで定義されます。
長さはsigned int 16で固定です。 この範囲を超える場合は新しくClusterを開始する必要があります。
flags
表現するフレームの情報を1byteに詰めたフラグです。 以下の状態を1byteで表現します。
- キーフレームかどうか
- 非表示のフレームかどうか
- lacing(一つのBlockに複数のデータをつめることでサイズを減らすための仕組みらしい?)
通常はキーフレームかどうかを表現するだけで事足りるので
- キーフレーム(opusの場合は常にキーフレーム)の場合は 0x80
- インナーフレームの場合は 0x00
と表現すれば良いはずです。
その他: EBMLを構築するための簡単なDSLライブラリを作った
JavaScript向けのEBML parserは結構見つかるのですが、builderが見つからなかったので作りました。
simple-ebml-builderです。学習の足がかりにどうぞ。