FFmpeg 9 Didn't Rewrite the Filter Graph. Keep Your Workarounds

You upgraded a worker to FFmpeg 9, read that the filter engine was rewritten to kill mixed-format errors, and your overlay still prints Buffer queue overflow, dropping. Before you spend an afternoon deleting setpts lines on that promise, it's worth checking where the promise came from.
Quick answer: The FFmpeg 9 filter graph did not get a new execution engine. FFmpeg 9.0's real format-negotiation work is Niklas Haas's libswscale rewrite plus proper modelling of interlaced content during negotiation, per the release write-up by Jean-Baptiste Kempf, and the widely quoted line about "eliminating a huge class of errors where filters would silently drop frames" traces to one unsourced blog post that also names filters FFmpeg doesn't ship. Re-run on 9.0.2, three of the four classic filtergraph failures behave exactly as they did on 8.1.3, so keep your normalization step. If you'd rather not maintain two FFmpeg branches to find out, send the job to FFmpeg Micro and let a managed build handle input normalization in one API call.
Conventional wisdom after a major release: the new version fixed the errors you've been working around, so the workarounds are dead weight. That's sometimes true. Here it isn't, and the reason isn't that FFmpeg's maintainers missed something. It's that the thing people think changed and the thing that actually changed are two different subsystems. Format negotiation got better at converting between pixel formats. None of these four errors is a pixel-format problem. Three of them are timestamp problems wearing a filtergraph costume.
Where the FFmpeg 9 filter engine rewrite claim came from
The sentence circulating about FFmpeg 9 is a single claim counted twice. A dev.to explainer says "the internal filter execution engine has been rewritten to support dynamic frame rate and format negotiation" and that this "eliminates a huge class of errors where filters would silently drop frames or produce mixed-format output." A Topaz Community post reproduces that wording nearly verbatim. Neither cites a commit, a changelog line, or an FFmpeg source.
The verifiable details in that post are wrong in ways you can check in a minute. It names a transpose_nv12 filter. No such filter exists: NV12 support was a proposed patch to `transpose_npp`, and FFmpeg 9.0 deleted every libnpp filter, transpose_npp included. It names eq_vulkan, which is absent from FFmpeg's own Vulkan filter list (avgblur_vulkan, bwdif_vulkan, chromaber_vulkan, color_vulkan, flip_vulkan, overlay_vulkan, scale_vulkan, v360_vulkan, xfade_vulkan, nlmeans_vulkan). So the headline example, chaining scale_cuda into transpose_nv12 into eq_vulkan with no GPU-to-CPU round trip, can't be run as written. Commenters on the Hacker News thread flagged the same post's claim about dictionary-based filter parameters as "not in this release."
What Kempf's release write-up actually describes is narrower and real: more than 2,200 commits from over 160 authors across 1,781 files, 4.5 months after 8.1 "Hoare," an ABI break across all seven libraries, and a multi-year swscale rewrite with 300-plus libswscale commits that decomposes conversions into elementary operations behind an optimizer and kernel backends. It mentions "proper modelling of interlaced content in the format negotiation." It says nothing about dropped frames, mixed-format output, or any user-facing error going away.
The retest: four documented errors on 8.1.3 vs 9.0.2
The four filtergraph failures this blog has already written up all went back on the bench, re-run on both live branches. FFmpeg 9.0.2 "Lei" shipped September 18, 2026; 8.1.3 "Hoare" followed on September 21, which is the line most Docker base images still pin.
| Error | 8.1.3 | 9.0.2 | Workaround |
|---|---|---|---|
| `Buffer queue overflow, dropping.` | Fires | Fires | Keep |
| concat across different resolutions | Fails | Fails | Keep |
| `Non-monotonous DTS in output stream` | Warns | Warns | Keep |
| `Impossible to convert between the formats` | Fails | Partly improved | Keep on hardware paths |
Buffer queue overflow, dropping
Buffer queue overflow, dropping. still fires on 9.0.2 with the same input that triggers it on 8.1.3: an overlay whose two inputs arrive with a timestamp spread, usually because one side was shifted with -itsoffset or came in as variable frame rate. The message comes from framesync inside the filter, not from format negotiation, which is why nothing in the swscale rewrite touches it. The underlying FFmpeg trac ticket 2467 is still open.
The fix is the same one it was before: make both inputs constant frame rate before they reach overlay, and pin the frame rate explicitly rather than hoping the graph infers it.
ffmpeg -i base.mp4 -itsoffset 1.5 -i logo.mov \
-filter_complex "[0:v]fps=30,setpts=PTS-STARTPTS[b];\
[1:v]fps=30,setpts=PTS-STARTPTS[o];\
[b][o]overlay=W-w-24:H-h-24:eof_action=pass[v]" \
-map "[v]" -map 0:a -c:v libx264 -crf 20 out.mp4
The full diagnosis of why the queue fills is in FFmpeg "Buffer Queue Overflow, Dropping" Is a Timestamp Bug. Nothing in it needs revising for 9.0.
Concat across different resolutions
Joining clips of different resolutions still requires normalizing first on 9.0.2. The concat filter refuses mismatched inputs with Input link in1:v0 parameters (size 1280x720, SAR 1:1) do not match the corresponding output link parameters (1920x1080, SAR 1:1), and the concat demuxer with -c copy still produces a file whose second segment is garbage or stretched, because stream copy can't change the decoder configuration mid-file. FFmpeg 9.0 did not add implicit scaling inside concat, and it was never going to: the filter has no basis for choosing which canvas wins.
So the scale plus pad prefix stays, on every input, before concat:
ffmpeg -i a_1080p.mp4 -i b_720p.mp4 \
-filter_complex "[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,\
pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30[v0];\
[1:v]scale=1920:1080:force_original_aspect_ratio=decrease,\
pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30[v1];\
[0:a]aresample=48000[a0];[1:a]aresample=48000[a1];\
[v0][a0][v1][a1]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -crf 20 -c:a aac out.mp4
That normalize-first block is the piece most pipelines get wrong, and it's also the piece you don't have to write yourself. Send both clips as an inputs array to the FFmpeg Micro API with an output format and a preset, and the resolution, SAR, frame rate, and sample rate normalization happens on a managed build on our side:
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{"url": "gs://<YOUR_BUCKET>/a_1080p.mp4"},
{"url": "gs://<YOUR_BUCKET>/b_720p.mp4"}
],
"outputFormat": "mp4",
"preset": {"quality": "medium", "resolution": "1080p"}
}'
You get a job id back, poll GET /v1/transcodes/:id until it reports completed, then fetch a signed URL from the download route. No local FFmpeg, and no second branch to test against.
Non-monotonous DTS
Non-monotonous DTS in output stream 0:0; previous: 1234, current: 1180; changing to 1235 is still a warning on 9.0.2, and -fflags +genpts still only silences it rather than fixing the timestamps that produced it. Timestamp-shaped filtergraph breakage is demonstrably alive on the 9.0 line: a fix in the effing project documents that on 9.0.1 concat emits a 1/1000000 timebase while overlay forwards the secondary input's EOF timestamp unrescaled, which makes a trailing fps= filter process excess frames.
That's the opposite of the circulating claim. Dynamic frame-rate negotiation across a graph is exactly where this bug lives, and it was reported against 9.0.1 after the release.
Impossible to convert between the formats
Pixel-format negotiation is the one area where 9.0 genuinely moved. Impossible to convert between the formats supported by the filter 'Parsed_null_0' and the filter 'auto_scaler_0' happens because FFmpeg inserts an auto_scaler_N when two adjacent filters can't agree on a pixel format, and the error is what you get when no common format exists. The canonical explanation is a 2018 mailing-list reply, which tells you how long this has been the best available answer.
Because the swscale rewrite decomposed conversions into elementary operations, the auto-inserted scaler can bridge more pairs than it used to, and interlaced content is now modelled during negotiation instead of being quietly mishandled. Software-path graphs mixing 8-bit and 10-bit sources are the ones most likely to stop erroring. Hardware paths are not fixed: mixed 8-bit and 10-bit inputs still break pixel-format negotiation in VAAPI pipelines, defaulting to monow or returning -22, and still need manual pinning, as tracked in intel/media-driver issue 2017.
Keep the explicit format= pin anywhere a hardware filter is in the chain:
-filter_complex "[0:v]format=nv12,hwupload,scale_vaapi=1920:1080[v]"
The reasoning behind pinning the format instead of fighting the filter is in FFmpeg "Impossible to Convert": Fix the Format, Not the Filter.
Pitfalls when you move a filter graph to 9.0
Three changes in 9.0 break graphs for reasons unrelated to negotiation, and they'll cost you more upgrade time than the four errors above.
-vsync is gone, not deprecated, and -fps_mode is not a drop-in swap for it, because the two differ in how they handle per-stream application and duplication. If your graph's output duration changes after the upgrade, this is usually why. The specifics are in FFmpeg vsync Is Deprecated. -fps_mode Isn't a Drop-In Swap.
Every libnpp filter was deleted: scale_npp, scale2ref_npp, sharpen_npp, and transpose_npp all fail to parse on 9.0. The replacements are transpose_cuda and a generic filtering path added to scale_cuda. A graph built around scale_npp needs rewriting, not a flag change.
And the major bump of all seven libraries, including libavfilter 12 and libswscale 10, is a full ABI break. Anything linking FFmpeg libraries directly has to be rebuilt, not just upgraded alongside. The DNN filter path changed too, which is worth reading about separately if you're running models inside a graph: Run an ONNX Upscaler in FFmpeg 9's DNN Filter Graph.
FAQ
Did FFmpeg 9.0 rewrite the filter execution engine?
No primary source supports that. The FFmpeg 9.0 release write-up by Jean-Baptiste Kempf describes a libswscale rewrite with over 300 commits and improved modelling of interlaced content during format negotiation, and the "filter execution engine has been rewritten" wording appears only in secondary coverage that cites nothing and names filters FFmpeg doesn't ship.
Do transpose_nv12 and eq_vulkan exist in FFmpeg 9?
Neither filter exists. NV12 support for transposition was a patch against transpose_npp, and FFmpeg 9.0 removed all libnpp filters, while eq_vulkan is not in FFmpeg's documented Vulkan filter list. Use transpose_cuda for CUDA transposition on 9.0.
Should I upgrade from FFmpeg 8.1.3 to 9.0.2 to fix my filtergraph errors?
Upgrading from 8.1.3 to 9.0.2 won't clear buffer queue overflow, resolution-mismatched concat, or non-monotonous DTS, so don't upgrade for those. Upgrade for the security fixes in 9.0.2, including the H.264 out-of-bounds read fix and the MOV demuxer metadata-key hardening, and expect to rewrite -vsync and any libnpp filters while you're there.
Does removing -vsync change my output duration?
Dropping -vsync can change output duration, because the frame-rate behavior it controlled doesn't disappear when the flag does. Replace it deliberately with -fps_mode per stream, or with an fps= filter inside the graph, then check the output duration with ffprobe instead of assuming it matched.
Can I still use scale_npp in an FFmpeg 9 filter graph?
scale_npp fails to parse on FFmpeg 9.0 because every libnpp filter was deleted in that release. Rewrite the chain around scale_cuda, which gained a generic filtering path in 9.0, and use transpose_cuda where you had transpose_npp.
Running two FFmpeg branches so you can tell which of your workarounds are still load-bearing is a real maintenance cost, and this post is the evidence that the answer is usually "keep it." If you'd rather hand the normalization and the version churn to someone else, browse the endpoints and copy a ready-to-run request from the API playground; the free tier gives you 200 tokens, about 33 minutes of video, with no card.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Kling 4.0 washed out on TikTok? Fix the tag, then the 9:16
Kling 4.0 TikTok washed out? Most 21:9 and 4K clips need a Rec.709 re-tag, not a tone map. ffprobe first, then the crop-vs-pad math for 9:16, 1:1 and 4:5.

Your 4K AI video isn't TikTok-ready. Convert it to 1080x1920
Convert 4K video for TikTok: downscale 10-bit AI output to 1080x1920 8-bit SDR H.264, cap the bitrate, normalize audio. FFmpeg command plus one API call.

FFmpeg vsync Is Deprecated. -fps_mode Isn't a Drop-In Swap
FFmpeg vsync deprecated? Map -vsync 0/1/2/drop to -fps_mode, repeat it per output, pin CFR at the right pipeline stage. Plus pre-5.1 static-build fallbacks.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free