MpegFlowBlogBack to home
← Blog·ffmpeg

FFmpeg presets that survive production

Honest operational lessons on which FFmpeg presets you can trust under load — psy-rd, lookahead, threading, GOP discipline, partition control. The defaults that bite, the settings worth pinning, and why preset stability matters across encoder versions.

ByMpegFlow Engineering Team
·May 9, 2026·9 min read·1,895 words
In this post
  1. Preset = encoder configuration, not encoder identity
  2. What "preset stability" means and why it matters
  3. Production presets per use case
  4. Bucket 1: Broadcast-grade VOD master encode
  5. Bucket 2: ABR ladder rendition encoding
  6. Bucket 3: Archive transcoding
  7. Bucket 4: Live encoder
  8. Defaults that bite
  9. HEVC-specific notes
  10. NVENC presets
  11. Preset choices are infrastructure
  12. Closing

The first version of any video pipeline runs ffmpeg -i input.mov -c:v libx264 -preset medium output.mp4 and it works. The tenth version runs the same command on a different machine and produces a subtly different file size, and now QC is asking why two encodes of the same master differ by 4MB.

This post is about the FFmpeg preset choices that survive that conversation — and the ones that quietly betray you under load. We've collected this from running FFmpeg at production scale and watching where the bodies are buried. None of it is theoretical; all of it is what we wish we'd known on day one.

#Preset = encoder configuration, not encoder identity

A preset name like medium or slow is shorthand for a bundle of low-level x264/x265 parameters: motion estimation method, subpixel refinement depth, partition decision granularity, b-frame analysis depth, lookahead size, and a dozen more. The name promises a quality/speed tradeoff. The reality is that the parameter bundle behind medium has shifted across libx264 minor versions and almost certainly differs between FFmpeg builds shipped by different distributions.

If you treat presets as named opaque blobs, you cannot reproduce encoder behavior across deploys. The right pattern: pin the encoder version, pin a specific preset, and document the parameters the preset expands to so you can verify them in build artifacts.

#What "preset stability" means and why it matters

Preset stability is the property where a given preset name + encoder version produces identical output for identical input. Sounds obvious. It isn't.

Three failure modes we've watched:

Encoder upgrade flips a default. libx264 minor releases occasionally adjust internal defaults. A preset bundle composed of those defaults silently shifts. Output files differ by a small amount. Customer's QC system flags 50 jobs as "different from expected." You spend a week proving the encoder is technically correct.

FFmpeg build flags affect linked encoders. The libx264 your distribution ships with may be compiled with different optimization flags than the upstream tarball. ARM builds, --enable-shared builds, and statically-linked builds all produce subtly different output for the same source.

Threading is non-deterministic. Multi-threaded x264/x265 encoding parallelizes slice analysis. Frame-level decisions can shift slightly depending on which thread completes first. For two encodes of the same input on the same hardware, output is byte-identical 95% of the time and within a few hundred bytes the rest. For broadcast contractual delivery, this is "close enough." For bit-for-bit reproducibility, you need single-threaded encode (with the throughput penalty that implies).

The discipline: encoder version is part of your job spec, the preset is named explicitly (not inherited from a default), and the audit log records both. We covered the audit-trail design that makes this practical.

#Production presets per use case

Different workloads demand different presets. The wrong move is using one preset for everything because "it works." Here's the four-bucket framework we run.

#Bucket 1: Broadcast-grade VOD master encode

The use case: you encode the primary mezzanine that all delivery renditions derive from. Quality matters more than speed. This file lives in your archive for years.

ffmpeg -i master.mov \
  -c:v libx264 \
  -preset slower \
  -tune film \
  -crf 17 \
  -profile:v high \
  -level 4.2 \
  -pix_fmt yuv420p \
  -x264opts keyint=48:min-keyint=48:no-scenecut \
  -c:a aac -b:a 320k \
  master_h264.mp4

The slower preset is worth its compute cost on the master. CRF 17 produces near-lossless output that's still reasonable in size. Closed GOP (keyint=48 + no-scenecut) makes downstream ABR ladder generation deterministic. The full pipeline is in the broadcast-grade VOD architecture.

What people get wrong: using slow instead of slower to save 30% encode time. At master-encode volume the time difference is real, but the quality delta on hard scenes (panning grass, water, fire) is visible at quality bars broadcast QC catches.

