LL-HLS/LL-DASH 両対応オリジンを作った話

## 初めに

[Apple Low Latency HLS のオリジンサーバを自作して試した話](https://qiita.com/monyo-tech/items/072b6dbcaa58b499eb09) の続編になります

世の配信プロトコルは MPEG-DASH と HLS です。どちらも低遅延を謳った方式があります。
HLS だと、Apple Low Latency HLS (LL-HLS) になりますし、
MPEG-DASH だと Low Latency DASH (LL-DASH) になります。
(一応 LHLS もありますが、最近は LL-HLS になりつつあります)

では、Apple Low Latency HLS (LL-HLS) と Low Latency DASH (LL-DASH) は両立できるのか?
その疑問に迫ります!

## 前提

### Apple Low Latency HLS (LL-HLS)

Apple が策定した HLS 上で、低遅延向けへの機能を有効化したものを LL-HLS と呼びます。
以下の機能が特徴的で、EXT-X-SERVER-CONTROL で有効化して利用します。

* Blocking Playlist Reload
    * プレイリストの更新をロングポーリングします
    * HTTP/2 でなくても有効です
* Preload Hint
    * 次に使う (かもしれない) パーシャルセグメントを予告できます
    * クライアントは投機的にダウンロードをして、使う場合に備えます
    * HTTP/2 でのみ有効です

### Low Latency DASH (LL-DASH)

ISO によって定められた MPEG-DASH の低遅延配信方式を LL-DASH と呼びます。
以下の機能が特徴的です。

* SegmentTemplate による投機的なセグメント取得
    * SegmentTimeline を利用しない事で現在時刻によってセグメントを取得します
* セグメントのプログレッシブ再生
    * Chunked Transter Encoding (HTTP/1.1) などでセグメントを取得しながら再生します
    * セグメントの完成を待つ必要なく、順次読み込んで再生します

注意点として、SegmentTemplate による投機的なセグメント取得を行うために、
サーバとクライアントで共通の時刻を参照し同期する必要があります。
(LL-HLS の場合はロングポーリングなので、時刻同期は必要ありません)

## 各配信方法で要求されること

### LL-HLS

* プレイリストの更新をロングポーリングできること
* パーシャルセグメントに分解して配信できること
* セグメントが TARGETDURATION 以上にならないこと

### LL-DASH

* CMAF準拠で AdaptationSet 毎に fmp4 が分かれてる事 (LL-HLS より強い条件)
* セグメント長がルールに従っていること (LL-HLS より強い条件)
* Chunked Transfer Encoding のようなプログレッシブな取得が可能な事

## LL-HLS/LL-DASH の互換性について

CTA-5005 [Web Application Video Ecosystem – DASH-HLS Interoperability Specification](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5005-final.pdf)
として互換の取り方が記載されています。
ただし、規格なので当たり前ですが、互換の取り方が書かれているだけです。
オリジンをどう実装するとよいかについては、当然ながら何も触れられていません。

## やりかた

結局、ストリームの仮定としては LL-DASH の仮定を使って、
マニフェストのモデルとしては LL-HLS を少し拡張した形で管理すると楽でした。
LL-HLS を LL-DASH にマッピングする上で足りない部分を補完する形です。

1. ストリームを LL-DASH で配信可能なルールベースで配信可能なストリームにします
    * GOP 間隔が定まって入れば OK です
2. LL-HLS 向けのパッケージングをします
    * PRELOAD-HINT を含む 1 つ未来のセグメントも用意します。
    * このセグメントはプログレッシブに転送できるようにします。
    * PRELOAD-HINT でもプログレッシブ対応しているので、ついでで出来ます。
3. M3U8 / MPD を作って配信します
    * M3U8 は LL-HLS をそのまま配信すれば OK です
    * MPD は 1 つ未来のセグメントまで参照し、プログレッシブにダウンロードさせます

## 実装

こちらに用意しました。
https://github.com/monyone/biim/tree/feature/ll-dash

こんな感じで実行して、 `master.m3u8` や `master.mpd` を見てください。
```bash
ffmpeg -re \
  -f lavfi -i testsrc=700x180:r=30 \
  -f lavfi -i sine=frequency=1000 \
  -vf "settb=AVTB,setpts='trunc(PTS/1K)*1K+st(1,trunc(RTCTIME/1K))-1K*trunc(ld(1)/1K)',drawtext=fontsize=60:fontcolor=black:text='%{localtime}.%{eif\:1M*t-1K*trunc(t*1K)\:d\:3}'" \
  -c:v libx264 -tune zerolatency -preset ultrafast -r 30 -g 15 -pix_fmt yuv420p \
  -c:a aac -ac 1 -ar 48000 \
  -f mpegts - | ./dash.py -t 0.5 -p 0.15 -w 10 --port 8080
```

## 感想

一応、LL-HLS / LL-DASH 両対応をしてみましたが、色々と思う所はありました。

LL-DASH は、時刻同期がめんどうだし危なっかしいなと思いました。
でも、投機的な取得なのでチューニングできれば、低遅延マニアも満足できそうです。

LL-HLS は、HTTP/2 でないと AVPlayer が低遅延モードにならないのを何とかしてほしいです。
(昔はそうじゃなかった記憶があるんだけどなぁ...)

## 余談

[RTMP を LL-HLS にする](https://qiita.com/monyo-tech/items/c83c91874c9c1ccf9606) をやったので、LL-DASH もできるという形で発展させてみました。
ただ、これから来る QUIC 時代において、LL-DASH 自体が古くなるかもしれないと思います。
(例えば、CDN がストリーム伝送を中継してくれるなら WARP が代替になる未来がありますね)

また、打ち上げ側も進歩しつつあり RTMP か HLS かという状態から、
DASH Ingest も加わる時代になりつつ、低遅延配信で使う RTMP では利用できなかった
H.265 や AV1 での打ち上げの夢も広がっています。
(ちなみに、H.265 の TS -> fmp4 のトランスマックスも上記の実装ではサポートしています)

低遅延ブームは終わったとの声も聴きますが、低遅延配信の技術は枯れてはいない印象で
これからも低遅延と安定性という 2 つのバランスを取るという部分は
割とまだまだ掘れる分野だなと思っています。みんな遊びましょう!

また、WebRTC ベースの WHIP/WHEP も注目をあつめている印象です。
ただし、WebRTC はビデオチャットが対象なので、映像の視聴体験という観点では
HTTP ベースの HLS/DASH に比べると、打ち上げ画質の固定化や
バッファ量の少なさによるジッタ揺らぎの対処など、
視聴品質を損ねやすい部分の対処を頑張るという課題があるなとぼんやり思ってます。