この記事でわかること
- 従来のHLSと低遅延HLS(LL-HLS)の違い
- LL-HLSを成立させる「partial segment(部分セグメント)」という考え方
- LL-HLS出力を生成するFFmpegコマンド(
-hls_time 1・-hls_flags・-hls_segment_type fmp4など) EXT-X-SERVER-CONTROL・EXT-X-PART-INF・EXT-X-PRELOAD-HINTが実際に何をするのか- プレーヤー互換性(iOS / macOSネイティブ / hls.js)と、実際のglass-to-glass遅延の測定方法
テスト済みバージョン: FFmpeg 7.1(検証スクリプトで実 FFmpeg 実行確認)
対象 OS: Windows / macOS / Linux
LL-HLSとは?
従来のHLSはコンテンツをセグメント単位(通常6秒)で配信するため、歴史的に15〜30秒のglass-to-glass遅延という問題を抱えてきました。
LL-HLS(Low-Latency HLS) は2020年にAppleが公開した拡張仕様で、以下の仕組みによってエンドツーエンドの遅延を2〜6秒まで縮められます。
| 仕組み | 概要 |
|---|---|
| Partial Segments(部分セグメント) | 通常の.ts / .m4sセグメントを200〜500msの「パート」に分割し、完全なセグメントが揃う前にプレイリストへ公開する |
| Blocking Playlist Reload(ブロッキング・プレイリスト再読み込み) | プレイリストがまだ更新されていない場合、サーバーは新しいパートが利用可能になるまでHTTPレスポンスを保留する |
| Preload Hint(プリロードヒント) | 次に来るパートのURLをプレイリストで予告し、プレーヤーが先読みリクエストできるようにする |
FFmpeg 7.0+はコアとなる要素(短いセグメント、独立セグメント、program date-time)をサポートしています。完全なLL-HLS運用には、これに加えて実際にブロッキングレスポンスを処理できるCDNか、FFmpegの後段に置く専用パッケージャーが必要です。
前提条件:固定キーフレーム間隔 / GOP
LL-HLSは、IDRキーフレーム間隔をセグメント長に固定して初めて機能します。これがないとセグメントがキーフレームに揃わず、分割の失敗や初フレーム表示までの長い遅延が発生します。
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -profile:v baseline -level 3.1 -pix_fmt yuv420p -g 30 -keyint_min 30 -sc_threshold 0 -b:v 2500k -c:a aac -b:a 128k -f hls -hls_time 1 -hls_list_size 10 -hls_flags independent_segments+delete_segments -hls_segment_type mpegts -hls_segment_filename "/tmp/seg_%04d.ts" /tmp/playlist.m3u8
主な選択肢:
-g 30+-keyint_min 30+-sc_threshold 0→ 30フレームごと(=30fpsで1秒)にIDRを強制-tune zerolatency→ Bフレームを無効化し、エンコーダーのパイプラインを縮める-profile:v baseline→ デバイス互換性を最大化(iOS / Android)-pix_fmt yuv420p→ baselineが要求する4:2:0クロマを明示的に満たす-hls_time 1→ 1秒セグメントを目標にする-hls_flags independent_segments→ 各セグメントが単独でデコード可能であることをプレイリストで宣言する
最小限のLL-HLS出力
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 30 -keyint_min 30 -sc_threshold 0 -c:a aac -f hls -hls_time 1 -hls_list_size 20 -hls_flags independent_segments+delete_segments+program_date_time -master_pl_name master.m3u8 -hls_segment_filename "/tmp/seg_%04d.ts" /tmp/stream.m3u8
生成されるもの:
/tmp/stream.m3u8— メディアプレイリスト/tmp/seg_0000.ts、seg_0001.ts、… — 1秒セグメント/tmp/master.m3u8— マスタープレイリスト
重要なLL-HLSオプション
| オプション | 推奨値 | 役割 |
|---|---|---|
-hls_time | 1 | ターゲットセグメント長(秒)。LL-HLSでは1秒以下が一般的 |
-hls_list_size | 10–20 | プレイリストに保持するセグメントの最大数 |
-hls_flags independent_segments | 必須 | 各セグメントが単独でデコード可能であることを通知 |
-hls_flags delete_segments | 推奨 | ライブ配信中のディスク肥大化を防ぐ |
-hls_flags program_date_time | 推奨 | EXT-X-PROGRAM-DATE-TIMEを付与(同期・シークに有用) |
-hls_segment_type | mpegts または fmp4 | fmp4の場合は-hls_fmp4_init_filenameも指定する |
-g / -keyint_min | fps × hls_time | キーフレームをセグメント境界に保つ |
-sc_threshold | 0 | シーンチェンジによるキーフレームを無効化(間隔を固定に保つ) |
-tune zerolatency | 推奨 | libx264の低遅延チューニング |
fMP4ベースのLL-HLS(推奨)
Appleの仕様は実質的にfragmented MP4を前提としています。
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 30 -keyint_min 30 -sc_threshold 0 -c:a aac -f hls -hls_time 1 -hls_list_size 20 -hls_segment_type fmp4 -hls_fmp4_init_filename "init.mp4" -hls_flags independent_segments+delete_segments+program_date_time -hls_segment_filename "/tmp/seg_%04d.m4s" /tmp/stream.m3u8
init.mp4は初期化セグメント(movie boxのみを含む)で、メディアセグメントを取得する前にプレーヤーが必ず取得する必要があります。
LL-HLSプレイリストのサンプル
上記のコマンドが生成するstream.m3u8は、おおよそ次のようになります。
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:1
#EXT-X-MEDIA-SEQUENCE:42
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="init.mp4"
#EXTINF:1.000000,
seg_0042.m4s
#EXTINF:1.000000,
seg_0043.m4s
#EXTINF:1.000000,
seg_0044.m4s
完全にLL-HLS準拠のマニフェストには、さらに以下のようなタグが必要です(通常はFFmpegの後段にあるCDNや専用パッケージャーが出力します)。
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.0
#EXT-X-PART-INF:PART-TARGET=0.33
#EXT-X-PART:DURATION=0.33,URI="seg_0044.1.m4s"
#EXT-X-PART:DURATION=0.33,URI="seg_0044.2.m4s"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg_0044.3.m4s"
FFmpegは部分セグメントのタグを直接出力しないため、完全なLL-HLS体験が必要な場合は Shaka Packager やAppleの mediastreamsegmenter などと組み合わせてください。
ライブソースからLL-HLSを配信する
RTMPやUDPのフィードを受け取ってLL-HLSを書き出す例です。
ffmpeg -i rtmp://localhost/live/stream -c:v libx264 -preset ultrafast -tune zerolatency -g 30 -keyint_min 30 -sc_threshold 0 -b:v 3000k -c:a aac -b:a 128k -f hls -hls_time 1 -hls_list_size 10 -hls_flags delete_segments+independent_segments+program_date_time -hls_segment_filename "/tmp/live_seg_%04d.ts" /tmp/live_stream.m3u8
-preset ultrafastでエンコーダーの遅延を最小化する-hls_list_size 10で10秒分のウィンドウを確保する- 出力ディレクトリをHTTPで配信し、プレーヤーを
live_stream.m3u8に向ける
補足:この例は入力に
rtmp://のURLを使っているため、verify-ffmpeg CI(ネットワークが必要)では自動的にスキップされます。ご自身のRTMPソースを使ってローカルで検証してください。
プレーヤー互換性
| プレーヤー | LL-HLSサポート | 備考 |
|---|---|---|
| iOS 14+ / tvOS 14+ Safari | ✅ 完全対応 | ネイティブAVPlayer |
| macOS Safari 14+ | ✅ 完全対応 | 同じAVPlayerパイプライン |
| hls.js 1.4+ | ⚠️ 部分対応 | lowLatencyMode: trueと、ブロッキング再読み込みを実装したCDNが必要 |
| ExoPlayer(Android) | ⚠️ 部分対応 | バージョン依存 |
| dash.js / Shaka Player | ❌ | DASH側ではLL-DASHを使う |
glass-to-glass遅延の測定
手軽な方法:
- 送信側にタイムスタンプを焼き込む(OBSのタイマーソースかFFmpegの
drawtext) - 送信側の画面と視聴側の画面を1枚の写真に同時に収める
- 表示されている2つのタイムスタンプの差を取る
ローカル時刻を焼き込むFFmpegの例:
ffmpeg -i input.mp4 -vf "drawtext=text='%{localtime}':fontcolor=white:fontsize=36:x=10:y=10:box=1:[email protected]" -c:v libx264 -preset veryfast -tune zerolatency -g 30 -c:a aac -f hls -hls_time 1 -hls_list_size 10 -hls_flags delete_segments+independent_segments -hls_segment_filename "/tmp/burnin_%04d.ts" /tmp/burnin.m3u8
補足:drawtextはフォントファイルを必要とします。環境によってはfontfile=/path/to/font.ttfを追加で渡す必要があります。
よくある問題
セグメント境界がキーフレームに揃わない
pkt->duration = 0, maybe the hls segment duration will not precise のような警告は、たいていGOP長がhls_timeと一致していないことを意味します。-gがfps × hls_timeにちょうど等しくなっているか確認してください。
iOS Safariでストリームが再生できない
ネイティブのLL-HLS再生には、プレイリストにEXT-X-INDEPENDENT-SEGMENTSがあること、そしてサーバー側で適切なCORS(Access-Control-Allow-Origin: *)が設定されていることが必要です。
hls.jsの再生が遅く感じる
hls.jsの設定でlowLatencyMode: trueを指定し、なおかつCAN-BLOCK-RELOAD=YESが通知されているときにCDNがブロッキングレスポンスを返す必要があります。そうでないと、結局は従来HLSの遅延に逆戻りします。
CPUが上限まで使い切られる
-preset ultrafastを試すか、ハードウェアエンコーダー(h264_nvenc・h264_videotoolbox・h264_qsv)に切り替えてください。1秒ごとにIDRを強制すると、従来HLSに比べてCPU負荷がかなり上がります。
遅延の比較(おおよその実測値)
| 構成 | セグメント長 | 典型的な遅延 | 備考 |
|---|---|---|---|
| 従来HLS(VODスタイル) | 6秒 | 18〜30秒 | hls_time 6 + 5セグメントのバッファ |
| 短セグメントHLS | 2秒 | 8〜12秒 | hls_time 2 |
| LL-HLS(本記事) | 1秒 | 3〜6秒 | hls_time 1 + ブロッキング再読み込み |
| LL-HLS + パート | 1秒 + 0.33秒パート | 2〜3秒 | 完全仕様(専用パッケージャーが必要) |
| WebRTC | — | 0.1〜0.5秒 | 用途が異なる |
関連リソース
よく使うオプション・フィルタ・コーデック設定をまとめた PDF チートシートです。手元に置いておくと調べる時間を短縮できます。
関連記事
よくある質問
hls_time 1を指定するだけでLL-HLSになりますか?
いいえ。hls_time 1は単に1秒セグメントを意味するだけです。本当のLL-HLS(2〜3秒の遅延)には、部分セグメントのマニフェストタグと、ブロッキングレスポンスを返せるサーバーが必要です。FFmpegだけでは短セグメントHLS(遅延5秒程度)になりますが、これでも多くの用途には十分です。
iOS以外でもLL-HLSを再生できますか?
hls.js 1.4+はlowLatencyMode: trueで部分的に対応します。Android ExoPlayerや各種スマートTVのプレーヤーの対応はまちまちです。UWP / Chromecastでは、LL-DASHのほうが実用的なことが多いです。
1秒セグメントはCDMとの相性が悪いですか?
発行するHTTPリクエスト数が約6倍(6秒→1秒)になります。リクエスト課金が有利なCDNか、公式にLL-HLSチューニングを備えたCDN(CloudFront、Fastly、Cloudflare)を選んでください。
CPU負荷はどう下げますか?
-preset ultrafast、ハードウェアエンコーダー(h264_nvenc・h264_videotoolbox・h264_qsv)を試すか、入力がすでにH.264なら-c:v copyを使ってトランスコード自体を省略してください。
LL-HLSはVODに役立ちますか?
いいえ。LL-HLSはライブ配信向けの技術です。オンデマンド配信には従来HLS(6秒セグメント、リスト全保持)を使ってください。VODにLL-HLSを使っても、視聴者にメリットがないままCDN負荷が倍増するだけです。
テスト環境: ffmpeg 7.1 / Ubuntu 24.04(検証スクリプトで実行確認) 一次ソース: ffmpeg.org/ffmpeg-formats.html#hls-1 / Apple Developer — Enabling Low-Latency HLS