未来を形作るテクノロジーの深掘り記事。

FFmpegの仕組み:知っておきたいメンタルモデル

FFmpegのコマンドをコピペするのはもうやめよう。コンテナ、コーデック、ストリーム、フィルタの考え方を理解して、実際に使える実践レシピを身につけよう。

フィルム、音声、テキストのストリームが漏斗を通り、1つの光るコンテナにまとめられていく機械。

みなさん一度は経験がありますよね。動画を変換したくてググると、キーボードを叩き散らかしたようなコマンドが載ったStack Overflowの回答が出てくる。ffmpeg -i input.mov -c:v libx264 -preset slow -crf 22 -c:a aac -b:a 128k -movflags +faststart output.mp4 。実行すると動く。でも、なぜ動くのかは全然わからない。少し違うことがしたくなると、また Stack Overflow に戻る。私も何年もそれを繰り返してきました。結局、FFmpegが自分に何を求めているのかをきちんと腰を据えて理解するまで、ずっとそんな状態でした。

実はFFmpegはそこまで複雑なツールではありません。一貫したメンタルモデルがあり、それさえ掴めばほとんどのコマンドは読めるようになります。問題は、そのモデルを誰も説明せず、コマンドだけを投げつけてくることです。なので、ここでそれを解消しましょう。

コンテナとコーデック:まず理解すべきこと

動画ファイルで混乱の一番の元になるのが、コンテナとコーデックの違いです。コンテナはファイル形式のことで、.mp4、.mkv、.webm、.movなどが該当します。中身を詰め込む箱のようなものです。一方コーデックは、その箱の中にある映像と音声のデータをどう圧縮するかという方式で、H.264、H.265、VP9、AV1、AAC、Opusなどがあります。

MP4ファイルにはH.264の映像とAACの音声が入っていることもあれば、H.265の映像とOpusの音声が入っていることもあります。その他にも組み合わせは数多くあります。MKVファイルはほぼ何でも入れられます。「MP4に変換」と言うとき、多くの場合は「H.264の映像とAACの音声をMP4コンテナに入れる」ことを指しますが、実際には3つの別々の判断が含まれているんです。

# See what's actually inside a file
$ ffprobe -hide_banner video.mkv
Input #0, matroska:
Stream #0:0: Video: hevc (Main), 1920x1080, 23.98 fps
Stream #0:1: Audio: opus, 48000 Hz, stereo
Stream #0:2: Subtitle: subrip
# This file is:
# Container: MKV (Matroska)
# Video codec: HEVC (H.265)
# Audio codec: Opus
# Plus a subtitle stream

何かをする前にffprobeを使いましょう。入力ファイルの中身を正確に教えてくれます。実は「変換」が必要な場面の半分くらいは、単なるリマックス、つまり同じストリームを再エンコードせずに別のコンテナに移すだけで済みます。これはほぼ一瞬で終わります。

FFmpegコマンドの構造

FFmpegのコマンドはすべて同じパターンに従っています。一度それに気づくと、もう気づかなかった頃には戻れません。

ffmpeg [global options] [input options] -i input [output options] output
# The KEY insight: options apply to the NEXT file.
# Options before -i apply to the input.
# Options after -i (before the output filename) apply to the output.
# This matters! These two commands are different:
ffmpeg -ss 00:01:00 -i video.mp4 output.mp4   # Seeks BEFORE decoding (fast)
ffmpeg -i video.mp4 -ss 00:01:00 output.mp4   # Seeks AFTER decoding (slow, precise)

FFmpegでは位置が重要です。ここで多くの人がつまずきます。オプションの意味は、-iの前に置くか後に置くかで変わることがあります。上の-ssの例がまさにその典型です。-iの前に置くと、FFmpegは入力ファイル内の目的の位置へ直接シークします(速いですが、位置が多少不正確になることがあります)。後ろに置くと、そこまでのすべてをデコードしてから出力を始めます(遅いですが、フレーム単位で正確です)。

ストリームの選択:FFmpegに欲しいものを伝える

動画ファイルには複数のストリームが含まれていることがよくあります。映像、音声、字幕に加え、言語の違う複数の音声トラックが入っている場合もあります。FFmpegでは、ストリーム指定子の構文を使って対象のストリームを選べます。

  • -c:v — 映像ストリームのコーデック(v = video)
  • -c:a — 音声ストリームのコーデック(a = audio)
  • -c:s — 字幕ストリームのコーデック(s = subtitle)
  • -c copy — すべてのストリームを再エンコードせずにコピー
  • -c:v copy -c:a aac — 映像はそのままコピーし、音声はAACに再エンコード
  • -map 0:a:1 — 1番目の入力の2番目の音声ストリームを選択

