HLSの仕組み

HLS(HTTP Live Streaming)は、Appleが開発した動画配信の方式です。回線の速さに合わせて、画質を切り替えながら再生できます(アダプティブストリーミング)。今では、ほぼすべての環境が対応しています。FFmpegでは -f hls を指定すると、HLS用のファイルを書き出せます。

HLSの出力は、次の2種類のファイルです。

ファイル種別 拡張子 説明
プレイリスト .m3u8 セグメントのURLを並べた目次ファイル
セグメント .ts 動画と音声を短く区切ったファイル(通常2〜10秒)

Webサーバーは、これらのファイルをそのままHTTPで配信するだけです。プレーヤー(ブラウザの hls.js、iOS/macOS の標準プレーヤー、Android)は、まずプレイリストを読み込み、再生に必要なセグメントを順に取得します。

最小限のHLS出力

ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f hls /tmp/playlist.m3u8

初期設定のままで、/tmp/playlist.m3u8 と、番号付きのセグメントファイル(/tmp/playlist0.ts、/tmp/playlist1.ts など)ができます。

セグメント長の制御

-hls_time N で、セグメントの長さの目標を秒で指定します(デフォルトは 2)。

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 /tmp/playlist.m3u8

セグメントは、キーフレーム(単独で表示できるフレーム)の位置でしか区切れません。libx264 の初期設定では、キーフレームの間隔が最大250フレーム(30fpsで約8.3秒)です。そのため -hls_time 6 を指定しても、8秒を超えるセグメントができることがあります。長さをそろえるには、キーフレームの間隔を固定します。

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -g 60 -keyint_min 60 -sc_threshold 0 -c:a aac -f hls -hls_time 6 /tmp/playlist.m3u8

-g 60 で、60フレームごとにキーフレームを入れます(30fpsなら2秒ごと)。-sc_threshold 0 は、場面の切り替わりで余分なキーフレームを入れない指定です。-hls_time 6 と組み合わせると、1つのセグメントにキーフレームの区間がちょうど3つ入ります。

プレイリストにすべてのセグメントを保持する

-hls_list_size のデフォルトは 5 で、プレイリストには最新の5セグメントしか載りません(ライブ配信向けの設定です)。VOD(録画済みの動画を、見たいときに再生できる配信)では 0 にして、すべてのセグメントを載せます。

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 /tmp/playlist.m3u8

セグメントファイル名のカスタマイズ

-hls_segment_filename で、セグメントファイルの保存先と名前の付け方を指定します。

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/segment_%03d.ts" /tmp/playlist.m3u8

%03d の部分には、0で埋めた3桁の連番が入ります(segment_000.ts、segment_001.ts など)。フォルダーを含めたパスも指定できます。

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/hls/seg_%04d.ts" /tmp/hls/playlist.m3u8

出力先のフォルダーは、先に作っておいてください。FFmpegは自動では作りません。

VOD用の完全な例

動画ファイルをVOD用のHLSに変換する、実用的なコマンド例です。

ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 22 -c:a aac -b:a 128k -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/hls_vod/seg_%04d.ts" /tmp/hls_vod/index.m3u8

/tmp/hls_vod/ フォルダーをまるごとHTTPサーバーに置き、プレーヤーには index.m3u8 のURLを指定します。

ライブストリーミング — 古いセグメントの自動削除

ライブ配信では、プレイリストから外れた古いセグメントは使いません。-hls_flags delete_segments を付けると、自動で削除されます。

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 5 -hls_flags delete_segments /tmp/playlist.m3u8

-hls_list_size 5 でプレイリストには5セグメントを載せます。プレイリストから外れたセグメントは自動で消えますが、最後に外れた1つだけは残るので、ディスクには6つのセグメントが残ります。残す数は -hls_delete_threshold で変えられます(初期値は 1)。

アダプティブビットレートラダー(概要)

ABRラダー(用意する画質の段階の組み合わせ)を作るには、同じ動画を複数のビットレート・解像度でエンコードします。そのうえで、それぞれの画質(バリアント)を指すマスタープレイリストを作ります。手順は次のとおりです。

  1. ビットレート・解像度ごとに、別々のHLSとしてエンコードする
  2. #EXT-X-STREAM-INF タグで各バリアントを並べた、マスタープレイリスト(.m3u8)を作る