#Bucket 2: ABR ladder rendition encoding

The use case: you encode the 4-6 renditions that streaming clients actually receive. Speed matters more here because you're encoding the same source 4-6 times. Quality is calibrated per-rendition.

# 1080p rendition
ffmpeg -i master_h264.mp4 \
  -c:v libx264 \
  -preset medium \
  -crf 21 \
  -profile:v high \
  -level 4.0 \
  -pix_fmt yuv420p \
  -x264opts keyint=48:min-keyint=48:no-scenecut \
  -maxrate 5500k -bufsize 11000k \
  -c:a aac -b:a 192k \
  rendition_1080.mp4

The combination that matters: -maxrate + -bufsize enforces VBV-compliant streams that won't break HLS bandwidth estimation, while CRF gives you quality-targeted encoding instead of fixed bitrate. The closed GOP (keyint=48:no-scenecut) keeps segment boundaries deterministic, which packagers depend on.

What people get wrong: dropping no-scenecut because they read it produces "better" quality. It does — for the player when watched locally. For an HLS player handling rendition switches at segment boundaries, scene-cut keyframes that don't align with segment boundaries cause visible artifacts at every switch.

#Bucket 3: Archive transcoding

The use case: bulk migration of legacy formats to modern containers. Throughput matters most. The output is destined for storage, not direct streaming. We covered the architecture in petabyte archive migration.

ffmpeg -i legacy.mxf \
  -c:v libx264 \
  -preset fast \
  -crf 19 \
  -tune fastdecode \
  -c:a aac -b:a 192k \
  archive.mp4

The fast preset trades 5-10% file-size efficiency for 3-4× throughput. At petabyte scale, that throughput improvement saves weeks of wall-clock time on the migration. CRF 19 keeps quality acceptable for archive (which is going to be re-encoded for delivery later anyway).

What people get wrong: using ultrafast to push throughput further. The compute savings beyond fast is small (~15%); the file-size penalty is large (~25-40% bigger files). At archive scale you'll pay that storage delta forever.

#Bucket 4: Live encoder

The use case: real-time encoding from a contribution feed (SRT, RTMP) for live broadcast or OTT. Latency is the constraint; quality is calibrated to fit within the latency budget.

ffmpeg -i srt://encoder:9999 \
  -c:v libx264 \
  -preset veryfast \
  -tune zerolatency \
  -bf 0 \
  -refs 1 \
  -profile:v high \
  -level 4.0 \
  -pix_fmt yuv420p \
  -x264opts keyint=60:min-keyint=60:no-scenecut \
  -maxrate 5000k -bufsize 5000k \
  -c:a aac -b:a 128k \
  -f flv rtmp://packager:1935/live/stream

The tune zerolatency flag disables b-frame reordering and lookahead, which are death for live latency. bf=0 (no b-frames) and refs=1 (single reference frame) reduce decoder buffering on the player side. This is a real quality penalty — visibly worse than VOD encoding — accepted for the latency win.

What people get wrong: omitting tune zerolatency. Without it, libx264's lookahead can buffer 20+ frames before emitting any output, adding ~700ms of encoder latency that compounds with packager latency.

#Defaults that bite

The settings most teams don't think about until they cause an incident.

Threading. -threads 0 (auto, the default) lets x264 pick. On a 32-core machine running 4 encodes in parallel, "auto" gives each encode 8 threads, but the threading model uses slice-parallel encoding which has measurable quality cost. For ABR ladder encoding where you want consistent output, set -threads 4 explicitly. For master encodes where quality matters most, set -x264opts threads=1:sliced-threads=0 and accept the slower encode for byte-identical output.

Partition decision. The default partition mode (p8x8,b8x8,i8x8,i4x4 for medium) is well-balanced for most content. For animation or screen content with large solid regions, adding ,p4x4 improves quality measurably. The cost: ~10% slower encode. The benefit: ~5-8% smaller files at same quality. Worth it for animation libraries; not for live-action content.

B-frame analysis. -bf 3 (the default for medium) inserts b-frames between p-frames for compression efficiency. Players support this universally; older media servers (looking at you, RTMP-era streaming) sometimes don't. If you're encoding for legacy delivery, -bf 0 is the safe default; if you're encoding for modern HLS/DASH players, the default is fine.