-c copyフラグは一番重要と言ってもいいものです。再エンコードせずにストリームのデータをそのままコピーします。ほぼ一瞬で、かつ無劣化で終わります。やっているのはデータを別のコンテナへ移すことだけですから。コーデックを変更する必要がないときは、いつでも-c copyを使いましょう。

# Remux MKV to MP4 without re-encoding (instant)
ffmpeg -i input.mkv -c copy output.mp4
# Copy video but re-encode audio (fast — only audio is processed)
ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k output.mp4
# Extract just the audio stream
ffmpeg -i video.mp4 -vn -c:a copy audio.aac
# -vn means "no video" — drop the video stream entirely

実際によく使うレシピ

普段から私が使っているコマンドを紹介します。それぞれ、自分の用途に合わせて書き換えられるように解説も付けています。

ウェブ向けに動画を圧縮する

ffmpeg -i raw_video.mov \
-c:v libx264 \
-preset slow \
-crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
web_video.mp4
# -c:v libx264     → encode video with H.264
# -preset slow     → slower encoding = smaller file (options: ultrafast to veryslow)
# -crf 23          → quality level (18=near lossless, 23=default, 28=low quality)
# -c:a aac         → encode audio as AAC
# -b:a 128k        → audio bitrate 128 kbps
# -movflags +faststart → move metadata to start of file for streaming

CRF(Constant Rate Factor)は、品質を調整する最も重要なつまみです。数値が小さいほど高画質になり、ファイルサイズは大きくなります。一般的なウェブ動画なら、CRF 20〜25がちょうどよい落としどころです。見た目を重視するものにはCRF 22を、品質があまり重要でない背景用や補助的な動画にはCRF 26を使うことが多いです。

再エンコードせずに動画をトリムする

# Extract from 1:30 to 3:45 — no re-encoding, nearly instant
ffmpeg -ss 00:01:30 -to 00:03:45 -i input.mp4 -c copy trimmed.mp4
# If you need frame-accurate cuts (slower):
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:03:45 \
-c:v libx264 -crf 18 -c:a copy trimmed.mp4

サムネイルを生成する

# Single thumbnail at the 10-second mark
ffmpeg -ss 00:00:10 -i video.mp4 -frames:v 1 thumb.jpg
# One thumbnail every 30 seconds
ffmpeg -i video.mp4 -vf "fps=1/30" thumbs/thumb_%04d.jpg
# Resize to 320px wide, maintain aspect ratio
ffmpeg -ss 00:00:10 -i video.mp4 -frames:v 1 \
-vf "scale=320:-1" thumb_small.jpg

動画の一部からGIFを作る

# Good quality GIF with palette generation (two-pass)
ffmpeg -ss 00:00:05 -t 3 -i video.mp4 \
-vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1]; \
[s0]palettegen[p];[s1][p]paletteuse" \
output.gif
# -t 3              → duration: 3 seconds
# fps=15            → 15 frames per second (keep GIFs small)
# scale=480:-1      → resize to 480px wide
# palettegen/use    → generate optimal color palette (much better quality)

フィルタを理解する:真価を発揮する機能

フィルタこそ、FFmpegが「便利なツール」から「とんでもなく強力なツール」へと変わる部分です。-vfフラグ(映像フィルタ)と-afフラグ(音声フィルタ)を使うと、処理を連結できます。フィルタは、順次処理する場合はカンマで区切り、複雑なフィルタグラフを組む場合はセミコロンで区切ります。

# Chain multiple filters: resize, then add padding for exact dimensions
ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,\
pad=1280:720:(ow-iw)/2:(oh-ih)/2:black" output.mp4
# Normalize audio volume
ffmpeg -i input.mp4 -af "loudnorm" -c:v copy output.mp4
# Speed up a video 2x (with audio pitch correction)
ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" -af "atempo=2.0" fast.mp4
# Add text overlay
ffmpeg -i input.mp4 \
-vf "drawtext=text='DRAFT':fontsize=72:fontcolor=red:x=10:y=10" \
watermarked.mp4

