LL-HLSとは?
HLSは、動画を短いファイル(セグメント。通常6秒)に分けて配信する方式です。そのため従来のHLSでは、撮影してから視聴者の画面に映るまでの遅延(glass-to-glass遅延)が大きくなります。ライブ配信のプレーヤーは、最新のセグメントからターゲット長の3倍以上さかのぼって再生を始めるよう推奨されています。これに従うと、6秒のセグメントなら18秒以上遅れます(後述)。
LL-HLS(Low-Latency HLS)は、2019年にAppleが発表したHLSの拡張仕様です(2020年に改訂)。次の仕組みで、この遅延を縮められます。
| 仕組み | 概要 |
|---|---|
| Partial Segments(部分セグメント) | 通常の.ts / .m4sセグメントを短い「パート」に分け、セグメント全体ができる前にプレイリストに載せる。Apple の推奨はパート1つあたり1秒 |
| Blocking Playlist Reload(ブロッキング・プレイリスト再読み込み) | プレイリストがまだ更新されていなければ、サーバーは新しいパートができるまでHTTPの応答を待たせる |
| Preload Hint(プリロードヒント) | 次のパートのURLをプレイリストで先に知らせ、プレーヤーが先に要求を出せるようにする |
FFmpegが対応しているのは、土台になる部分(短いセグメント、独立セグメント、program date-time)です。LL-HLSを完全に動かすには、ブロッキング応答を返せるCDNか、FFmpegの後ろに置く専用のパッケージャーも必要です。
前提条件:固定キーフレーム間隔 / GOP
LL-HLSでは、どのセグメントもIDRキーフレームから始まるように、キーフレームの間隔(GOP)を、セグメントの長さと同じか、それを割り切れる長さに固定します。IDRキーフレームは、そこから単独で再生を始められるフレームです。FFmpegはキーフレームの位置でしか新しいセグメントを始めないので、間隔が合わないとセグメントが hls_time より長くなり、遅延も大きくなります。
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -profile:v baseline -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:どのセグメントも単独でデコードできることを、プレイリストに書く
最小限の短セグメント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で短く区切るのは、セグメントではなくパート |
-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の場合は初期化セグメントinit.mp4も作られる(名前は-hls_fmp4_init_filenameで変えられる) |
-g / -keyint_min |
fps × hls_time |
キーフレームをセグメント境界に保つ |
-sc_threshold |
0 |
シーンチェンジによるキーフレームを無効化(間隔を固定に保つ) |
-tune zerolatency |
推奨 | libx264の低遅延チューニング |
fMP4セグメント(推奨)
AppleのHLSオーサリング仕様では、H.264はMPEG-TSとfragmented MP4(fMP4)のどちらでもよく、HEVCとAV1はfMP4が必須です。fMP4で書き出す例です。
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 は初期化セグメント(ftyp と moov のボックスだけを含むファイル)です。プレーヤーは、メディアセグメントより先に必ずこれを読み込みます。
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,
#EXT-X-PROGRAM-DATE-TIME:2026-09-24T17:48:35.393+0900
seg_0042.m4s
#EXTINF:1.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-09-24T17:48:36.393+0900
seg_0043.m4s
#EXTINF:1.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-09-24T17:48:37.393+0900
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が必要なら、AppleのmediastreamsegmenterのようにLL-HLSに対応したツールと組み合わせます。
ライブソースから配信する
RTMPやUDPで届く映像を受け取り、短いセグメントの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は、自分のRTMPの配信元に置き換えてください。
プレーヤー互換性
| プレーヤー | LL-HLSサポート | 備考 |
|---|---|---|
| iOS 14+ Safari / tvOS 14+ のアプリ | ✅ 完全対応 | ネイティブAVPlayer |
| macOS Safari 14+ | ✅ 完全対応 | 同じAVPlayerパイプライン |
| hls.js 1.0+ | ⚠️ 部分対応 | lowLatencyMode(既定で有効)と、ブロッキング再読み込みを実装したCDNが必要 |
| ExoPlayer(Android) | ⚠️ 部分対応 | バージョン依存 |
| Shaka Player | ✅ 対応 | 部分セグメント・プリロードヒント・差分更新・ブロッキング再読み込みに対応 |
| dash.js | ❌ | DASH専用。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 の指定も必要です。
よくある問題
セグメント境界がキーフレームに揃わない
セグメントの長さが hls_time どおりにならないときは、たいていGOPの長さが hls_time と合っていません。-g が fps × hls_time と同じか、それを割り切れる値になっているか確認します。Stream 0 packet with pts ... has duration 0. The segment duration may not be precise. という警告は別の原因で、長さ(duration)の情報を持たないパケットが届いたときに出ます。
iOS Safariでストリームが再生できない
EXT-X-INDEPENDENT-SEGMENTS は、再生に必須のタグではありません。CORS(別のサイトからの読み込みを許可する設定。Access-Control-Allow-Origin)が必要になるのは、hls.jsなどのスクリプトで、ページとは別のドメインからプレイリストやセグメントを読み込むときです。<video> にURLを直接指定してSafariで再生するだけなら、CORSは使われません。
hls.jsの再生が遅く感じる
hls.jsの設定で、lowLatencyMode を false にしていないか確認します(既定は true)。さらに、プレイリストに CAN-BLOCK-RELOAD=YES があるときに、CDNがブロッキング応答を返す必要があります。そうでないと、遅延は従来のHLSと変わりません。
CPUが上限まで使い切られる
1秒ごとにIDRを入れても、増えるのは主にビットレートで、CPUの負荷はほとんど変わりません。エンコードが追いつかないときは、-preset ultrafast を試すか、ハードウェアエンコーダー(h264_nvenc・h264_videotoolbox・h264_qsv)に切り替えます。
遅延の比較(仕様の推奨に従う場合の下限)
ライブ配信のプレーヤーは、最新のセグメントからターゲット長の3倍以上さかのぼった位置から再生を始めるよう推奨されています(RFC 8216)。下の表は、この推奨に従うプレーヤーでの値です。パートを使うLL-HLSでは、この距離を PART-HOLD-BACK で決め、パートの長さの3倍以上が推奨です。実際の遅延は、これにエンコードや配信の時間が加わります。
| 構成 | セグメント長 | 遅延の下限 | 備考 |
|---|---|---|---|
| 従来HLS | 6秒 | 18秒 | hls_time 6 |
| 短セグメントHLS | 2秒 | 6秒 | hls_time 2 |
| 1秒セグメント(FFmpegだけ) | 1秒 | 3秒 | hls_time 1(上のコマンド) |
| LL-HLS + パート | 1秒 + 0.33秒パート | PART-HOLD-BACK の値(約1秒以上が推奨) |
完全仕様(専用パッケージャーが必要) |
関連記事
よくある質問
hls_time 1を指定するだけでLL-HLSになりますか?
なりません。hls_time 1 は、セグメントを1秒にするだけです。本来のLL-HLSには、部分セグメントのタグを書いたプレイリストと、ブロッキング応答を返せるサーバーが必要です。FFmpegだけだと短いセグメントのHLSになり、1秒セグメントでも、推奨に従うプレーヤーは3秒以上遅れて再生します(前述)。多くの用途では、これで足ります。
iOS以外でもLL-HLSを再生できますか?
hls.js(1.0以降)は一部に対応しています(lowLatencyMode は既定で有効)。Shaka Playerも、部分セグメントやブロッキング再読み込みに対応しています。AndroidのExoPlayerやスマートテレビのプレーヤーは、製品によって対応が違います。UWPやChromecastでは、LL-DASHのほうが使いやすいことが多いです。
1秒セグメントはCDNとの相性が悪いですか?
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はライブ配信のための技術です。オンデマンド配信(VOD)には、従来のHLS(6秒セグメントで、全セグメントをリストに残す)を使います。VODにLL-HLSを使っても視聴者に利点はなく、CDNの負荷が倍増するだけです。