なるべく理解したいffmpeg

ストリーム・フィルターチェーン・フィルターグラフ・リンクラベルという概念に注目して調べてみた。

ファイル情報を見る

  • ファイルに含まれるビデオとオーディオの形式を確認してみる。
$ ffmpeg -i sample.flv
ffmpeg version 2.5.4 Copyright (c) 2000-2015 the FFmpeg developers

...中略...

Input #0, flv, from 'sample.flv:
  Metadata:
    starttime       : 0
    totalduration   : 1155
    totaldatarate   : 346
    bytelength      : 50005082
    canseekontime   : true
    sourcedata      : B0AFCF105HH1424147114695748
    purl            : 
    pmsg            : 
    httphostheader  : r18---sn-oguesnl7.googlevideo.com
  Duration: 00:19:14.65, start: 0.000000, bitrate: 346 kb/s
    Stream #0:0: Video: flv1, yuv420p, 426x240, 280 kb/s, 29.97 fps, 29.97 tbr, 1k tbn, 1k tbc
    Stream #0:1: Audio: mp3, 22050 Hz, stereo, s16p, 65 kb/s

...中略...

At least one output file must be specified
  • エラー表示*1されるのだけど、ファイル情報が詳細に表示されるのだ。
  • 二つのストリームオレンジ色の部分)が確認できる。
    • ストリーム=ファイル内部で区別される連続するデータの流れ。
      • Stream #0:0 には、ビデオ形式 flv1 の映像が記録されている。
      • Stream #0:1 には、オーディオ形式 mp3 の音楽が記録されている。

ストリームという概念

ストリームという概念は重要である!

  • ファイルは-iオプションで指定した順に0、1、2...と番号管理されている。
  • 同様に、ファイル中のストリームも順に0、1、2...と番号管理されている。
  • つまり、Stream #0:0とは、ファイル0番のストリーム0番という意味。
    • 一般的に、ストリーム0番にはビデオが記録される。
    • ストリーム1番以降に、一つあるいは複数のオーディオが記録される。
  • ffmpegでは、処理対象をストリーム単位で指定できる。
  • Stream #0:0は、コマンドオプション中で[0:0]のように表記できる。

変換する

  • .flvファイルを.mp4ファイルに変換する。
    • ファイルフォーマットは、出力ファイルの拡張子によって判別される。
$ ffmpeg -i sample.flv sample.mp4

変換しないで取り出す

  • .flvファイルからmp3オーディオだけ取り出す。
    • -acodec copyを指定することで、.flvファイル中のmp3をそのままコピーするのだ。(再変換なし・劣化なし)
    • -acodec copyを指定しないと、再変換する手間と時間がかかり、多少劣化すると思う。(再変換あり・劣化あり)
      • codec = コーデック = データをエンコード(符号化・圧縮化)する時の処理方法のこと。
$ ffmpeg -i sample.flv -acodec copy sample.mp3
  • ちなみに、.flvファイル中にmp3オーディオが含まれているからコピーできるのだ。
  • 含まれているのがaacオーディオだったりすると、エラーが発生してコピーできない。
  • もしaacオーディオが含まれている場合は、以下のように指定すればコピーできる。
$ ffmpeg -i sample.flv -acodec copy sample.aac
  • さらに、h264ビデオとaacオーディオが含まれているなら、劣化なしでmp4ファイルに変換できる。
    • h264とaacがmp4の要件に合っているからコピーできる。
    • 以下の3通りの書き方は、すべて同じ処理を指定している。
# ビデオストリームとオーディオストリームをコピーする
$ ffmpeg -i sample.flv -vcodec copy -acodec copy sample.mp4

# -codecオプション=-vcodecと-acodecをまとめて指定する
$ ffmpeg -i sample.flv -codec copy sample.mp4

# -cオプション=-codecの短縮形
$ ffmpeg -i sample.flv -c copy sample.mp4

指定した範囲を切り出す

  • ビデオやオーディオの好みの部分を指定して、切り出せる。
    • 再生時刻1分10秒から2分30秒まで(スタート時刻とエンド時刻を指定)
$ ffmpeg -i sample.mp3 -ss 1:10 -to 2:30 sample_110_to_230.mp3
    • 再生時刻1分10秒から、1分20秒間を切り出す。(スタート時刻と期間を指定)
$ ffmpeg -i sample.mp3 -ss 1:10 -t 1:20 sample_110_t_120.mp3
  • 上記はどちらも同じ部分を切り出し、1分20秒間のmp3オーディオとなる。