Lookahead. -x264opts rc-lookahead=40 is roughly the default for medium. Higher numbers (60-80) improve rate-control quality on hard scenes; lower numbers (10-20) reduce encoder memory. For multi-rendition ABR ladders where you encode 4-6 simultaneous streams on the same machine, dropping lookahead to 20 saves significant memory without much quality cost.

GOP structure. Default GOP (250 frames at 30fps = ~8 seconds) is fine for general-purpose encoding. For ABR streaming where players segment at GOP boundaries, you want GOP aligned with segment length — usually 2-4 seconds. -g 60 (2 seconds at 30fps) is the conventional choice for HLS/DASH. Closed GOP (-x264opts keyint=60:min-keyint=60:no-scenecut) is non-negotiable for clean segment boundaries.

Adaptive quantization. -aq-mode 1 (variance) is a libx264 default. -aq-mode 2 (auto-variance) usually improves quality on faces and detailed regions at no compute cost. Worth setting explicitly — relying on the default means it could change in a future libx264 release.

#HEVC-specific notes

libx265 has presets with the same names as libx264 but different parameter bundles behind them. Don't assume medium means the same thing across both encoders.

For HEVC ABR ladders we run medium for renditions and slow for masters, because libx265's slower and veryslow presets are dramatically slower than their x264 counterparts (often 4-8× slower) for marginal quality gain. The compute math doesn't justify the slower presets at production volume.

HDR10 metadata signaling deserves its own treatment — master-display, max-cll, color primaries, transfer characteristics. If you're encoding HDR, every parameter matters. Get them wrong and the player either falls back to SDR (best case) or shows wildly oversaturated output (worst case). The broadcast-grade VOD architecture walks through the HDR signaling chain.

#NVENC presets

GPU encoding (NVENC on NVIDIA T4 / A10) uses preset names that don't map directly to libx264's. NVENC's p1 through p7 is the speed/quality scale, with p7 (slowest, highest quality) approximately equivalent to libx264's medium to slow. Don't expect NVENC's p7 to match libx264's slow for output quality — NVENC produces visibly different output even at the highest quality settings.

Use NVENC for live encoding (latency wins), high-throughput archive transcoding (compute savings), and proxies/preview renditions. Use libx264 for primary VOD master encoding where quality is paramount and throughput is secondary.

#Preset choices are infrastructure

The thread running through all of this: presets are not encoder configuration you set once and forget. They're infrastructure you version, document, and pin. Encoder version + preset name + parameter expansion = part of the contract between your pipeline and the output it produces.

The teams that operate this well treat preset choices like Terraform configuration: in a repo, reviewed in PR, deployed via CI. Not buried in a worker script someone wrote two years ago. The audit log records which preset version produced which output, so the QC question "did the same encoder produce these two ladders?" has a one-query answer.

The teams that operate this badly find out when an encoder version upgrade ships and outputs start subtly differing, and they spend two weeks chasing what changed. We've seen it. The fix is the discipline above.

#Closing

If your team is comfortable with one preset for everything because "it works," ship for now and pin the version you're using before the next libx264 release. If your team is rebuilding the operational layer for FFmpeg from scratch, the running FFmpeg at scale post is what we wish we'd had on day one. If your team is at the volume where the preset choice multiplies across millions of minutes of output, it's worth treating preset configuration as code.

We track preset versions per pool in MpegFlow's audit log so customers can answer "what produced this output" with confidence. That's not a marketing feature; it's what you need to survive a real conversation with broadcast QC.

Topics
  • FFmpeg
  • presets
  • Operations
  • production
  • codec
See also

Related reading

  • Engineering blog
    Running FFmpeg at scale
    Queues, retries, audit — what FFmpeg leaves to you
  • Engineering blog
    FFmpeg in Kubernetes: pod, queue, operator
    The four patterns and where each one breaks
  • Watermarking and overlays — burning logos, tags, and identifiers into video for streaming
Want this in production?

Join the MpegFlow beta.

We're shipping the encoder MVP this quarter. Slot opens when it can take your traffic — no card, no console waiting.

Join the beta More posts
© 2026 MpegFlow, Inc. · Trust & complianceAll systems nominal·StatusPrivacy