ffmpeg で Apple HTTP Live Streaming(HLS)形式に出力する方法。各セグメントの動画ファイルは TS(ffmpeg 3.4 からは .m4s(fragmented MP4, fmp4)も可能)でテキスト形式のプレイリストとなるマニフェストは .m3u8 で出力される。またsegmentmuxer のオプションを使うことでより詳細な設定ができる。

詳細な分割設定ができる Segment

字幕付きの配信なら
ARIB字幕をVTTにしてHLSで視聴する

MPEG-DASHは以下より
ffmpeg で MPEG-DASH を扱う

基本コマンド

ファイルからライブ配信には-reをつけて、VOD 配信には-reをつけない。-reは動画を1倍速で読み込むオプションである。同様の効果にrealtimeフィルタがある。MP4 ファイルから TS ファイルで出力するときには-bsf:v h264_mp4toannexbをつける。

ffmpeg -i input -c:v libx264 -crf 23 -c:a aac -b:a 128k -f hls output.m3u8

MP4 を HLS 形式にコーデックコピーする例。(-bsf:v h264_mp4toannexbを付けないとエラーで処理が止まっていたが止まらなくなった)詳しくはここ-hls_list_size 0ですべてのセグメントをマニフェストに書き込む。
ffmpeg -i input.mp4 -bsf:v h264_mp4toannexb -c copy -f hls -hls_list_size 0 output.m3u8
ffmpeg -i input.mp4 -c copy -f hls -hls_list_size 0 output.m3u8

MP4 から暗号化とセグメントの時間指定(60秒)をする例(file.keyinfo は後述)。
ffmpeg -i input.mp4 -bsf:v h264_mp4toannexb -c:v libx264 -c:a aac -f hls -hls_key_info_file file.keyinfo -hls_list_size 0 -hls_time 60 output.m3u8

H.265(HEVC)の fmp4 での HLS 出力コマンド例。
wiki: Encode / H.265 – trac.ffmpeg.org
movenc: allow alternative hvc1 h.265 codec tag
ffmpeg -i input -c:v libx265 -c:a aac -tag:v hvc1 -f hls -hls_list_size 0 -hls_segment_type fmp4 -movflags +faststart output.m3u8

公式ドキュメント

オプション