複数のファイルを連結する

  • sample_1.mp4とsample_2.mp4を連結して、sample_1_2.mp4に出力する。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "concat=n=2:v=1:a=1" sample_1_2.mp4
  • -filter_complex オプションで連結の指定
    • concat=
      • n=連結するファイル数(デフォルト: 2)
      • v=ビデオ ストリーム数(デフォルト: 1)
      • a=オーディオ ストリーム数(デフォルト: 0)
  • concatの引数を何も指定しないと、デフォルト設定によって以下のように解釈される。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "concat"             sample_1_2.mp4
# 以下のように解釈される
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "concat=n=2:v=1:a=0" sample_1_2.mp4
  • デフォルトはa=0なので、二つ目のファイルのオーディオは連結されないのだ。
    • 連結とは、一つ目のファイルにconcatの処理をした二つ目のファイルを接続する処理なので、
    • 一つ目のファイルのオーディオはそのまま残るが、二つ目のファイルのオーディオは削除される。
  • また、-iオプションのファイル数より、nの値が小さくてもコマンドは正常に終了するが、nで指定したファイル数しか連結されない。
  • 以下の例では、sample_1_2_3.mp4には、sample_1.mp4とsample_2.mp4しか連結されない。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -i sample_3.mp4 -filter_complex "concat=n=2:v=1:a=1" sample_1_2_3.mp4
  • すべて連結するには、n=3を指定するのだ。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -i sample_3.mp4 -filter_complex "concat=n=3:v=1:a=1" sample_1_2_3.mp4


よって、正しく連結できたかどうかは、動画の最後まで確認する必要がある!

フェードイン・フェードアウト

ビデオ
  • ビデオの先頭から、30フレーム使ってフェードインさせる。
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30" sample_fade-in.mp4
  • ビデオの300フレームから、30フレーム使ってフェードアウトさせる。
$ ffmpeg -i sample.mp4 -vf "fade=out:300:30" sample_fade-out.mp4
  • ビデオの先頭から、1秒使ってフェードインさせる。
$ ffmpeg -i sample.mp4 -vf "fade=t=in:st=0:d=1" sample_fade-in.mp4
  • ビデオの時刻10秒から、1秒使ってフェードアウトさせる。
$ ffmpeg -i sample.mp4 -vf "fade=t=out:st=10:d=1" sample_fade-out.mp4
オーディオ
  • オーディオの先頭から、1秒使ってフェードインさせる。
$ ffmpeg -i sample.m4a -af "afade=t=in:ss=0:d=1" sample_fade-in.m4a
# あるいは
$ ffmpeg -i sample.m4a -af "afade=t=in:st=0:d=1" sample_fade-in.m4a
    • afadeのみ、ss=Nを指定できる。
    • ssの場合、Nに何を指定しても、常に先頭の指定となる。
  • オーディオの60秒から、1秒使ってフェードアウトさせる。
$ ffmpeg -i sample.m4a -af "afade=t=out:st=60:d=1" sample_fade-out.m4a
    • 仮にss=60と指定しても、いきなりフェードアウトが始まってしまう...。

フィルターチェーン・フィルターグラフ・リンクラベルによる連続技

フィルターチェーン
  • フィルターチェーンを使って、二つのフィルター(フェードイン・フェードアウト)を適用する。
    • フィルターチェーンとは、複数のフィルターをカンマ,で区切って指定する方法。
    • 一つ目のフィルター処理、その結果に対して二つ目のフィルター処理...のように適用される。
    • 以下の処理で、フェードインで始まり、フェードアウトで終わるビデオになる。
# -vf(video filters)で指定した場合
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30,fade=out:300:30" sample_fade-in_fade-out.mp4
# -filter_complexで指定した場合
$ ffmpeg -i sample.mp4 -filter_complex "fade=in:0:30,fade=out:300:30" sample_fade-in_fade-out.mp4
フィルターグラフとリンクラベル
  • フィルターグラフとリンクラベルを使っても、フィルターチェーンと同様の処理を行える。
      • フィルターグラフとは、フィルターやフィルターチェーンをセミコロン;で区切って指定する方法。
  • フィルターグラフとフィルターチェーンは似ているので、よく混乱するのだけど、決定的な違いがある。
    • フィルターチェーンは、リンクラベルがなくても処理結果を次のフィルターに引き継ぐが...
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30,fade=out:300:30" sample_fade-in_fade-out.mp4
    • フィルターグラフは、リンクラベルなしでは処理結果を次のフィルターに引き継がない。
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30;fade=out:300:30" sample_fade-in_fade-out.mp4
...エラー発生...
Simple filtergraph 'fade=in:0:30;fade=out:300:30' does not have exactly one input and output.
Error opening filters!
    • フィルターグラフでは、必ずリンクラベルを設定しておく必要がある。
