FFmpeg Encoding Explained: CRF, Presets, and Codec Choices
Every ffmpeg quality question eventually reduces to one distinction: are you rewrapping streams or re-encoding them? A container (MP4, MKV, WebM) is a box holding already-compressed streams plus timing metadata. A codec (H.264, H.265, VP9, AV1) is the compression itself. -c copy moves the existing streams into a new box untouched — near-instant, bit-exact, zero quality loss. Anything else decodes every frame and encodes new ones, which costs minutes to hours of CPU and permanently discards detail.
# rewrap: no re-encode, finishes in seconds
ffmpeg -i input.mkv -c copy output.mp4
# re-encode: a brand new H.264 stream, minutes to hours
ffmpeg -i input.mkv -c:v libx264 -crf 23 -c:a aac output.mp4Re-encode only when you actually need to: the target container rejects the source codec, the file is too large, the resolution or frame rate must change, or a filter (-vf) has to touch the pixels. If none of those apply, -c copy is the answer and the rest of this guide is irrelevant. And remember that every re-encode is a generation loss — running an already-compressed file through -crf 23 does not restore a master, it compresses the previous encode's artifacts along with the picture. Encode once, from the best source you have.
## Rate control: CRF, target bitrate, two-pass
Rate control decides how many bits the encoder spends and where. Three modes cover essentially every real job:
- CRF (constant rate factor) — you pick a quality level and the encoder spends whatever bitrate that level needs. A static shot of a wall gets few bits, a confetti explosion gets many. Final file size is unknown until the encode finishes.
- Target bitrate (
-b:v 5M) — you pick a budget and quality floats. Easy scenes waste bits, hard scenes fall apart. - Two-pass — a bitrate-targeted encode that analyzes the whole file first, then distributes the budget across scenes on the second pass. Same final size as single-pass, visibly better quality.
For anything you keep, CRF is the default. You care how the file looks, not that it lands at exactly 240 MB. Use a bitrate target only when something downstream enforces one: a hard upload cap, a fixed streaming ladder rung, a device or network with a bandwidth ceiling. And when you do have a target, use two-pass — the analysis pass is free quality. Compute the video bitrate as total target bits divided by duration in seconds, minus whatever the audio takes.
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 1 -f null /dev/null
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 2 -c:a aac output.mp4// note: Both passes must use identical encoding settings or the pass-1 statistics are meaningless. Pass 1 writes ffmpeg2pass-0.log plus a .mbtree file into the working directory — clean them up afterwards. On Windows the pass-1 sink is -f null NUL, not /dev/null.
A fourth case sits between the two: you want CRF's quality behavior but must never exceed a bandwidth ceiling. Add VBV constraints to a CRF encode and the encoder holds the quality target except where it would breach the cap. Note that -maxrate does nothing on its own — -bufsize defines the window over which the cap is enforced, and without it the encoder has nothing to enforce against.
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -maxrate 4M -bufsize 8M -c:a aac output.mp4## CRF in practice
For libx264 the CRF scale runs 0 to 51, where 0 is mathematically lossless and 51 is unwatchable. The default is 23. The useful working range is 18 to 28; outside it you are usually doing something wrong — below 18 you spend bits on detail nobody can see, above 28 blocking becomes obvious in motion. Lower number means higher quality and a bigger file, a direction that trips people up constantly.
The scale is roughly logarithmic: a change of about 6 halves or doubles the bitrate. If -crf 23 produced a 400 MB file and you need something near 200 MB, -crf 29 gets you there and tells you exactly what you traded. CRF 18 is often called visually lossless, meaning most viewers can't distinguish it from the source at normal playback — it is not lossless in the mathematical sense, which is -crf 0 and produces enormous files.
# near-transparent for most content, large file
ffmpeg -i input.mp4 -c:v libx264 -crf 18 -preset slow -c:a aac output.mp4
# the default: good quality, sane size
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -c:a aac output.mp4
# noticeably compressed, fine for previews and screen recordings
ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset medium -c:a aac output.mp4// note: CRF numbers are not comparable across encoders. libx265 defaults to -crf 28, aimed at roughly the same perceived quality as libx264 at -crf 23 but in a smaller file. Copying -crf 23 from an x264 command into an x265 one gives you a much larger file than you expected, not a better one. libvpx-vp9 and the AV1 encoders use a 0-63 scale — different again.
## Presets: buying compression with time
-preset controls how hard the encoder works to find an efficient representation. The x264/x265 ladder, fastest to slowest: ultrafast, superfast, veryfast, faster, fast, medium (the default), slow, slower, veryslow, placebo.
The critical point, and the one most people get backwards: at a fixed CRF a slower preset does not make the video look better. It makes the file smaller at the same quality. The encoder searches more motion vectors and partition modes, finds cheaper ways to encode the same picture, and the CRF target holds either way. The trade is encode time for file size, not encode time for quality.
medium is the default for good reason. Moving from medium to slow typically buys a single-digit percentage of file size for roughly double the encode time; veryslow costs several times more again for a few percent beyond that. That math is worth it for something you encode once and then store or distribute forever, and not worth it for a clip you are about to send to one person. placebo is a joke name and should be read as one.
The fast end has real uses. ultrafast and veryfast exist for live streaming and real-time capture, where falling behind the clock is a worse failure than a bloated file, and for intermediate files you delete after editing.
### -tune
-tune adjusts the encoder's psychovisual assumptions for a content type. libx264 accepts film, animation, grain, stillimage, fastdecode, and zerolatency (plus psnr and ssim, which are for benchmarking, not viewing). Most of the time, leave it alone. The two that genuinely matter are grain, which stops the encoder from smoothing film grain into mush at the cost of a much bigger file, and zerolatency, which disables lookahead and frame buffering for live pipelines.
ffmpeg -i film_scan.mov -c:v libx264 -crf 20 -preset slow -tune grain -c:a copy output.mkv## Choosing a codec
### H.264 (libx264)
The compatibility king. Effectively every browser, phone, TV, and set-top box from the last fifteen years decodes H.264 in hardware. It compresses worse than everything newer, and it is still the right default whenever the file simply has to play for someone else with no conversation about it. Add -pix_fmt yuv420p if the source might be 4:2:2 or 10-bit, since many players and browsers reject anything else.
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 192k output.mp4### H.265 / HEVC (libx265)
Roughly half the bitrate of H.264 at comparable quality, for several times the encode time. Hardware decode is widespread on phones, Apple devices, and modern TVs, but patent licensing kept it out of Chrome and Firefox for years, so it remains a poor choice for a plain <video> tag on the open web. It is an excellent choice for archiving and for anything you play back on hardware you control.
// note: The MP4 muxer tags HEVC as hev1 by default, and Safari, QuickTime, and many hardware players refuse to open that. Add -tag:v hvc1 whenever you put libx265 into an .mp4. This one flag is the single most common reason an H.265 file "doesn't work" on a Mac or iPhone.
ffmpeg -i input.mov -c:v libx265 -crf 20 -preset slow -tag:v hvc1 -c:a aac -b:a 192k output.mp4### VP9 (libvpx-vp9)
Google's royalty-free answer to H.265, and the reason WebM plays in every browser. Compression sits between H.264 and H.265 in practice. Its main ffmpeg quirk: constant-quality mode requires -b:v 0 alongside -crf, otherwise the encoder quietly switches to constrained-quality and chases a bitrate instead. The scale is 0-63, not 0-51, with around 30 a reasonable start for 1080p. Default encoding is famously slow — -row-mt 1 enables row-based multithreading and helps a lot.
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -row-mt 1 -c:a libopus output.webm### AV1 (libsvtav1)
The best compression available and royalty-free, at the cost of the slowest encodes. Use libsvtav1 rather than the reference libaom-av1 unless you have a specific reason — SVT-AV1 is dramatically faster for a marginal efficiency loss. It has its own -preset scale, 0 (slowest) through 13 (fastest), unrelated to the x264 names, plus a 0-63 CRF scale. Chrome, Firefox, and Edge decode AV1 broadly; Safari's support came later and depends on the device. Hardware decode only exists on recent GPUs and phones, so older machines fall back to CPU decoding and can struggle at 4K.
ffmpeg -i input.mp4 -c:v libsvtav1 -crf 30 -preset 6 -c:a libopus output.webm## Audio, briefly
Audio is a rounding error next to video bitrate, so do not over-think it. If the source audio is already in a codec the target container accepts, use -c:a copy and move on — free and lossless. When you must re-encode, -c:a aac -b:a 192k is right for MP4 and near-universally playable; libopus is better at every bitrate and is the correct choice for WebM, which does not accept AAC at all. Re-encoding audio repeatedly is more audible than re-encoding video, so copy whenever the container allows it.
// note: -c:a copy is a stream copy, so a resample request goes nowhere: -c:a copy -ar 48000 writes the original sample rate with no error and no warning. (A filter like -af volume=2.0 at least fails loudly, since filtering and stream copy cannot be combined.) Drop the copy if you actually want the change.
## Hardware encoders
h264_videotoolbox and hevc_videotoolbox on macOS, h264_nvenc and hevc_nvenc on NVIDIA GPUs, and the _qsv encoders on Intel all hand the work to a fixed-function block instead of the CPU. They are often an order of magnitude faster and use almost no CPU. They also compress meaningfully worse: at any given bitrate a hardware encode looks worse than a slow libx264 or libx265 encode, because the silicon cannot afford the exhaustive searches software presets perform.
Use them where speed is the constraint — live streaming, batch-converting a camera card, generating editing proxies. Do not use them for a master you intend to keep. Two flag details: VideoToolbox is bitrate-driven rather than CRF-driven, and it ignores libx264-style -preset names entirely; NVENC's constant-quality mode is -cq, which behaves as true constant quality only when you also pass -b:v 0.
# macOS: bitrate-driven, no CRF
ffmpeg -i input.mp4 -c:v h264_videotoolbox -b:v 5M -c:a copy output.mp4
# NVIDIA: constant-quality mode, p1 (fastest) to p7 (slowest)
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p7 -cq 23 -b:v 0 -c:a copy output.mp4## Picking settings without thinking
- Container change only, codecs already fine →
-c copy. Seconds, lossless. - Share it with anyone, anywhere →
libx264 -crf 23 -preset medium, AAC audio, MP4. - Archive a master →
libx265 -crf 20 -preset slow -tag:v hvc1, orlibx264 -crf 18when compatibility outranks size. - Hard size cap (upload limit, disc) → two-pass with a computed
-b:v, never single-pass CBR. - Bandwidth ceiling but size can float →
-crf 23 -maxrate 4M -bufsize 8M. - Open-web
<video>, royalty-free →libvpx-vp9 -crf 30 -b:v 0for reach,libsvtav1 -crf 30 -preset 6for size. Opus audio, WebM container. - Live stream or real-time capture →
-preset veryfast -tune zerolatency, or a hardware encoder. - Editing proxies and throwaway previews →
-preset ultrafast -crf 28. Quality does not matter here, your time does. - Grainy film scan → add
-tune grainand expect a much larger file.
When you are unsure, encode thirty seconds of the hardest part of your footage at two or three settings and look at them side by side. That takes a minute and settles arguments that bitrate charts cannot.