hlsファイルを出力する(出力オプションで使う)ときにはmuxerのオプションを指定する。ヘルプコマンドffmpeg -h muxer=hls

  • -start_number [int64]
    セグメントの開始番号。開始番号を変更するのであり、映像の開始をずらすのではない。-hls_flags +second_level_segment_indexを使うときにつける
    範囲:0 から I64_MAX まで
    既定値:0
  • -hls_time [float]
    各セグメントの動画時間。指定した時間が来たら次のキーフレームで分割される。任意の時間で分割する場合にはGOPのフレーム間隔(group of picture size)を決めるエンコードオプションの-g(keyint)、-keyint_minを併用する。-sc_threshold 0をつけるとキーフレームがシーンチェンジにで入らなくなり意図したセグメント分割になる
    範囲:0 から FLT_MAX まで
    既定値:2
  • -hls_init_time[float]
    最初のセグメントの動画時間。0 だと最初のキーフレームが来たら分割する。挙動は-hls_timeと同じ
    既定値:0
  • -hls_list_size [int]
    マニフェストに記載されるセグメント数。0 はすべて
    範囲:0 から INT_MAX まで
    既定値:5
  • -hls_delete_threshold [int]
    ディスクから削除せずに残すセグメント数。-hls_flags +delete_segmentsと併用。詳しくは後述
    範囲:1 から INT_MAX まで
    既定値:1
  • -hls_ts_options [dictionary]
    Set output format options using a :-separated list of key=value parameters. Values containing : special characters must be escaped.非推奨に変更し-hls_segment_optionsの使用を推奨
  • -hls_vtt_options [string]
    set hls vtt list of options for the container format used for hls
  • -hls_wrap [int]
    セグメント数の上限を指定する。上限に達すると最初のセグメントから上書きするので容量の削減になる。しかし廃止予定なので代わりに-hls_list_size、-hls_flags +delete_segmentsを併用する
    範囲:0 から INT_MAX まで
    既定値:0
  • -hls_allow_cache [int]
    キャッシュを許可するかどうかを明示的に指定する
    1 は許可するかもしれない、0 は許可してはならない
    範囲:-1, 0, 1
    既定値:-1
  • -hls_base_url [string]
    各セグメントに URL をつける。各セグメントに絶対パスを指定するときに使う
    既定値:なし
  • -hls_segment_filename [string]
    -hls_flags single_fileを使うとそのファイル名で出力し、それ以外は各セグメント番号を含む文字列形式のファイル名で出力する。字幕のセグメント名は指定できない。無指定だとマニフェストのファイル名に連番が振られる。Windows 環境ではdrawtextフィルタと同じように%sが使えず、組み合わせによってもエラーになる
    Windows の ffmpeg で生放送する方法 : drawtext(テキスト描写)
    例:file%03d.ts(file000.ts, file001.ts, file002.ts, …)
    既定値:なし
  • -hls_segment_options [dictionary]
    key1=value1:key2=value2のように指定する。segment muxerの-segment_formatで指定したフォーマットオプションの指定
    FFmpeg Formats Documentation: mpegtsFFmpeg Formats Documentation : mov, mp4, ismv
  • -hls_segment_size [int]
    1セグメント当たりのバイト単位の最大ファイルサイズ値。マニフェストはバイトレンジ(#EXT-X-BYTERANGE)分割になり、マニフェストのバージョンは 4 になる
    範囲:0 から INT_MAX まで
    既定値:0(無制限)
  • -hls_key_info_file [string]
    セグメントの暗号化で使うファイルを指定する。key には 16バイトのバイナリ、または16文字の16進数の文字列を、IV に32文字の16進数の文字列を指定する。書式は後述
    既定値:なし
  • -hls_enc [boolean]
    有効にするとオプションで指定したキーファイルの URI と IV をマニフェストに書き込む。-hls_enc_key-hls_enc_ivを指定する。無効にすると暗号化しない。-hls_key_info_fileと併用すると-hls_key_info_fileが優先される
    既定値:0
  • -hls_enc_key [string]
    指定した16文字の16進数の文字列からキーファイルを作成する
  • -hls_enc_key_url [string]
    -hls_enc 1で暗号化したときに既定値ではマニフェストと同じ場所に「マニフェスト名.m3u8.key」が出力されるが、パスとキーファイル名を指定するとそれがマニフェストに記載される。マニフェストの保存先をカレント以外で出力してキーファイルが参照できずに復号エラーがでたらこのオプションを使う
    例:-hls_enc_key_url “L:\hoge/\piyo.key”
    例:-hls_enc_key_url “hoge/\piyo.key”
  • -hls_enc_iv [string]
    指定した32文字の16進数の文字列からIV を作成する。無指定だとIV=0x00000000000000000000000000000000がマニフェストに記載される
  • -hls_subtitle_path [string]
    字幕マニフェストのファイル名とパス指定。無指定だと映像と音声のセグメント、マニフェストと同じ場所になる
    既定値:なし
  • -hls_segment_type [int]
    セグメントのファイルタイプの指定。詳しくはここ1, fmp4にするとマニフェストのバージョンは 7 になる。-hls_segment_type 1のときは-hls_enc 1の暗号化に対応していない
    範囲:0(mpegts), 1(fmp4)
    既定値:0
  • -hls_fmp4_init_filename [string]
    #EXT-X-MAPタグの mp4 のファイル名の指定。出力ファイルと同じ場所に出力される
    既定値:init.mp4
  • -hls_fmp4_init_resend [boolean]
    -var_stream_mapで2以上のストリームを出力するときに、出力先の指定でフォルダ名に%vをつけるとストリーム番号のフォルダを作成してそれぞれのストリームを別フォルダに出力する。字幕や副音声、言語やビットレート、デフォルトの設定は公式ドキュメントを参照
    既定値:0
    例:ffmpeg -i input -filter_complex split[0][1];[0]scale=1280:-2[0v];[1]scale=640:-2[1v] -map [0v] -map 0:a -c:v:0 libx264 -c:a:0 copy -map [1v] -map 0:a -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 -hls_segment_type 1 -hls_fmp4_init_resend 1 -var_stream_map "v:0,a:0 v:1,a:1" "%v/output.m3u8"
  • -hls_flags[flags]
    フラグの指定。+で複数のオプションをつなげられる

    • single_file
      #EXT-X-BYTERANGEタグを追加する。セグメントを分割せずに1つのファイルで出力する。マニフェストのバージョンは4になる。いわゆるバイトレンジ。暗号化してされてなかったバグが直った
    • temp_file
      セグメントを.tempファイルで一時出力し、1セグメント分の出力が終わるとリネームして通常のセグメント名になる
    • delete_segments
      マニフェストに記載されていないセグメントを順番に削除していく。-hls_list_size 0を併用すると効果が無い
    • round_durations
      マニフェストに記載されている1秒未満の時間を四捨五入する
    • discont_start
      #EXT-X-DISCONTINUITYタグを追加する。セグメントが不連続してるフラグ。配信が止まったとき仕切り直すときにつける。エンコードが正常終了すると#EXT-X-DISCONTINUITYタグは削除される
    • omit_endlist
      最終行に追記される#EXT-X-ENDLISTタグを省略する
    • split_by_time
      時間指定で分割するのでキーフレーム以外で分割されることもある。特定のプレイヤーで挙動がよくなるかもしれないが一般的にはよくならない。-hls_timeと併用する
    • append_list
      最終行に記載されている#EXT-X-ENDLISTタグを削除し、新しいセグメントを追記し再び最後に#EXT-X-ENDLISTタグを追加する
    • program_date_time
      #EXT-X-PROGRAM-DATE-TIMEタグを追加する
    • second_level_segment_index
      -use_localtimeまたは-strftimeを使ったときにセグメントのファイル名にインデックス、つまり連番%dを含める
    • second_level_segment_duration
      -use_localtimeまたは-strftimeを使ったときにセグメントのファイル名に動画時間、つまり%tを含める。単独で使うとセグメント時間が変更にならないと同じファイル名になるので他のフラグを併用する
    • second_level_segment_size
      -use_localtimeまたは-strftimeを使ったときにセグメントのファイル名にファイルサイズ、つまり%sを含める
    • periodic_rekey
      キーファイルが動的に書き換えられるのに対応して再読み込みする。現時点では ffmpeg 単体で-hls_key_info_fileをセグメントの途中で書き換えることは出来ない(はず)
    • independent_segments
      #EXT-X-INDEPENDENT-SEGMENTSタグを追加する。それぞれのセグメントが独立しデコードするのに他のセグメントを参照しない。マニフェストのバージョンは 6 になる
    • iframes_only
      #EXT-X-I-FRAMES-ONLYタグを追加する。Iフレームだけのセグメントを明示するが実際に使うにはエンコード設定もそれに直す必要がある。詳しくは後述
  • -use_localtime[boolean]
    セグメントのファイル名に strftime を割り当てる。廃止予定なので-strftimeを使う。-use_localtime_mkdir、-hls_segment_filenameを併用する
    既定値:0
  • -strftime[boolean]
    セグメントのファイル名に strftime を割り当てる。-use_localtimeの代わりに使う。-strftime_mkdir、-hls_segment_filenameで併用する
    例:-strftime 1 -hls_segment_filename %Y%m%d%H%M%S.ts(20160420231015.ts)
    既定値:0
  • -use_localtime_mkdir[boolean]
    セグメントの出力フォルダ名の指定に strftime を割り当てる。廃止予定なので-strftime_mkdirを使う。-use_localtime、-hls_segment_filenameで併用する。フォルダがなければつくる
    既定値:0
  • -strftime_mkdir[boolean]
    セグメントの出力フォルダ名の指定に strftime を割り当てる。-use_localtime_mkdirの代わりに使う。-strftime, -hls_segment_filenameを併用する。フォルダがなければつくる
    例:-strftime 1 -strftime_mkdir 1 -hls_segment_filename “%Y%m%d/%H%M%S.ts”(20160420/231015.ts)
    そのほかに、%H%M%S、時間分秒も使える
    既定値:0
  • -hls_playlist_type[int]
    • 0:何もつけない。既定値
    • 1, event
      #EXT-X-PLAYLIST-TYPE:EVENTタグを追加し、-hls_list_size 0(マニフェストにすべてのセグメント名を書き込む)も強制される
    • 2, vod
      #EXT-X-PLAYLIST-TYPE:VODタグを追加し、-hls_list_size 0も強制される
  • -method[string]
    指定したHTTPメソッドでセグメントを作る。これを使うにはHTTPサーバがそのメソッドに対応していなければならない
    例:HTTP PUTメソッドでセグメントをアップロードし、refresh時間毎にm3u8ファイルが同じメソッドで更新される
    既定値:PUT
    ffmpeg -re -i in.ts -f hls -method PUT http://example.com/live/out.m3u8
  • -hls_start_number_source [int] 各セグメントのファイル名の様式
    • 0, generic:各セグメントのファイル名に連番が付く。既定値
    • 1, epoch:各セグメントのファイル名に unix time が付く(ライブ配信向け)
    • 2, datetime:各セグメントのファイル名に年月日と時間が付く(ライブ配信向け)
    • 3, epoch_us:各セグメントのファイル名に年月日とマイクロ秒時間が付く(ライブ配信向け)
  • -http_user_agent[string]
    HTTP ヘッダの User-Agent フィールドを優先させる
    既定値:なし
  • -var_stream_map[string]
    マニフェストにどの映像や音声を記載するのかを指定する
    1入力の映像と音声と、2入力の映像と音声をそれぞれのマニフェストで出力する例。スペースを空けると別マニフェストになる
    例:”v:0,a:0 v:1,a:1″
    既定値:1入力の映像と音声
  • -cc_stream_map[string]
    Closed captionsをマニフェストに記載するのかを指定する。reddit
    既定値:無指定(指定しないと出力しない、未確認)
  • -master_pl_name[string]
    複数のマニフェストを参照するマスタープレイリストのファイル名を指定する。出力パスは出力ファイルと同じ場所になるので、同じように相対パスを指定するとエラーになる
    既定値:無指定(指定しないと出力しない)
  • -master_pl_publish_rate[int]
    最初の何セグメントを出力したときにマスタープレイリストを出力するか
    既定値:0
    範囲:0 から INT_MAX まで
  • -http_persistent[boolean]
    HTTP接続を継続する
    既定値:0
  • -timeout[duration]
    I/O処理のタイムアウト時間を指定する
    既定値:-0.000001
  • -ignore_io_errors[boolean]
    ネットワーク越しの長時間配信で IO エラーが出たときに無視する
    既定値:0
  • -headers[string]
    既定値のヘッダから書き換える

hlsファイルを入力する(入力オプションで使う)ときにはdemuxerのオプションを指定する。ヘルプコマンドffmpeg -h demuxer=hls

  • -live_start_index[int]
    何番目のセグメントから開始するか。負の値は最後から計算する
    既定値:-3
  • -allowed_extensions[string]
    アクセス許可する拡張子の指定。カンマ区切りで指定する
  • -max_reload[int]
    不十分なリストを再読み込みする最大の回数
    既定値:1000
  • -m3u8_hold_counters[int]
    リストにセグメントの更新がなかったときに再読み込みする最大の回数
    既定値:1000
  • -http_persistent[boolean]
    持続的なHTTP接続を許可する。HTTPのみで有効。stackoverflow
    既定値:1(有効)
  • -http_multiple[boolean]
    無効にすることで「HTTP error 429 Too Many Requests」のエラーを回避する
    既定値:1(有効)
  • -http_seekable[boolean]
    HTTPセグメントのダウンロードにHTTP partial requestsを使用。0で無効、1で有効、-1でauto
    既定値:auto

ビットレート設定

libx264を使ってのビットレート設定の方法。

CBR(固定ビットレート):-b:v、-minrate 、-maxrateを同じ値で併用。-bufsizeはレートコントロールバッファで、指定した値、例では4000kb/s毎の映像に平均2000kb/sを目標にエンコードする。セグメント時間を厳密にするならこの方法をとる。-bufsize-b:vなどと同じ値にした方がよいかは配信時に確認してないので判断できない。AWS Elemental MediaLiveではバッファサイズは最大ビットレートの2倍を推奨。
ffmpeg -i input -c:v libx264 -x264-params "nal-hrd=cbr" -b:v 2000k -minrate:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:a aac -b:a 128k -f hls output.m3u8

Bitrate over and undershooting despite nal-hrd cbr – Doom9’s Forumより、-bufsizevbv-maxrate / fpsで計算する。25fpsだと80k。-minrateは指定してない。
ffmpeg -i input -c:v libx264 -x264-params "nal-hrd=cbr" -b:v 2000k -maxrate:v 2000k -bufsize:v 80k -c:a aac -b:a 128k -f hls output.m3u8

VBR(可変ビットレート):-b:vで目標とする平均ビットレートを指定。-maxrate 、-bufsizeを併用することで最大ビットレートを制限できる。
ffmpeg -i input -c:v libx264 -b:v 2000k -maxrate 3000k -bufsize 4000k -c:a aac -b:a 128k -f hls output.m3u8

CRF(品質指定):-crfで品質を指定。8ビット深度の映像だと0に近い正の値ほど高品質大容量になる。-maxrate 、-bufsizeを併用することで最大ビットレートを制限できる。目標ビットレートではないので映像次第で指定したビットレートより大きく下回る。
ffmpeg -i input -c:v libx264 -crf 23 -maxrate 3000k -bufsize 4000k -c:a aac -b:a 128k -f hls output.m3u8

Encode/H.264 – FFmpeg
レート制御モード設定 – AWS Elemental MediaLive
HLS Authoring Specification for Apple Devices | Apple Developer Documentation

エンコード設定

うまく配信するにはGOPサイズであったり、映像と音声の同期問題であったり、遅延だったり色々と解決しなければならない問題も多い。

一般的には10秒のGOPが普通だが、ライブ配信はそれより小さくなりABRで映像切り替えがあるのでGOPを短くする。大勢で見ない個人的なファイルで1ストリームを流してシークしないのならばGOPは長くてもよい。

GOPサイズなら-gと、オープンGOP-flags -cgop、クローズドGOP-flags +cgoの設定。通常は圧縮率の高いオープンGOP、つまり設定変更しない。
Compressor 4 ユーザーズマニュアル: MPEG-2 に関する参考情報

シーンチェンジにキーフレームを入れずにキーフレーム間隔を固定するなら、-sc_threshold 0ffmpeg Documentation。実際に運用するなら-gをフレームレートのN倍でN*fpsフレームごとにキーフレームを入れて、シーンチェンジにキーフレームを入れさせない-sc_threshold 0を併用する。何フレーム間隔にするかは1セグメント時間の間に1キーフレームを最低限入れる。

シーンチェンジにキーフレームを入れる弊害が固定ビットレートにはある。

シーンチェンジ時にKeyframeやI-frameを入れて効果があるのは、ビットレートを指定しないエンコードの場合です。ビットレートを指定したエンコードでシーンチェンジ検出を有効にするとKeyframeやI-frameに多くのビットを費やされるので、画質は悪くなることがあります。

エンコーダーを支えるffmpeg活用 – DMM inside

つまり意図した通りにセグメント分割するには-hls_timeで分割時間を指定するだけではなく、映像を再エンコードし-g、-sc_thresholdを併用しなければならない。

映像と音声の同期問題は固定フレームの指定-rと、-vsync cfrffmpeg でのフレームレート設定の違い

遅延はエンコード設定とオーバーヘッド、マニフェストにいくつのセグメントを記載する-hls_list_sizeと、セグメント時間-hls_timeが大きく依存する。またsegmentmuxer にあるキーフレームで分割しない-break_non_keyframesを使う方法もある。

バッファの調整では、エンコーダ側(配信側)の設定とデコーダ側(視聴者側)の設定も考える。

再生確認

「mp4をFFmpegでhlsに変換し、ストリーミング再生」(記事削除:https://qiita.com/korsmic/items/fac1d737f48aabb1294f#%E5%AE%9F%E8%A3%85%E3%82%B3%E3%83%BC%E3%83%89)を参考にし、hls.js,plyr.jsを使って再生する。

コマンド例(video フォルダに出力)
ffmpeg -re -i input.mp4 -bsf:v h264_mp4toannexb -c copy -f hls -hls_list_size 0 -flags +global_header video/video.m3u8

セグメントファイル名の設定

設定できるのは任意の桁数で指定できる連番%d、セグメントのマイクロ時間%t、セグメントのファイルサイズ%sに、マニフェスト名の後に続くUNIX時間と年月日時と-strftime 1で使える一部のstrftime関数である。-start_numberを指定すると連番の%dの値が0以外から開始する。-hls_flags+で複数のオプションがつけられる。コマンドプロンプトに直接実行するときは%dなどの%は一つで、バッチファイルなどで実行するときは%%dの2つにする。

%dで0から始まる連番になる。

fmpeg -i input -c:v libx264 -c:a aac output.m3u8
output0.ts
output1.ts
output2.ts
:

second_level_segment_indexフラグは上と同じ連番になる。

fmpeg -i input -c:v libx264 -c:a aac -strftime 1 -hls_flags +second_level_segment_index -hls_segment_filename output%d.ts output.m3u8
output0.ts
output1.ts
output2.ts
:

%03dは0を埋めた3桁の数になる。

ffmpeg -i input -c:v libx264 -c:a aac -strftime 1 -hls_flags +second_level_segment_index -hls_segment_filename output-%03d.ts output.m3u8
output-000.ts
output-001.ts
output-002.ts
:

second_level_segment_durationフラグはセグメントが固定間隔だと同じ値になるので他のオプションと併用する。

ffmpeg -i input -c:v libx264 -c:a aac -strftime 1 -hls_flags +second_level_segment_index+second_level_segment_duration -hls_segment_filename output-%03d-%t.ts output.m3u8
output-000-10000000.ts
output-001-10000000.ts
output-002-10000000.ts
:

second_level_segment_sizeフラグはセグメントごとのファイルサイズがファイル名につく。

ffmpeg -i input -c:v libx264 -c:a aac -strftime 1 -hls_flags +second_level_segment_index+second_level_segment_duration+second_level_segment_size -hls_segment_filename output-%03d-%t-%s.ts output.m3u8
output-000-10000000-472632.ts
output-001-10000000-481092.ts
output-002-10000000-477520.ts
:

-hls_start_number_source 0は通常の連番。

ffmpeg -i input -c:v libx264 -c:a aac -hls_start_number_source 0 output.m3u8
output0.ts
output1.ts
output2.ts
:

-hls_start_number_source 1は UNIX時間。

ffmpeg -re -i input -c:v libx264 -c:a aac -hls_start_number_source 1 output.m3u8
output1575032400.ts
output1575032410.ts
output1575032420.ts
:

-hls_start_number_source 2は年月日と時間分秒。

ffmpeg -re -i input -c:v libx264 -c:a aac -hls_start_number_source 2 output.m3u8
output20191129220000.ts
output20191129220010.ts
output20191129220020.ts
:

-strftime 1で使えるstrftime関数。

フォーマット 説明 返り値の例
%A 曜日の名前 Wednesday
%a 短縮された曜日の名前 Wed
%B 月の名前 January
%b 省略された月の名前 Jan
%d 2桁の日付 08
%H 24時間制の時(00-23) 20
%I 12時間制の時(01-12) 11
%j 年中の通算日(001-366) 008
%M 分(00-59) 53
%m 月を表す数字(01-12) 01
%p 午前または午後(AM,PM) AM
%S 秒(00-60) (60はうるう秒) 06
%U 週を表す数。最初の日曜日が第1週の始まり(00-53) 01
%W 週を表す数。最初の月曜日が第1週の始まり(00-53) 01
%w 曜日を表す数(0-6) 日曜日が0 3
%x 日付(%m/%d/%y) 12/20/19
%Y 西暦を表す数 2014
%y 西暦の下2桁(00-99) 14
%Z タイムゾーンの略称 東京 (標準時)
%z タイムゾーンの略称 東京 (標準時)

ここでは連番で使われていた%dが2桁の日数扱いになる。

ffmpeg -re -i input -c:v libx264 -c:a aac -strftime 1 -hls_segment_filename output-%Y%m%d%H%M%S.ts output.m3u8
output-20191129220000.ts
output-20191129220010.ts
output-20191129220020.ts
:

-strftime_mkdir 1で任意の日時のフォルダの中にセグメントを入れられるが、マニフェストの出力場所にstrftime関数が使えないので直接指定することになる。

ffmpeg -re -i input -c:v libx264 -c:a aac -strftime 1 -strftime_mkdir 1 -hls_segment_filename %Y%m%d/output-%Y%m%d%H%M%S.ts output.m3u8
20191129/output-20191129220000.ts
20191129/output-20191129220010.ts
20191129/output-20191129220020.ts
:

月/日/年(2桁)になる%xは、スラッシュがフォルダ内扱いになるので、-strftime_mkdir 1と併用するとフォルダも同時作成して出力エラーが無くなる。しかしこれもマニフェストの出力場所に strftime関数が使えないので直接指定することになる。12フォルダの23フォルダの19フォルダの中に %Y%m%d%H%M%S.ts のセグメントが出力される。

ffmpeg -re -i input -c:v libx264 -c:a aac -strftime 1 -strftime_mkdir 1 -hls_segment_filename %x/%Y%m%d%H%M%S.ts output.m3u8
12/23/19/20191223220000.ts
12/23/19/20191223220010.ts
12/23/19/20191223220020.ts
:

削除するセグメント数を調整する

-hls_flags +delete_segmentsでマニフェストに記載されていないセグメントを削除できる。以前までは消されずに残っているセグメント数は-hls_list_size + 1だったのが、-hls_flags +delete_segmentsを併用すると残すセグメント数を調整できる。

マニフェストに記載されるセグメント数は-hls_list_sizeの 10。
ffmpeg -i input -c copy -f hls -hls_list_size 10 output.m3u8

マニフェストに記載されるセグメント数は-hls_list_sizeの 10 で、データが残っているのは-hls_list_sizeの 10 + 1 = 11。
ffmpeg -i input -c copy -f hls -hls_list_size 10 -hls_flags +delete_segments output.m3u8

マニフェストに記載されるセグメント数は-hls_list_sizeの 10 で、データが残っているのは-hls_list_size 10-hls_delete_threshold 5の 10 + 5 = 15。
ffmpeg -i input -c copy -f hls -hls_list_size 10 -hls_flags +delete_segments -hls_delete_threshold 5 output.m3u8

マスタープレイリストを出力する

複数のプレイリストをまとめたマスタープレイリストを出力する例。-mapで指定した順番が-var_stream_mapで指定する順番になる。出力マニフェスト名は連番%vの output_0.m3u8、output_1.m3u8 と-master_pl_nameで指定したファイル名、例えば master.m3u8 になる。master.m3u8 を入力するときに-map 0:a:0, -map 0:v:0で音声や映像を指定する方法と、-map p:0(p はプレイリスト)のようにプレリストを指定する方法がある。

2種類の動画をそれぞれコーデックコピーして2つの個別マニフェストとそれを合わせたマスタープレイリストを出力する例。
ffmpeg -i input1 -i input2 -c copy -map 0:v -map 0:a -map 1:v -map 1:a -f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 -var_stream_map "v:0,a:0 v:1,a:1" %v-output.m3u8

ffmpeg -i input1 -i input2 -c copy -map 0:v -map 0:a -map 1:v -map 1:a \
-f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 \
-var_stream_map "v:0,a:0 v:1,a:1" %v-output.m3u8

1つの動画を複数の解像度にリサイズしてマスタープレイリストを出力する例。「Cannot use rename on non file protocol, this may lead to races and temporary partial files.」のエラーは2019年7月時点で出なくなってた。
ffmpeg -i input -filter_complex split[0][1];[0]scale=1280:-2[0v];[1]scale=640:-2[1v] -map [0v] -map 0:a -c:v:0 libx264 -c:a:0 copy -map [1v] -map 0:a -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 -var_stream_map "v:0,a:0 v:1,a:1" %v-output.m3u8

ffmpeg -i input \
-filter_complex split[0][1];[0]scale=1280:-2[0v];[1]scale=640:-2[1v] \
-map [0v] -map 0:a -c:v:0 libx264 -c:a:0 copy \
-map [1v] -map 0:a -c:v:1 libx264 -c:a:1 aac -b:a:1 96k \
-f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 \
-var_stream_map "v:0,a:0 v:1,a:1" %v-output.m3u8

上の-filter_complexを使わずに個別にフィルタを当てる方法もある。
ffmpeg -i input -filter:v:0 scale=1280:-2 -c:v:0 libx264 -c:a:0 copy -filter:v:1 scale=640:-2 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -map 0:v -map 0:v -map 0:a -map 0:a -f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 -var_stream_map "v:0,a:0 v:1,a:1" %v-output.m3u8

ffmpeg -i input \
-filter:v:0 scale=1280:-2 -c:v:0 libx264 -c:a:0 copy \
-filter:v:1 scale=640:-2  -c:v:1 libx264 -c:a:1 aac -b:a:1 96k \
-map 0:v -map 0:v -map 0:a -map 0:a \
-f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 \
-var_stream_map "v:0,a:0 v:1,a:1" %v-output.m3u8

teeデマクサを使いマスタープレイリストとセグメントを1つの動画に出力する例。
ffmpeg -i input -filter:v:0 scale=1280:-2 -c:v:0 libx264 -c:a:0 copy -filter:v:1 scale=640:-2 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -map v:0 -map v:0 -map a:0 -map a:0 -flags +global_header -f tee "[select=\'v:0,a:0,v:1,a:1\':f=hls:master_pl_publish_rate=4:hls_list_size=0:var_stream_map=\'v:0,a:0 v:1,a:1\':master_pl_name=playlist.m3u8:hls_segment_filename=out_%v_%d.ts]out_%v.m3u8|[select=\'v:0,a:0\':movflags=+faststart]out1.mp4|[select=\'v:1,a:1\':movflags=+faststart]out2.mp4"

ffmpeg -i input \
-filter:v:0 scale=1280:-2 -c:v:0 libx264 -c:a:0 copy \
-filter:v:1 scale=640:-2 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k \
-map v:0 -map v:0 -map a:0 -map a:0 -flags +global_header -f tee \
"[select=\'v:0,a:0,v:1,a:1\':f=hls:master_pl_publish_rate=4:hls_list_size=0:\
var_stream_map=\'v:0,a:0 v:1,a:1\':master_pl_name=playlist.m3u8:\
hls_segment_filename=out_%v_%d.ts]out_%v.m3u8|\
[select=\'v:0,a:0\':movflags=+faststart]out1.mp4|\
[select=\'v:1,a:1\':movflags=+faststart]out2.mp4"

詳しい書式は1つの出力内容を複数に振り分ける teeを参照。

-var_stream_mapで指定するストリームに同じ出力内容の複数指定はできないので、

  1. tee個別のプレイリストを出力して、それらをのせたマスタープレイリストを手動でつくる方法。利点は同じエンコードを流用できるので負荷は小さくなるがマスタープレイリストは手動作成になるので、BANDWIDTHの値がビットレート指定のエンコードでないと固定値にならない。個別のマニフェスト名は任意に指定できる
  2. teeプレイリスト出力ではなく個別のファイルを出力して、マスタープレイリストをつくる方法。利点は同じエンコードを流用できるので負荷は小さくマスタープレイリストも作られるが、個別のマニフェスト名は同名の連番指定になる
  3. 同じ設定を出力数だけエンコードして-var_stream_mapで指定する方法。利点は1番目の逆で負荷は大きくなるがエンコードと同時にマスタープレイリストも作られBANDWIDTHの計算も不要で個別のプレイリスト名は同名の連番指定になる
VOD LIVE
1
2 ×
3
  1. ffmpeg -re -i input -filter:v:0 scale=-2:720 -c:v:0 libx264 -c:a:0 aac -b:a:1 128k -filter:v:1 scale=-2:360 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -filter:v:2 scale=-2:240 -c:v:2 libx264 -map v:0 -map v:0 -map v:0 -map a:0 -map a:0 -flags +global_header -f tee "[select=\'v:0,a:0\':f=hls:start_number=0:hls_time=10:hls_list_size=0]high.m3u8|[select=\'v:1,a:1\':f=hls:start_number=0:hls_time=10:hls_list_size=0]medium.m3u8|[select=\'v:2,a:1\':f=hls:start_number=0:hls_time=10:hls_list_size=0]low.m3u8"
    ffmpeg -re -i input \
    -filter:v:0 scale=-2:720 -c:v:0 libx264 -c:a:0 aac -b:a:1 128k \
    -filter:v:1 scale=-2:360 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k \
    -filter:v:2 scale=-2:240 -c:v:2 libx264 \
    -map v:0 -map v:0 -map v:0 -map a:0 -map a:0 -flags +global_header -f tee \
    "[select=\'v:0,a:0\':f=hls:start_number=0:hls_time=10:hls_list_size=0]high.m3u8|\
     [select=\'v:1,a:1\':f=hls:start_number=0:hls_time=10:hls_list_size=0]medium.m3u8|\
     [select=\'v:2,a:1\':f=hls:start_number=0:hls_time=10:hls_list_size=0]low.m3u8"

    master.m3u8 を以下のようにつくる

    #EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-STREAM-INF:BANDWIDTH=3382474,RESOLUTION=1280x720
    high.m3u8
    #EXT-X-STREAM-INF:BANDWIDTH=1250067,RESOLUTION=640x360
    medium.m3u8
    #EXT-X-STREAM-INF:BANDWIDTH=508669,RESOLUTION=320x180
    low.m3u8
  2. ffmpeg -re -i input -filter:v:0 scale=-2:720 -c:v:0 libx264 -c:a:0 aac -b:a:0 128k -filter:v:1 scale=-2:360 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -filter:v:2 scale=-2:240 -c:v:2 libx264 -map v:0 -map v:0 -map v:0 -map a:0 -map a:0 -flags +global_header -f tee "[select=\'v:0,a:0\']high.mp4|[select=\'v:1,a:1\']medium.mp4|[select=\'v:2,a:1\']low.mp4"
    ffmpeg -i high.mp4 -i medium.mp4 -i low.mp4 -c copy -map 0:v -map 0:a -map 1:v -map 1:a -map 2:v -map 2:a -f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" %v-output.m3u8

    ffmpeg -re -i input \
    -filter:v:0 scale=-2:720 -c:v:0 libx264 -c:a:0 aac -b:a:0 128k \
    -filter:v:1 scale=-2:360 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k \
    -filter:v:2 scale=-2:240 -c:v:2 libx264 \
    -map v:0 -map v:0 -map v:0 -map a:0 -map a:0 -flags +global_header -f tee \
    "[select=\'v:0,a:0\']high.mp4|[select=\'v:1,a:1\']medium.mp4|[select=\'v:2,a:1\']low.mp4"
    
    ffmpeg -i high.mp4 -i medium.mp4 -i low.mp4 -c copy 
    -map 0:v -map 0:a -map 1:v -map 1:a -map 2:v -map 2:a -f hls 
    -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" %v-output.m3u8
  3. ffmpeg -re -i input -filter:v:0 scale=-2:720 -c:v:0 libx264 -c:a:0 aac -b:a:0 128k -filter:v:1 scale=-2:360 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k -filter:v:2 scale=-2:240 -c:v:2 libx264 -c:a:2 aac -b:a:2 96k -map v:0 -map v:0 -map v:0 -map a:0 -map a:0 -map a:0 -f hls -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" %v-output.m3u8
    ffmpeg -re -i input \
    -filter:v:0 scale=-2:720 -c:v:0 libx264 -c:a:0 aac -b:a:0 128k \
    -filter:v:1 scale=-2:360 -c:v:1 libx264 -c:a:1 aac -b:a:1 96k \
    -filter:v:2 scale=-2:240 -c:v:2 libx264 -c:a:2 aac -b:a:2 96k \
    -map v:0 -map v:0 -map v:0 -map a:0 -map a:0 -map a:0 -f hls \
    -hls_list_size 0 -master_pl_name master.m3u8 -master_pl_publish_rate 4 \
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" %v-output.m3u8

分割ファイルと連結ファイルを同時出力する

セグメントを出力して連結した動画も出力するコマンド例は5通りある。セグメントを連結するのにマニフェストがない場合はconcatすることで連結できる。1,2,3はsegmentdemuxerを使っている。

concat を使い分ける
詳細な分割設定ができる Segment
Creating multiple outputs – trac.ffmpeg.org

  1. セグメントと連結した動画の2種類エンコードする。つまり負荷も2倍
    ffmpeg -i input.mp4 -c:v libx264 -c:a aac -flags +loop+global_header -bsf:v h264_mp4toannexb -f segment -segment_format mpegts -segment_time 10 -segment_list output.m3u8 output_%04d.ts -c:v libx264 -c:a aac -flags +loop+global_header -bsf:v h264_mp4toannexb output.ts
  2. 上の2種類エンコードせずに、udp などにエンコードした映像を流して再度 ffmpeg で-c copyで取り込みセグメントと連結した動画の2種類を出力する
    ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac -flags +loop+global_header -bsf:v h264_mp4toannexb -f mpegts "udp://localhost:8080"
    ffmpeg -i "udp://localhost:8080" -c copy -f segment -segment_format mpegts -segment_time 10 -segment_list output.m3u8 -c copy output_%04d.ts -c copy output.ts
  3. -hls_list_size 0ですべてのセグメントを書き込むように設定し、各セグメントをエンコードした後に ffmpeg でマニフェストを読み込み-c copyで連結する
    ffmpeg -i input.mp4 -c:v libx264 -c:a aac -flags +loop+global_header -bsf:v h264_mp4toannexb -f segment -hls_list_size 0 -segment_format mpegts -segment_time 10 -segment_list output.m3u8 output_%04d.ts
    ffmpeg -i "output.m3u8" -c copy output.ts
  4. teemuxer を使って1度のエンコードで2出力する(これが一番おすすめ)
    1つの出力内容を複数に振り分ける tee
    ffmpeg -i input.mp4 -c:v libx264 -c:a aac -map 0:v -map 0:a -flags +global_header -f tee "[f=hls:hls_time=10:onfail=ignore]output0.m3u8|[bsfs/v=h264_mp4toannexb:onfail=ignore]output.ts"
  5. 配信で個別セグメントは出力せずにバイトレンジで出力するなら-hls_flags single_fileを使う。出力ファイルは output.m3u8 と output.ts だけ
    ffmpeg -i input -f hls -hls_flags +single_file output.m3u8

中断したのを途中から再開する

エンコード内容の変更や宣伝を入れたりするのに使う。#EXT-X-ENDLISTがついてないときは、-hls_flags +discont_start+append_listをつけて正常終了したマニフェストに変換する。

-hls_flags omit_endlistをつけて意図的に#EXT-X-ENDLISTを省略したマニフェストを作るコマンド例。
ffmpeg -i output.mp4 -c:v libx264 -hls_flags +omit_endlist output.m3u8

-hls_flags +discont_start+append_listをつけて上の output.m3u8 に追記する。実際にエンコード中に output.m3u8 を開いてみると書き込み内容がわかる。append_listをつけないと上書きになる。
ffmpeg -re -i output.mp4 -c:v libx264 -hls_flags discont_start+append_list output.m3u8

EXT-X-DISCONTINUITY タグがある HLS の Live コンテンツのビットレート切り替えで苦労した話 – PLAY DEVELOPERS BLOG

YoutubeにHLSで送信する

通常はRTMPで送信するがHLSでも送信でき、HEVCが使えるのが特徴。

hevc_nvencを使用。
ffmpeg -hide_banner -loglevel quiet -re -fflags +genpts -i "Besalu Ducks.mov" -flags +global_header -c:v hevc_nvenc -tag:v hvc1 -b:v 23500k -r 30 -g 60 -c:a aac -b:a 240k -vf "format=yuv420p" -hls_init_time 6.000 -hls_time 6.000 -strftime 1 -master_pl_name master.m3u8 -http_persistent 1 -f hls -segment_wrap 1 -method POST "https://a.upload.youtube.com/http_upload_hls?cid=©=0&file=master.m3u8"

コーデックコピーの場合。
ffmpeg -hide_banner -loglevel quiet -re -fflags +genpts -i "Acerting Art Rain.mp4" -flags +global_header -c:v copy -tag:v hvc1 -codec:a copy -hls_init_time 6.000 -hls_time 6.000 -strftime 1 -master_pl_name master.m3u8 -http_persistent 1 -f hls -segment_wrap 1 -method POST "https://a.upload.youtube.com/http_upload_hls?cid=©=0&file=master.m3u8"

HLS 配信を設定する – YouTube ヘルプ
HLS Streaming to Youtube, working fine! : ffmpeg

iframes_only のエンコード設定

単にフラグをつけてもIフレームだけの動画にならないので、それにあわせてエンコードの設定をする。このフラグがついている動画は Apple で公開されている。この動画は通常の動画のIフレーム部分だけの動画なのでそれ以外のフレームがない。

HEVC Video with Alpha – WWDC 2019 – Videos – Apple Developer
https://devstreaming-cdn.apple.com/videos/wwdc/2019/506lqy7sprpfyo800/506/hls_vod_mvp.m3u8
https://devstreaming-cdn.apple.com/videos/wwdc/2019/506lqy7sprpfyo800/506/0640/0640_I-Frame.m3u8

HLS で使える映像コーデックは H.264, H.265 なので ffmpeg で使えるコーデックの一覧は以下になる。

  • H.264:libx264, libopenh264, h264_qsv, h264_nvenc, h264_amf
  • H.265:libx265, libkvazaar, hevc_qsv, hevc_nvenc, hevc_amf

QSV 対応の ffmpeg をつくる
ffmpeg に nvenc(cuda)をインストールする
AMD VCE 対応の ffmpeg をつくる

例えば 25fpsの映像に5秒間隔、つまり25*5=125フレーム間隔、のキーフレームを入れて、同様に5秒ごとにキーフレームだけの動画も出力する。キーフレームだけの動画にするにはキーフレーム以外の映像はドロップさせるのでselectフィルタでそれ以外のフレームを選ばないようにしている。だたしこれで HLS 向けに正しくエンコードできているのかは確認できていない。

1ファイル出力なら問題ないのだがセグメント出力すると意図した通りに出力していないのでコマンド例を削除した。

Iフレームだけにエンコードする設定例

-c:v libx264 -intra -crf 23
-c:v h264_qsv -intra -g 1 -look_ahead 0 -q:v 20
-c:v libopenh264 -g 1 -q:v 20
-c:v libx265 -x265-params frame-threads=4:keyint=1:ref=1:no-open-gop=1:weightp=0:weightb=0:cutree=0:rc-lookahead=0:bframes=0:scenecut=0:b-adapt=0:repeat-headers=1

hls_key_info_file の書式

書式

key URI
key file path
IV (optional)

http://example.com/file.key
/path/to/file.key
0123456789ABCDEF0123456789ABCDEF

file.keyをopensslで作って暗号化ファイルを作るシェルスクリプト。

#!/bin/sh
BASE_URL=${1:-'.'}
openssl rand 16 > file.key
echo $BASE_URL/file.key > file.keyinfo
echo file.key >> file.keyinfo
echo $(openssl rand -hex 16) >> file.keyinfo
ffmpeg -f lavfi -i testsrc2 -t 10 -c:v libx264 -hls_key_info_file file.keyinfo output.m3u8

上のシェルスクリプトで作られるfile.keyinfoの例。

./file.key
file.key
50d6dc6e65d9aa0f810a3f699950c8a0

opensslで復号する例。-Kの指定値はfile.keyをバイナリエディタで読むことができる。
openssl aes-128-cbc -d -in output0.ts -out output.ts -K A4856CAC48F1E48D -iv 50d6dc6e65d9aa0f810a3f699950c8a0

ffmpegで復号する例。
ffmpeg -i output.m3u8 -c copy output.ts

ffmpegで復号する例(mp4)。
ffmpeg -i output.m3u8 -c copy -bsf:a aac_adtstoasc output.mp4

output.m3u8 の中身。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="./file.key",IV=0x50d6dc6e65d9aa0f810a3f699950c8a0
#EXTINF:10.000000,
output0.ts
#EXT-X-ENDLIST

ffmpeg でローカルファイルを復号してプレビューする。
ffmpeg -re -allowed_extensions ALL -i output.m3u8 -f sdl -
ffplay -allowed_extensions ALL -i output.m3u8

hls_enc, hls_enc_key, hls_enc_iv で暗号化する

コマンド例、output.m3u8.key が-hls_enc_keyで作られる。
ffmpeg -i input.ts -hls_enc 1 -hls_enc_key A4856CAC48F1E48D -hls_enc_iv 50d6dc6e65d9aa0f810a3f699950c8a0 -c copy -f hls output.m3u8

output.m3u8 の中身。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="output.m3u8.key",IV=0x35306436646336653635643961613066
#EXTINF:3.920844,
output0.ts
#EXTINF:1.251333,
output1.ts
略
#EXT-X-ENDLIST

キーファイルのファイル名とパスの設定は-hls_enc_key_urlを使う。
ffmpeg -i input.ts -c copy -f hls -hls_enc 1 -hls_enc_key A4856CAC48F1E48D -hls_enc_iv 50d6dc6e65d9aa0f810a3f699950c8a0 -hls_enc_key_url hoge.key output.m3u8

output.m3u8 の中身。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="hoge.key",IV=0x35306436646336653635643961613066
#EXTINF:3.920844,
output0.ts
#EXTINF:1.251333,
output1.ts
略
#EXT-X-ENDLIST

保存した m3u8 やセグメントから動画に変換する

HLS 配信している動画でマニフェストやセグメントを保存した場合にマニフェストを書き換えて ffmpeg で動画にする方法。主な用途はライブ配信で過去のセグメントがマニフェストに載っていない場合に、セグメントが連番で推測しやすくアクセスも出来る場合である。

まずマニフェストに記載してあるセグメントを絶対パスにし、最後の行に#EXT-X-ENDLISTがなければ追加する。同様に暗号用の key も絶対パスにする。各セグメントを保存した場合はファイルパスに注意する。

ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -i master.m3u8 -c copy output.ts
ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -i master.m3u8 -movflags +faststart -c copy -bsf:a aac_adtstoasc output.mp4

セグメントが多いとコンソールのログが長くなり処理が遅くなるので、-loglevel 0
で非表示にすることもできる。
ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -i master.m3u8 -c copy output.ts -loglevel 0
ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -i master.m3u8 -movflags +faststart -c copy -bsf:a aac_adtstoasc output.mp4 -loglevel 0

セグメントの開始がキーフレームでは無くて音声は流れるのに映像がすぐに動かない場合は入力ファイルの前に-ss 0を追加するとキーフレームまでの映像をカットする。
ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -ss 0 -i master.m3u8 -c copy output.ts
ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -ss 0 -i master.m3u8 -movflags +faststart -c copy -bsf:a aac_adtstoasc output.mp4

m3u8 を読み込む

マニフェストを読み込むときはhlsdemuxerの設定が使える。しかし挙動の確認が取れていない。
ffmpeg -h demuxer=hls

  • -live_start_index[int]
  • -allowed_extensions[string]
  • -max_reload[int]
  • -m3u8_hold_count[int]
  • -http_persistentint[boolean]
  • -http_multiple[boolean]
  • -http_seekable[boolean]

#5811 (live_start_index option doesn’t work.) – FFmpeg

HLS配信されているマニフェストを保存せずにそのまま読み込んで復号し動画で出力するコマンド。cookie や UA を指定して接続もできる。それらを調べるには開発ツールを有効にしてマニフェストだけを開き「Copy as CURL」で調べられる。

リファラや cookie、UA が必要なときは-headers-user_agentを指定する。-headersの指定内容が複数あるときは複数回-headersを指定する。-user_agent"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"のように指定し、-headers
"Cookie: _alid_=hogehoge==; hdntl=exp=15123456..."のように指定する。

コマンド例
ffmpeg -user_agent "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0" -headers "Cookie: _alid_=hogehoge==; hdntl=exp=15123456..." -i "https://example.com/master.m3u8" -c copy output.ts
ffmpeg -user_agent "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0" -headers "Cookie: _alid_=hogehoge==; hdntl=exp=15123456..." -i "https://example.com/master.m3u8"-movflags +faststart -c copy -bsf:a aac_adtstoasc output.mp4

FFmpegを使ってradikoを録音する方法 – Qiitaを参考に、-headersは以下の設定が可能。

-headers "Origin: https://radiko.jp"
-headers "Accept-Encoding: gzip, deflate"
-headers "Accept-Language: ja,en-US;q=0.8,en;q=0.6"
-headers "Accept: */*"
-headers "X-Radiko-AuthToken: foobar"
-headers "Referer: https://radiko.jp/"
-headers "Connection: keep-alive"
-headers "Cookie: _alid_=hogehoge==; hdntl=exp=15123456..."

How to enable cookies in ffmpeg HLS – Stack Overflow

Windows環境で-headersで複数指定すると先に指定した内容が消えるのでオプションを変えて指定する。

-referer "https://radiko.jp/"
-cookies "nlqptid=nltid=tsn; path=/; domain=somedomain.com;"

強制終了したファイルの互換性について

一般的にはライブ配信の録画にはTSファイルを使うがMP4やMOVファイルでも再生できるファイルで出力できる。

Twitterに投稿しないなら-movflags +frag_keyframe+empty_moovを使う。Twitterにはエラーになり投稿できない。YouTubeやニコニコ動画には投稿できる。
ffmpeg -i https://example.com/master.m3u8 -c copy -movflags +frag_keyframe+empty_moov output.mp4

公式ドキュメント:FFmpeg Formats Documentation : mov, mp4, ismv

  • -movflags frag_keyframe:各フラグメント(セグメント)の最初のフレームをキーフレームとする
  • -movflags empty_moov:mdat atomに空のデータをを書き込む
  • -movflags faststart:ファイルの出力が終わった後にmoov atomを先頭に移動させて、プログレッシブダウンロードに対応する。処理に時間がかかるので既定値では無効になっている

vtt字幕付きHLSの動画を保存する

4.3.1時点では通常には読み込めないので入力オプションに-strict experimentalをつける。

コーデックコピーすると字幕はそのままvtt形式になりPCで見るならおそらく再生できる。
ffmpeg -strict experimental -i "https://example.com/master.m3u8" -c copy output.mkv

モバイル端末向けには字幕をmov_text形式に変更しMP4出力にする。
ffmpeg -strict experimental -i "https://example.com/master.m3u8" -c copy -c:s mov_text output.mp4

【アダルト禁止】動画の保存方法総合スレ【DL】part42

保存と同時に映像を確認する

映像だけで音声は不要の例。
ffmpeg -i "https://example.com/master.m3u8" -c copy output.ts -vf realtime -f sdl -
ffmpeg -i "https://example.com/master.m3u8" -c copy -bsf:a aac_adtstoasc output.mp4 -vf realtime -f sdl -

音声が必要なら標準出力してffplayに渡す。適宜標準入力に対応したプレイヤーのパスを指定することもできる。
ffmpeg -i "https://example.com/master.m3u8" -c copy output.ts -c copy -f mpegts - | ffplay -
ffmpeg -i "https://example.com/master.m3u8" -c copy -bsf:a aac_adtstoasc output.mp4 -c copy -f mpegts - | ffplay -

途中で解像度が変わるときに強制終了させる

ffmpeg -xerror -skip_frame:v nokey -flags:v +drop_changed -i "http://mpegts-live-stream" -c copy -f flv "rtmp://nginxserver/live/streamname" -an -f null -

-xerror : エラーが起きたら終了
-skip_frame:v nokey : ビデオストリームのキーフレームだけでコード
-flags:v +drop_changed : 解像度変化などパラメーターが変更するとエラーを表示しドロップさせる
-an -f null – : デコードされたストリームを必要とするビデオ出力ストリームをマップし、それ以外はデコードしない

Force ffmpeg to quit when input resolution changes – Stack Overflow

更新履歴

オプションが追加されていたので追加した。2016年7月30日
オプションが追加されていたので追加し文章を直した。2016年9月6日
オプションが追加されていたので追加した。2016年9月11日
オプションが追加されていたので追加し順番を直した。2017年2月12日
セグメントを出力して連結した動画も出力するコマンド例。2017年3月26日
hls_key_info_file の書式をわかりやすく書き直した。2017年4月17日
hls_enc, hls_enc_key, hls_enc_key_url, hls_enc_iv のオプションを追加した。 2017年6月8日
concat する方法のリンクが間違ってたのを直した。2017年6月12日
fragment mp4 のオプションを追加した。2017年7月20日
fragment mp4 の記述が間違っているので一度削除。2017年7月22日
fragment mp4 のコマンド例を追加したが、#EXT-X-MAP:URI=”(null)” になるのを回避する方法が分からない。periodic_rekey のフラグ追加。2017年8月10日
ネットから保存した m3u8 から動画に変換するを追加。2017年10月11日
livestreamer、streamlink では使えなかったので記述を削除した。streamlinkは file プロトコルが使える 2017年10月12日
新しいオプションを追加し、マスタープレイリストを出力するコマンド例を追加。2017年12月9日
cookie 指定を headers のオプションに変更しコマンド例を追加。2018年2月21日
use_localtime, use_localtime_mkdir が廃止予定になったので代替のオプションを追加。再生確認の項目追加。オプションの追加。2018年9月10日
GOP、映像音声同期、遅延について追記。2018年11月24日
loop-global_header を loop+global_header に直した。2019年6月19日
.ts 出力には movflags +faststart を消した。2019年7月1日
hls_delete_threshold の説明を追加した。2019年7月5日
記事の順番を見直し目次を細分化した。2019年7月6日
master_pl_name, tee の併用例を追記した。2019年7月30日
headers の利用例を追加した。2019年10月18日
マスタープレイリストの出力例を追加した。2019年10月19日
hls_segment_filename を誤訳していたのを直した。2019年11月18日
中断したのを途中から再開する項目を追加した。2019年11月24日
セグメントファイル名の設定の項目を追加した。2019年11月29日、2019年11月30日
セグメントファイル名の設定の項目の%%の部分を直した。2019年12月23日
hls_fmp4_init_resend、epoch_us を追加。2020年5月2日
VTT字幕付きHLSの動画を保存するの項目を追加。2020年9月22日
強制終了したファイルの互換性についての項目を追加。2020年10月15日
保存と同時に映像を確認するの項目を追加。2020年11月17日
demuxerの項目を追加。2021年5月2日
途中で解像度が変わるときに強制終了させる項目を追加。2022年10月29日
YoutubeにHLSで送信する項目を追加。2022年11月1日

12 thoughts on “ffmpeg で Apple HTTP Live Streaming(HLS)を扱う

  • 匿名希望

    「ts_audio〇〇.ts」の「〇〇」が同じものが来るとちゃんと読み込まないんですけどどうすればいいですか?

    • admin

      使用コマンドがないので詳しくわかりませんが、
      6.セグメントファイル名の設定
      の項目を参考にして、セグメント名を異なる名前にしてください。

      • 匿名希望

        単刀直入に言うと、響 radio station を保存したいんです。本編の前にCM用のセグメントがあって、セグメントの番号が00000から00004まであります。その後、本編の同じセグメント番号が来ると無視されて結果本来の長さより短くなってしまいます。インターネットから取得したものなのでセグメント名を変えることはできなさそうなのですが、どうすればいいですか?

        • admin

          ffmpeg にはセグメントをスキップするコマンドはないので
          CM時間、例えば20秒だと
          ffmpeg -ss 20 -i input …
          のようにして最初の20秒をカットする。
          詳しくは https://nico-lab.net/cutting_ffmpeg/
          それ以外ではm3u8ファイルとキーファイルを保存してから
          m3u8ファイルのキーファイルをローカルの参照に変更して保存する方法。
          m3u8ファイルが3種類ある場合は2番目が本編なので2番目のm3u8ファイルを保存する。
          キーファイルは 3D%3D を == に置換すればブラウザでも保存できる。
          ffmpeg -protocol_whitelist http,https,tls,tcp,crypto,file -allowed_extensions ALL -analyzeduration 30M -probesize 30M -i dynamic_media.m3u8 -c copy output.ts

  • 匿名希望

    すみません。OSはLinux系なんですけど、今まではpythonのダウンロードプログラムを使ってダウンロードしてたんです。ご提示いただいたプログラムはwindows用みたいなんですがlinuxで出来るダウンロード方法ってありますか?

    • admin

      パス指定や改行扱いが少し異なるくらいでダウンロードコマンドはLinux系でもほとんど同じです。エラーが出るのでしたらサイト名やコマンド内容、ログをコメントしてください。

      • 匿名希望

        何度もご回答いただきありがとうございました。
        windowsに変えたのでもう大丈夫です。私のlinux環境があまりよろしくなかったので、少し環境を変えてやってみたいと思います。

  • 匿名

    -hls_delete_threshold
    で指定した値が
    -hls_list_size
    で指定した値より大きい場合は無視(?)されるのは仕様でしょうか?
    例えばセグメント数が5の場合、閾値が5までの指定なら5+1~5で合計6~10個のセグメントが残せるのですが、閾値を10や20にしても5+5の10個を超えるとセグメントが削除されてしまいます

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)