$ ffmpeg -i sample_1.mp4 -vf "fade=in:0:30[a];[a]fade=out:300:30" sample_fade-in_fade-out.mp4
      • [a]という表記がリンクラベル。(英数文字とアンダースコア_を使って任意の名前を命名する)
      • fade=in:0:30のフィルターの結果に[a]というリンクラベルを設定して、
      • [a]が指し示す結果に対してfade=out:300:30フィルターを適用している。
  • たった二つのフィルターの連続では、フィルターグラフの利用価値をあまり見出せないが...
  • フィルターグラフには、フィルターチェーンの区切りとなって、グループ化する役割がある。
  • 例えば、sample_1.mp4とsample_2.mp4を連結してから、フェードイン・フェードアウトする場合...
    • ビデオだけなら、シンプルなフィルターチェーンのみで処理できる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "
concat=n=2:v=1:a=0, fade=in:0:30, fade=out:800:30
" sample_1_2.mp4
    • ビデオとオーディオを扱うと、concatの出力は二つのストリームになるので、必ず二つのリンクラベルが必要になる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "
concat=n=2:v=1:a=1[v][a], [v]fade=in:0:30, fade=out:800:30
" -map [a] sample_1_2.mp4
    • -map [a]は何をしている?
      • フィルター出力がリンクラベルの指定で終了している場合、 -mapにそのリンクラベルを指定して、出力ファイルに書き込む必要がある。
      • 最後のフィルター出力でリンクラベルを指定しなければ、そのフィルター出力は自動的に出力ファイルに書き込まれる。
    • フィルターグラフを使えば、もう少しシンプルに表現できる。
      • フィルターグラフの場合、ビデオは[v]にリンクされ、リンクなしのオーディオは出力ファイルに書き込まれる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "
concat=n=2:v=1:a=1[v]; [v]fade=in:0:30, fade=out:800:30
" sample_1_2.mp4
      • 一方、フィルターチェーンを使うと、リンクなしのオーディオは次のフィルターにリンクしてしまう。
      • だから二つのリンクを受け取ってfadeフィルターがエラーになってしまうのだ!
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "
concat=n=2:v=1:a=1[v], [v]fade=in:0:30, fade=out:800:30
" sample_1_2.mp4
...エラー発生...

リンクラベルを使うなら、フィルターグラフで区切った方が幸せになれそう。

  • フィルターの順序を逆にして、sample_1.mp4をフェードイン・sample_2.mp4をフェードアウトしてから、連結することもできる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "
[0:0]fade=in:0:30[a];
[1:0]fade=out:300:30[b];
[a][0:1][b][1:1]concat=n=2:v=1:a=1
" sample_1_2.mp4
オプション指定 意味
[0:0]fade=in:0:30[a]; sample_1.mp4のストリーム0番をフェードインして、リンクラベル[a]を設定する。
[1:0]fade=out:300:30[b]; sample_2.mp4のストリーム0番をフェードアウトして、リンクラベル[b]を設定する。
[a][0:1][b][1:1]concat=n=2:v=1:a=1 [a]・sample_1.mp4のストリーム1番・[b]・sample_2.mp4のストリーム1番を連結する


難解なffmpegのオプション指定が少しずつ見えてきた!

  • フィルターチェーン・フィルターグラフ・リンクラベルが分かってくると、フィルター処理の流れが見える。
  • そしたら後は、個々のフィルターの動作を調べれるだけで、ffmpegが何をしているのか理解できるのだ。
    • フィルターの動作をすべて覚える必要なんてない。
    • 必要になったらマニュアルを見て調べればいいのだ。

合成

  • ffmpegのフィルター指定は、まるでプログラミングコードである。
  • フィルターチェーン・フィルターグラフ・リンクラベルを理解すると、そのコードの流れが見えてくる。
  • コードの流れが見えてくると、自分が何をわからず、何を調べればいいのか、理解できるようになる。

こうなると、俄然面白くなってくる!

  • 以下をサンプル映像とさせていただき、いろいろなオプション指定を試してみた。
