TS packet 格闘日記

アニメを見ながら TS ときゃっきゃうふふするお仕事。 github: https://github.com/github-mec/mecenc twitter: https://twitter.com/mecenc

Seeking is hard (ffmpeg とシークの話)

ffmpeg で動画の中の特定の場所に飛びたい場合、次のようにすることが多い。

$ ffmpeg -ss seek1 -i input_file -ss seek2 ...

seek1と seek2 はともに時刻を指定する。便宜上、-ss seek1 を前置 ss、-ss seek2 の部分を後置 ss と呼ぶ。どちらも指定時刻へシークするのだが違いがある。前置 ss は高速で不正確、後置 ss は低速で正確だ。次のコマンドを実行すると内部が透けて見える。入力ファイル名は仮に input.mp4 とする。

$ ffmpeg -ss 1 -i input.mp4 -vframes 1 -filter:v showinfo -f null -

$ ffmpeg -i input.mp4 -ss 1 -vframes 1 -filter:v showinfo -f null -

コマンドの意図は次の通り。

  • 動画を開始から1秒後の地点までシークして、そこの画像1枚の情報を showinfo フィルタで標準エラー出力に表示する。
  • 処理した動画は null 形式で標準出力に出力、つまり捨てる。

2つのコマンドは同じ結果を出力するように見えるが、結果は違う。前置 ss は 1 枚分の情報を出力するのに対し、後置 ss はスキップした画像の情報も表示している。おそらく前置 ss では動画の詳細は見ずに動画コンテナに格納されている時刻情報を元にシークしており、後者は画像を 1 枚ずつ処理していると思われる。実際、動画データを変更せずに別のコンテナに詰め替えるだけで前置 ss の正確性は変化する。放送波の TS ファイルはとても大雑把な精度でしかシークできない。また後置 ss も完全に正確なわけではなく、残念ながら数フレームの誤差が発生しているようだ。時刻をフレーム数に変換する部分が変なのかもしれない。

mecenc ではフレーム単位で CM 検出のために動画の解析を行っている、つまり解析箇所に移動するためにシークする必要がある。しかし前置 ss のシークは不正確すぎるし、後置 ss は遅い上に誤差がある以上使えない。よって別の手段が必要である。mecenc では CM 候補の列挙に無音区間を利用しているため、音声解析を先に終わらせておけば解析したい動画の位置は事前にすべて知ることができる。よってファイル全体を一度舐める間に trim フィルタを複数使って、まとめて画像情報を取得している。毎回シークすると気が遠くなるような時間がかかるが、一度全体を舐めるだけであれば許容範囲な時間で収まる。

$ ffmpeg -i input.mp4 \
    -filter:v trim=start_frame=AA:end_frame=BB out/000/%04d.jpg \
    -filter:v trim=start_frame=CC:end_frame=DD out/001/%04d.jpg \
    ....
    -filter:v trim=start_frame=YY:end_frame=ZZ out/012/%04d.jpg

これで正確なシークがそこそこの時間でできるようになった、がニッチ過ぎて参考にできる人は少ないように思う。というかシークするだけでこんな手間をかけさせないで欲しい…