この記事でわかること

  • 従来のHLSと低遅延HLS(LL-HLS)の違い
  • LL-HLSを成立させる「partial segment(部分セグメント)」という考え方
  • LL-HLS出力を生成するFFmpegコマンド(-hls_time 1-hls_flags-hls_segment_type fmp4 など)
  • EXT-X-SERVER-CONTROLEXT-X-PART-INFEXT-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.tsseg_0001.ts、… — 1秒セグメント
  • /tmp/master.m3u8 — マスタープレイリスト

重要なLL-HLSオプション

オプション推奨値役割
-hls_time1ターゲットセグメント長(秒)。LL-HLSでは1秒以下が一般的
-hls_list_size10–20プレイリストに保持するセグメントの最大数
-hls_flags independent_segments必須各セグメントが単独でデコード可能であることを通知
-hls_flags delete_segments推奨ライブ配信中のディスク肥大化を防ぐ
-hls_flags program_date_time推奨EXT-X-PROGRAM-DATE-TIMEを付与(同期・シークに有用)
-hls_segment_typempegts または fmp4fmp4の場合は-hls_fmp4_init_filenameも指定する
-g / -keyint_minfps × hls_timeキーフレームをセグメント境界に保つ
-sc_threshold0シーンチェンジによるキーフレームを無効化(間隔を固定に保つ)
-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 PlayerDASH側ではLL-DASHを使う

glass-to-glass遅延の測定

手軽な方法:

  1. 送信側にタイムスタンプを焼き込む(OBSのタイマーソースかFFmpegのdrawtext
  2. 送信側の画面と視聴側の画面を1枚の写真に同時に収める
  3. 表示されている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と一致していないことを意味します。-gfps × 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_nvench264_videotoolboxh264_qsv)に切り替えてください。1秒ごとにIDRを強制すると、従来HLSに比べてCPU負荷がかなり上がります。


遅延の比較(おおよその実測値)

構成セグメント長典型的な遅延備考
従来HLS(VODスタイル)6秒18〜30秒hls_time 6 + 5セグメントのバッファ
短セグメントHLS2秒8〜12秒hls_time 2
LL-HLS(本記事)1秒3〜6秒hls_time 1 + ブロッキング再読み込み
LL-HLS + パート1秒 + 0.33秒パート2〜3秒完全仕様(専用パッケージャーが必要)
WebRTC0.1〜0.5秒用途が異なる

関連リソース

よく使うオプション・フィルタ・コーデック設定をまとめた PDF チートシートです。手元に置いておくと調べる時間を短縮できます。

FFmpeg チートシート

関連記事


よくある質問

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_nvench264_videotoolboxh264_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