フィルタシステムは奥が深く、それだけで1本の記事になるほどです。ただ、核心となる考え方は次の通りです。フィルタはデコードされたフレームに対して処理を行います。つまり、フィルタを使う場合は-c copyを併用できません。映像をデコードし、フィルタをかけ、再エンコードする必要があります。コマンドに-vfが含まれているなら、フルエンコードの時間を見込んでおきましょう。

ハードウェアアクセラレーション:GPUを使う

動画を定期的にエンコードしているなら、ハードウェアアクセラレーションで5〜10倍速くなることもあります。最近のGPUには専用のエンコードハードウェアが搭載されていて、FFmpegはそれを利用できます。

# NVIDIA GPU (NVENC)
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p4 -crf 23 output.mp4
# Apple Silicon (VideoToolbox)
ffmpeg -i input.mp4 -c:v h264_videotoolbox -b:v 5M output.mp4
# Intel Quick Sync (QSV)
ffmpeg -i input.mp4 -c:v h264_qsv -preset medium output.mp4
# Check what's available on your system:
ffmpeg -encoders 2>/dev/null | grep -E '(nvenc|videotoolbox|qsv|vaapi)'

ひとつ注意点があります。ハードウェアエンコーダは速いものの、同じ見た目の画質で比べると、ソフトウェアエンコード(libx264)より若干ファイルサイズが大きくなる傾向があります。画質が重要な単発の変換なら、やはりソフトウェアエンコードが優れています。バッチ処理やリアルタイム配信なら、ハードウェアアクセラレーションが明らかに有利です。私は最終的な本番用のエンコードにはlibx264を、それ以外にはNVENCを使っています。

よくあるミスと回避策

  • 必要もないのに再エンコードする。 トリム、音声の抽出、コンテナの変換だけなら-c copyを使いましょう。30秒切り取るだけの動画を20分かけて再エンコードする理由はありません。
  • CRF値を低くしすぎる。 CRF 18はファイルが巨大になるわりに、多くの人には違いがわからない程度の画質向上しかもたらしません。まずは23から始め、アーティファクトが見えたときだけ下げましょう。一般的なウェブコンテンツなら23〜25で十分です。
  • -movflags +faststartを付け忘れる。 これがないと、MP4のメタデータがファイルの末尾に置かれるため、ファイル全体をダウンロードし終えるまでブラウザが再生を始められません。ウェブ向けの動画には必ず付けましょう。
  • 入力を確認しない。 コマンドを書く前にffprobeを実行しましょう。入力のコーデック、解像度、ストリーム構成が分かっていれば、すべてが格段に楽になります。
  • 行き先のないまま待ち続ける。 FFmpegが固まったように見えるときは、既存ファイルを上書きするかどうかといった質問に答えるのを待っているのかもしれません。自動で上書きするなら-yを、絶対に上書きしないなら-nを付けましょう。

チートシート

デスクトップのテキストファイルに常に残しているものを紹介します。日々の作業のおよそ90%はこれでカバーできます。

# Probe a file
ffprobe -hide_banner input.mp4
# Remux (change container, no re-encode)
ffmpeg -i input.mkv -c copy output.mp4
# Compress for web
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset medium \
-c:a aac -b:a 128k -movflags +faststart output.mp4
# Extract audio only
ffmpeg -i video.mp4 -vn -c:a copy audio.m4a
# Trim without re-encoding
ffmpeg -ss 00:01:00 -to 00:02:30 -i input.mp4 -c copy clip.mp4
# Resize to 720p
ffmpeg -i input.mp4 -vf "scale=-1:720" -c:a copy output.mp4
# Convert to GIF
ffmpeg -ss 5 -t 3 -i input.mp4 \
-vf "fps=12,scale=400:-1" output.gif
# Concatenate files (same codec)
echo "file 'part1.mp4'" > list.txt
echo "file 'part2.mp4'" >> list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy combined.mp4

FFmpegは一日でマスターするツールではありません。しかし、メンタルモデルを理解すれば(コンテナはストリームを保持し、ストリームにはコーデックがあり、オプションは次のファイルに適用され、フィルタは再エンコードを必要とする)、ドキュメントの内容がすっと頭に入ってくるようになります。コマンドを盲目的にコピペすることをやめ、意図を持って組み立てられるようになります。正直なところ、「まるで魔法だ」から「ああ、こういうことをしているのか」へと変わる瞬間は、開発者として得られる最も満足感のある体験の一つです。