MPEG-DASH におけるセグメントの URL とタイムスタンプ

# はじめに

HLS のプレイリストと同じように、 MPEG-DASH の MPD からはセグメントの URL や属性情報を知ることができます。
しかし、 URL やタグがシンプルに記述される HLS のプレイリストと違い、 MPD を読むのは少し難易度が高いと言えます。
本稿では MPD のセグメントに関する事柄を簡単に整理します。

# 参考にすべき MPD

[DASH Industry Forum](https://dashif.org/) では [Reference Client](https://reference.dashif.org/dash.js/) とともに様々なストリームが用意されており、これらの MPD の内容は大いに参考になります。

https://reference.dashif.org/dash.js/latest/samples/dash-if-reference-player/index.html
（ "Stream" と書かれたプルダウンメニューでサンプルを選ぶことができます。）

ぜひ後述する内容と照らし合わせながら MPD を読んでみてください。

# BaseURL

BaseURL タグがある場合、セグメントのパスはそこに書かれた URL からの相対パスとして解釈します。

```xml
<BaseURL>http://localhost/mystream/hd/</BaseURL>
```

MPD からの相対パスを書くことも可能です。

```xml
<BaseURL>./hd/</BaseURL>
```

全てのセグメントが 1 つのファイルにまとまっている場合は BaseURL に MP4 の完全な URL を書くこともできます。

```xml
<BaseURL>http://localhost/mystream/video.mp4</BaseURL>
```

この場合、プレイヤーはレンジリクエストを使ってセグメントを取得します。
後述する SegmentBase か SegmentList/SegmentURL と組み合わせることでバイトレンジの情報をプレイヤーに伝えることができます。

# SegmentTemplate

ライブのプロファイルでは SegmentTemplate というタグがよく使われます。
これはライブ、 VOD のどちらの用途にも使えます。
筆者が開発に携わっている ABEMA では全ての MPD で SegmentTemplate を使っています。

## media 属性と initialization 属性

SegmentTemplate タグの `@media` 属性と `@initialization` 属性がそれぞれメディアセグメント（moof や mdat を含んだ MP4）と初期化セグメント（ftyp や moov を含んだ MP4）を指します。
しかし、これらには通常 `$` で囲まれたテンプレート（プレースホルダ）が埋め込まれています。

```xml
<SegmentTemplate 
    media="$RepresentationID$/$Time$.mp4"
    initialization="$RepresentationID$/init.mp4">
```

テンプレートは 5 種類ほどありますが `$RepresentationID$` と `$Time$` あるいは `$Number$` を使うケースが多いでしょう。
`$RepresentationID$` の値はその名前の通り Representation タグの `@id` 属性の値です。
次の例では SegmentTemplate と同じ階層に Representation タグが 2 つあります。

```xml
<AdaptationSet contentType="video" mimeType="video/mp4" segmentAlignment="true">
    <SegmentTemplate timescale="90000" media="$RepresentationID$/$Time$.mp4" initialization="$RepresentationID$/init.mp4">
    <Representation id="video-hd" bandwidth="2000000" frameRate="30000/1001" height="720" width="1280" scanType="progressive" />
    <Representation id="video-sd" bandwidth="1000000" frameRate="30000/1001" height="480" width="854" scanType="progressive" />
</AdaptationSet>
```

`video-hd` や `video-sd` を `@initialization` 属性の `$RepresentationId$` へ当てはめれば `video-hd/init.mp4` 、 `video-sd/init.mp4` となり、それぞれ HD の初期化セグメント、 SD の初期化セグメントを指します。
では `@media` 属性の `$Time$` には何が入るのでしょうか。
メディアセグメントには通常 `$Time$` か `$Number$` のどちらか一方が使われますが、それらに当てはめる値の求め方を以降で説明します。

## SegmentTimeline を使う場合

SegmentTimeline はセグメントの相対的な時間と長さを列挙するために使います。
その例を次に示します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" availabilityStartTime="1970-01-01T00:00:00Z" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="dynamic" minBufferTime="PT5.000000S" publishTime="2021-10-28T13:07:58Z" minimumUpdatePeriod="PT5.000000S" timeShiftBufferDepth="PT60.000000S" suggestedPresentationDelay="PT15.000000S">
  <BaseURL>http://localhost/mystream/</BaseURL>
  <Period id="1" start="PT1609426800S">
    <AdaptationSet mimeType="video/mp4" segmentAlignment="true">
      <SegmentTemplate timescale="90000" presentationTimeOffset="10786776" media="$RepresentationID$/$Time$.mp4" initialization="$RepresentationID$/init.mp4">
        <SegmentTimeline>
          <S d="357357" t="11771760" />
          <S d="360360" r="3"/>
          <S d="357357" />
        </SegmentTimeline>
      </SegmentTemplate>
      <Representation id="video-hd" bandwidth="2000000" frameRate="30000/1001" height="720" width="1280" scanType="progressive" />
      <Representation id="video-sd" bandwidth="1000000" frameRate="30000/1001" height="480" width="854" scanType="progressive" />
    </AdaptationSet>
    <AdaptationSet mimeType="audio/mp4" segmentAlignment="true">
      <SegmentTemplate timescale="48000" presentationTimeOffset="5752947" media="$RepresentationID$/$Time$.mp4" initialization="$RepresentationID$/init.mp4">
        <SegmentTimeline>
          <S d="191488" t="6278272"/>
          <S d="192512" r="4"/>
        </SegmentTimeline>
      </SegmentTemplate>
      <Representation id="audio-high" bandwidth="190000">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
      </Representation>
      <Representation id="audio-low" bandwidth="64000">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="1" />
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>
```

SegmentTimeline タグの中には S タグが書かれ、これらが一つひとつのセグメントを指します。
ただし、 `@r` 属性が非 0 の場合はその回数リピートすることを示します。（負の値が書かれる場合もあり、これは無制限に繰り返されることを示します。） `r="3"` は +3 件、つまり 4 件の同じ長さのセグメントがあることを示しています。
`@d` がセグメントの長さ、 `@t` がセグメントの先頭のタイムスタンプを表します。
2 件目以降は `@t` を省略することができ、前のセグメントの `@t` に `@d` を足した値で求められます。
この `@t` の値を `$Time$` に当てはめることでメディアセグメントの URL が完成します。
ビデオの最初のセグメントであれば `http://localhost/mystream/video-hd/11771760.mp4` や `http://localhost/mystream/video-sd/11771760.mp4` となります。

ここまででセグメントの URL はわかりましたが、時間に関する値についてもう少し整理しておきましょう。
`S@t` や `S@d` は整数で書かれますが、 SegmentTemplate の `@timescale` 属性の値で割ると秒になります。
つまり、上記の例でビデオの最初のセグメントの長さは `357357 / 90000 = 3.97` 秒、タイムスタンプは `11771760 / 90000 = 130.797` 秒となります。
`@timescale` と `@t` の値は MP4 の中に書かれている値と同じものを書きます。
そして、 SegmentTemplate の `@presentationTimeOffset` を `@t` から引くと Period の先頭からの経過時間になります。
次の式は先程の MPD において、 **ビデオの最新のセグメントが Period の開始時刻から何秒経過しているか** を表しています。

```
(11771760 + 357357 + 360360 * 4 - 10786776) / 90000 = 30.93 秒
```

Period が始まった時刻は `MPD@availabilityStartTime` に `Period@start` を足して求まります。
つまり Period の先頭は 2021 年 1 月 1 日 00:00:00 JST、ビデオの最後のセグメントは 2021 年 1 月 1 日 00:00:30.930 JST となります。

## SegmentTemplate のみを使う場合

先述の通り SegmentTimeline を使えば各セグメントのタイムスタンプや長さを書くことができます。
一方で全てのセグメントの長さが常に同じであれば SegmentTimeline を使わない方法もあります。

```xml
<AdaptationSet contentType="video" mimeType="video/mp4" segmentAlignment="true">
    <SegmentTemplate duration="2" startNumber="1000" initialization="$RepresentationID$/init.mp4" media="$RepresentationID$/$Number$.mp4" />
    <Representation id="video-300k" bandwidth="300000" codecs="avc1.64001e" frameRate="30" height="360" width="640" />
</AdaptationSet>
```

この例では `@duration` がセグメントの長さを表しています。つまりセグメントは 2 秒ごとに存在します。
`$Number$` テンプレートが使われており、ここに Period の開始からのセグメントの番号を入れます。
セグメントの番号は `@startNumber` で示された値から開始します。この例では 1000 です。
したがってメディアセグメントのパスは以下のようになります。

```
video-300k/1000.mp4 --- 最初の 2 秒
video-300k/1001.mp4 --- 2 件目の 2 秒
video-300k/1002.mp4 --- 3 件目の 2 秒
    :
video-300k/1099.mp4 --- 100 件目の 2 秒
    :
```

今回示した例では、 SegmentTimeline を使った場合に `$Time$` テンプレート、そうでない場合に `$Number$` テンプレートを使いました。
しかし、そうである必要はなく、例えば SegmentTimeline の場合に `$Number$` を使うことも可能です。
ファイル名に番号を含めるか時間を含めるか、パッケージャにとって都合の良い方を用いることができます。

## Representation との位置関係

先述の例では SegmentTemplate と Representation が同じ階層にありましたが、 Representation の子供に SegmentTemplate を配置することも可能です。

```xml
<Representation id="video-hd" bandwidth="2000000" frameRate="30000/1001" height="720" width="1280" scanType="progressive">
    <SegmentTemplate timescale="90000" presentationTimeOffset="10786776" media="hd/$Time$.mp4" initialization="hd/init.mp4">
        ...
    </SegmentTemplate>
</Representation>
<Representation id="video-sd" bandwidth="1000000" frameRate="30000/1001" height="480" width="854" scanType="progressive">
    <SegmentTemplate timescale="90000" presentationTimeOffset="10786776" media="sd/$Time$.mp4" initialization="sd/init.mp4">
        ...
    </SegmentTemplate>
</Representation>
```

この場合は Representation ごとに SegmentTemplate を書くことになります。
これは例えばセグメントのパスをテンプレートでうまく表現できない場合に有用でしょう。
しかし、特に理由がなければ `$RepresentationID$` テンプレートを使って、 AdaptationSet ごとに 1 つの SegmentTemplate を書いたほうが MPD のサイズをおさえられます。

# SegmentList と Initialization, SegmentURL

SegmentURL タグは各セグメントの URL あるいはバイトレンジを示します。
使用する場合は次のように SegmentList タグの子供に Initialization タグと SegmentURL タグを含めます。

```xml
<SegmentList>
    <Initialization sourceURL="init.mp4" />
    <SegmentURL media="0.mp4" />
    <SegmentURL media="1.mp4" />
    <SegmentURL media="2.mp4" />
</SegmentList>
```

これは HLS のプレイリストとも似ていて、比較的理解しやすいでしょう。
SegmentURL にはバイトレンジを書くこともでき、この点も HLS と似ています。

# SegmentBase

単一の MP4 の URL を　BaseURL に書き SegmentList や SegmentTemplate を使わない場合、プレイヤーは MPD から直に各セグメントのバイトレンジを知ることはできません。
MP4 の中にセグメントのバイト位置を示す情報（具体的には sidx Box）がある場合、そのバイトレンジを SegmentBase で示すことができます。

```xml
<BaseURL>http://localhost/sample.mp4</BaseURL>
<SegmentBase indexRange="896-1730"/>
```

上記の例ではプレイヤーは最初に 896 バイト目から 1730 バイト目を取得します。
そこには各セグメントの時間やオフセットの情報が書かれており、それを使って各セグメントへのレンジリクエストを実行します。

# おわりに

MPD は色々な書き方ができますが、その反面理解が難しいと言えます。
自身でプレイヤーやパッケージャーの開発をしている方は限られると思いますが、その周辺の開発やメンテナンスをするエンジニアでもセグメントの URL が分かる程度には読めるようになっておきたいところでしょう。
本稿では MPD からセグメントの URL やバイトレンジをどう読み取ればよいかだったり、時間の計算方法などの観点で簡単に整理しました。

# 参考文献

- ISO/IEC 23009-1 Dynamic adaptive streaming over HTTP (DASH) - Part 1: Media presentation description and segment formats
- [フレームレートとタイムコード、タイムスタンプ](https://qiita.com/sunfish-shogi/items/4dc020954d46f0e998a6)
- [AbemaTV の MPEG-DASH 対応](https://qiita.com/sunfish-shogi/items/008b3da9309b08465604)
- [MPEG-DASH (Dynamic Adaptive Streaming over HTTP, ISO/IEC 23009-1) - BITMOVIN Blog](https://bitmovin.com/dynamic-adaptive-streaming-http-mpeg-dash/)