34
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ライブストリーム向けのwebmコンテナについて

Last updated at Posted at 2017-01-09

webmを業務で触る時に、初心者向けの資料を見つけられなくて困ったので学習メモを公開します。webmについてもっと知見が出てきてほしい :pray:

webmの概要

webmはGoogleが策定した規格で、matroska(.mkv)のサブセットです。
映像の再生に関してはmatroskaとほぼ同じなのでwebの仕様について学ぶときはmatroskaの規格を読むことになります。

見ておくと良いwebm(とmatroska)の資料

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です。学習の足がかりにどうぞ。

34
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?