FFmpeg では、1つの -f hls 出力に -var_stream_map(例: "v:0,a:0 v:1,a:1")と -master_pl_name を指定し、出力名に %v を入れる(例: stream_%v.m3u8)と、各画質のプレイリストとマスタープレイリストを1回の実行でまとめて書き出せます。ただし、出力には画質ごとに映像と音声のストリームが必要です。2段階なら -map 0:v -map 0:a -map 0:v -map 0:a で映像と音声を2回ずつ割り当て、-s:v:1 1280x720 -b:v:1 3M のように、ストリームごとに解像度やビットレートを指定します。本番のABR配信では、AWS MediaConvert や、FFmpegをもとにした専用の変換ツールを使うほうが現実的です。

主要オプション一覧

オプション デフォルト 説明
-hls_time N 2 セグメントの長さの目標(秒)
-hls_list_size N 5 プレイリストの最大セグメント数(0=全保持)
-hls_segment_filename パターン (自動) セグメントファイルの命名パターン
-hls_flags delete_segments オフ プレイリストから除外されたセグメントを自動削除
-hls_flags append_list オフ 既存プレイリストに追記(上書きせず)
-start_number N 0 セグメントの開始シーケンス番号

よくある問題

セグメントがプレイリストに含まれていない

VODで -hls_list_size 0 を付け忘れると、プレイリストには最新のN件のセグメントしか載りません。再生はできますが、最後の部分だけになります。

出力ディレクトリが存在しない

セグメントの保存先のフォルダーがないと、Failed to open file というエラーが出て、セグメントが書き出されません。FFmpeg 8.1 では、1つ目のセグメントを書き終える時点で Conversion failed! と表示されて止まります。動画が短くてセグメント1つに収まるときは、最後まで進んで終了コード 0 で終わりますが、この場合もセグメントはできません。先にフォルダーを作ってから実行してください。

mkdir -p /tmp/hls_output
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/hls_output/seg_%04d.ts" /tmp/hls_output/playlist.m3u8

プレーヤーで再生できない

HTTPサーバーが、正しいMIMEタイプ(ファイルの種類を伝える情報)を返しているか確認してください。

  • .m3u8 → application/vnd.apple.mpegurl または application/x-mpegURL
  • .ts → video/mp2t

サーバーによっては、この設定を自分で追加する必要があります。

関連記事

よくある質問

セグメント時間は何秒がいい?

VOD なら 6〜10 秒(CDN のコストを抑えやすい)、ライブなら 2〜4 秒(遅延が小さい)が目安です。Apple は以前は 10 秒を勧めていましたが、今は短くする傾向です。

master playlist と media playlist の違いは?

master playlist は、複数の画質を並べた「索引」です。media playlist は、実際のセグメントの一覧を持つ「中身」です。画質を切り替える配信(アダプティブビットレート)には、master が必要です。

音声と映像を別々にセグメント化したい

-var_stream_map で、音声を別のグループ(agroup)にします。複数の言語の音声に対応するときの土台になります。"v:0,a:0" と書いた場合は、映像と音声が同じセグメントに入った1つの m3u8 になり、音声は分かれません。

ffmpeg -i input.mp4 -map 0:v -map 0:a -c:v libx264 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -var_stream_map "v:0,agroup:aud a:0,agroup:aud" -master_pl_name master.m3u8 /tmp/stream_%v.m3u8

映像だけの stream_0.m3u8、音声だけの stream_1.m3u8、両方をまとめる master.m3u8 ができます。

セグメントが大きすぎる

キーフレームの間隔を -g 60(30fps で 2 秒ごと)にして、-hls_time の区切りに合わせます。これをしないと、区切りたい位置にキーフレームがなく、セグメントが大きくなりがちです。

HLS の最小遅延(LL-HLS)はどう違う?

通常の HLS は遅延が 15〜30 秒、LL-HLS は 2〜6 秒です。LL-HLS には、partial segment(セグメントをさらに細かく分けたもの)と blocking response(更新が出るまでサーバーが応答を待つ仕組み)が必要です。詳しくは低遅延HLS(LL-HLS)の記事を見てください。