左右に並べる
ffmpeg -i sample_1.mp4  -i sample_2.mp4 -filter_complex "
[0:0]pad=2*iw[a];
[a][1:0]overlay=w
" overlay.mp4


  • padは、ビデオの表示領域を設定する。
    • iwは、入力側のビデオの横幅(input width)
    • 2*iwを設定すると、横幅が2倍のビデオになる。
    • 拡大された領域は、背景が黒くなる。
  • overlayは、二つのビデオを重ね合わせる。
    • [a][b]overlayとした場合、ビデオ[a]にビデオ[b]を重ねる。(ビデオ[b]が上になる)
    • [a][b]overlay=x:yで、ビデオ[a]の(x,y)座標を起点にビデオ[b]を配置する。
      • w・hは、ビデオ[b]の幅・高さ解釈される。
      • W・Hは、ビデオ[a]の幅・高さと解釈される。
      • 指定なしは、0と解釈される。
  • つまり、sample_1.mp4の横幅を2倍にして、(640,0)の座標を起点に、sample_2.mp4を重ね合わせるのだ。
    • sample_1.mp4とsample_2.mp4のビデオサイズは、640x360。
上下に並べる
ffmpeg -i sample_1.mp4  -i sample_2.mp4 -filter_complex "
[0:0]pad=iw:2*ih[a];
[a][1:0]overlay=0:h
" overlay.mp4


  • padとoverlayの指定を上下に配置する設定に変更してみた。
ピクチャー in ピクチャー
ffmpeg -i sample_1.mp4  -i sample_2.mp4 -filter_complex "
[1:0]scale=iw/2:ih/2[red];
[0:0][red]overlay
" -map 1:1 overlay.mp4


  • scaleは、入力されたビデオサイズを拡大・縮小する。
    • scale=iw/2:ih/2は、ビデオの幅と高さを半分に縮小するのだ。
  • map 1:1は、sample_2.mp4のオーディオも合成している。
  • つまり、sample_1.mp4に、幅と高さを半分に縮小したsample_2.mp4を重ねているのだ。
    • overlayの座標指定なしなので、(0,0)を起点として、左上の1/4領域に配置される。
透過合成
ffmpeg -i sample_1.mp4  -i sample_2.mp4 -filter_complex "
[0:0]split[black1][black2];
[black1][1:0]overlay[black1red];
[black1red][black2]blend=c0_mode=average
" -map 1:1 overlay.mp4


  • 重ねたビデオ同士を透過する。
      • しかし、blendフィルターをちゃんと理解できていない...。
      • c0_mode、c1_mode、c2_mode、c3_mode、all_modeの違いは?
      • averageは、何をしている?
ピクチャー in ピクチャー + 透過合成
ffmpeg -i sample_1.mp4  -i sample_2.mp4 -filter_complex "
[1:0]scale=iw/2:ih/2[red];
[0:0]split[black1][black2];
[black1][red]overlay[black1red];
[black1red][black2]blend=c0_mode=average
" -map 1:1 overlay.mp4


  • 上に重なるビデオサイズを調整してから透過合成すれば、ピクチャーinピクチャーの透過合成となる。
確実にconcat
  • 実は、上記サンプル映像二つは、単純にはconcatできない...。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "concat=n=2:v=1:a=1" sample_1_2.mp4

...エラー発生...
[Parsed_concat_0 @ 0x7fabbbc00360] Input link in1:v0 parameters (size 640x360, SAR 1281:1280) do not match the corresponding output link in0:v0 parameters (640x360, SAR 1:1)
[Parsed_concat_0 @ 0x7fabbbc00360] Failed to configure output pad on Parsed_concat_0
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 498 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)

Stream #1:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1281:1280 DAR 427:240], 593 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
  • ならばsample_2.mp4のアスペクト比をsample_1.mp4に合わせてみる。
  • その後にconcatを使えば、ちゃんと連結できた!
$ ffmpeg -i sample_2.mp4 -aspect 16:9 sample_2_16.9.mp4
$ ffmpeg -i sample_1.mp4 -i sample_2_16.9.mp4 -filter_complex "concat=n=2:v=1:a=1" sample_1_2.mp4
  • しかし、上記ではコマンドラインが2行に分かれて、余分な中間ファイルも作成してしまい、あまり嬉しくない。
  • そこで、filter_complexを使って、setdarあるいはsetsarのフィルターを追加することで、一行で連結できた。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "[0:v]setdar=16:9[0v]; [1:v]setdar=16:9[1v]; [0v][0:a][1v][1:a]concat=n=2:v=1:a=1" sample_1_2.mp4
# あるいは...
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "[0:v]setsar=1:1[0v]; [1:v]setsar=1:1[1v]; [0v][0:a][1v][1:a]concat=n=2:v=1:a=1" sample_1_2.mp4
    • [0:v]・[0:a]は、ファイル0番のビデオストリーム・ファイル0番のオーディオストリームという意味のようだ。
    • sample_1.mp4の場合、[0:v]は[0:0]と同じストリーム、[0:a]は[0:1]と同じストリームを指す。

*1:At least one output file must be specified(少なくとも一つは出力ファイルを指定しなければならない)