ffmpegvideo-encodingaudio

FFmpeg 9.1: what to re-test before you upgrade

··10 min read
FFmpeg 9.1: what to re-test before you upgrade

Your pipeline has been shipping the same AAC audio for months and nobody has complained. FFmpeg 9.1 changes what comes out of -c:a aac without printing a warning, failing a job, or changing a flag in your command line. That's harder to catch than a removed option, because a removed option errors out and a different encoder just quietly sounds different.

Quick answer: FFmpeg 9.1 entered its release process on October 23, 2026 carrying the rewritten native AAC encoder that was too late for 9.0, so plain -c:a aac now produces different bytes, a tighter bitrate curve and a different loudness profile than your 9.0.x binary did, with no error to warn you. Before upgrading, encode a fixed reference set on both binaries, compare bitrate and EBU R128 loudness, and pin the exact version in your Dockerfile and CI. If you'd rather not re-run that audit every release, send the encode to FFmpeg Micro as one API call and let a managed FFmpeg build absorb the version churn.

The AAC story everyone is covering is the wrong one for a pipeline

Coverage of the new encoder is all about quality: whether FFmpeg's native AAC finally beats fdkaac and qAAC. IgorC's 12-sample test on Doom9 ran the new coder against twoloop, FDK-AAC CBR, and Apple AAC in CBR and ABR at roughly 128 kbps. The Hydrogenaudio thread goes deeper: rate control, RDO and every coding tool (PNS, TNS, I/S, M/S) was reengineered, with results measured against Zimtohrli and ViSQOL.

All of that is true and none of it is your problem. Your problem is reproducibility. The new NMR coder is the default in upstream FFmpeg, so the encoder behind -c:a aac swaps under a command you haven't touched. Better-sounding output is still different output: different bytes, a different measured loudness, and a rate curve that holds much closer to the target than twoloop's did. If you archive renders, diff masters, or ship audio against a loudness spec, a point upgrade just moved the ground.

FFmpeg said plainly that the encoder rewrite missed 9.0 and would land in 9.1. That's the one sentence most upgrade checklists skipped.

What actually changes at the flag level

Three things change between 9.0.x and 9.1, and only one is about sound quality. The table is the short version; the commands below confirm it on your own files.

What you runOn 9.0.xOn 9.1
`-c:a aac -b:a 128k`twoloop coder, loose rate variation around the targetNMR coder, strict CBR holding very close to the target
`-c:a aac -q:a 2`VBR via the old quality pathQuality setting takes precedence over `b` and `aac_rc`, and the encoder's author does not recommend real VBR here
`-c:a aac -b:a 128k -aac_rc abr`option not in playABR mode; below roughly 56 kbps stereo it lands *above* the requested rate, so use `cbr` down there

The aac_rc option controls rate-control mode when a bitrate is set and defaults to cbr, per FFmpeg's own codec documentation. That doc is also where the sub-56 kbps ABR caveat is written down, which matters if you encode voice-only tracks at 48k or 64k for faceless-channel or podcast pipelines.

If your audio path uses -q:a for VBR, move to an explicit bitrate: the rewrite is CBR-first by design, and the VBR path through it is not where the work went.

Re-test it in three measurements, not by listening

Pick five to ten files that represent what your pipeline ingests: a screen recording with speech, a music bed, a phone-shot UGC clip, one 48 kHz stereo master, one mono voiceover. Keep them in the repo. Run the same command on both binaries and compare numbers, because ears are a bad regression test and a file-size diff tells you almost nothing.

First, capture what each build is:

ffmpeg -version | head -1
ffmpeg -buildconf > build-9.0.2.txt
ffmpeg -hide_banner -encoders | grep -i aac

Second, measure the actual bitrate and the decoded audio, not the container:

# real bitrate of the audio stream
ffprobe -v error -select_streams a:0 \
  -show_entries stream=codec_name,bit_rate,sample_rate,channels \
  -of default=nw=1 out.m4a

# hash of the DECODED audio, so container metadata doesn't pollute the diff
ffmpeg -v error -i out.m4a -f md5 -

Third, measure loudness the way the platforms do:

ffmpeg -nostats -i out.m4a -af ebur128=peak=true -f null - 2>&1 | tail -12

Integrated LUFS and true peak are the two numbers to log next to each file. If your spec says -14 LUFS for YouTube or -16 for a podcast feed, a half-LU shift across an encoder swap is the difference between passing and getting normalized at ingest. Check the same files at 64k and 96k, not just at 128k, because the rate control behaves differently down there, where cheap voice tracks live.

AAC output also carries encoder priming samples, which have bitten people in a completely different way: why an FFmpeg concat audio gap isn't your cut, it's AAC priming. If you're already fighting that, changing encoders in the same week is a bad idea.

9.1 is a feature branch, not a security fix

Anyone upgrading this week because of October's CVE batch is solving the wrong problem. The October 8 disclosures, plus CVE-2026-107678 published October 13, affect FFmpeg through 9.0.2, the newest stable release, with no fixed release reported. VulnCheck's advisory on the stack exhaustion via recursive free of pssh boxes in av_encryption_init_info_free() is the clearest example: an MP4 with tens of thousands of tiny pssh boxes kills the process, so an upload can crash any pipeline that runs ffmpeg or ffprobe over user-supplied MP4s. As of mid-October, release/9.0 carried no upstream fixes for CVE-2026-107675, 107676, 107677 or 107696 either, and distro trackers still list ffmpeg as vulnerable.

Moving to 9.1 gets you a new AAC encoder and a new set of Vulkan toys. It doesn't close the RTSP redirect SSRF, the SFTP host-key bypass, or the pssh crash. You end up tracking a feature branch and an unpatched hole at the same time. Our containment notes are in FFmpeg CVE October 2026: no patch exists. Contain them instead., and they apply unchanged on 9.1.

The rest of the 9.1 surface that can trip a build

Beyond audio, Phoronix's October 23 inventory lists an APV Vulkan encoder, vf_fruc_vulkan for NVIDIA Optical Flow frame-rate up-conversion, ASTC encode and decode via libastcenc with .astc and KTX 1.0 muxers, an iTerm2 inline image muxer, an MVR demuxer, a latticepal filter, DVD-Audio LPCM, and H.264 data partitioning.

The trap is that the headline items are build- and driver-dependent. A Vulkan encoder in the release notes is not a Vulkan encoder in your Alpine container, and vf_fruc_vulkan needs an NVIDIA driver your CI runner probably doesn't have. Verify presence instead of assuming it:

ffmpeg -hide_banner -hwaccels
ffmpeg -hide_banner -filters | grep -i vulkan
ffmpeg -hide_banner -muxers | grep -iE 'astc|ktx'

Run that inside the image you ship, not on your laptop. The good news: 9.1 is additive on the filter side. The flag removals that hurt were in 9.0, and our earlier read still holds, that FFmpeg 9 didn't rewrite the filter graph, so keep your workarounds.

Pin the version, or stop owning the binary

Skip pinning and you'll repeat this whole audit in six months. Pin a digest, not a tag:

FROM linuxserver/ffmpeg@sha256:<digest>

Then assert it in CI so a base-image rebuild can't move you silently:

ffmpeg -version | head -1 | grep -q 'ffmpeg version 9.0.2' || exit 1

And keep one golden-file check that encodes a committed WAV and compares the decoded MD5 against a stored value. That single test turns "the audio sounds slightly different now" into a red build.

This is the second FFmpeg migration in three months for self-hosters, so there's a third option: stop owning the upgrade cycle. The same encode runs as one call to the FFmpeg API, with no binary to pin and no CVE branch to track:

# the command you run today
ffmpeg -i input.mp4 -c:v copy -c:a aac -b:a 192k output.mp4
# the same job as one API call
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/input.mp4" }],
    "outputFormat": "mp4",
    "options": [
      { "option": "-c:v", "argument": "copy" },
      { "option": "-c:a", "argument": "aac" },
      { "option": "-b:a", "argument": "192k" }
    ]
  }'

You submit, poll GET /v1/transcodes/{id} until status is completed, then download. No Vulkan driver matrix, no digest pinning, no deciding on a Friday whether a feature branch is worth the unpatched CVEs.

When this is the wrong call: if you need vf_fruc_vulkan on your own GPUs, if you're doing frame-exact forensic work where you must control the exact libavcodec build, or if your volume makes a self-hosted encode farm genuinely cheaper, keep the binary and keep the pin. Owning the stack is a real answer, just a job that most teams running a few video steps inside n8n or Make never signed up for.

Common pitfalls when moving to 9.1

The 9.1 pitfalls below are the ones that actually cost people a weekend, not the generic ones.

  • Testing only at 128 kbps. The rate control differs most at the low end, and ABR below roughly 56 kbps stereo overshoots the requested rate.
  • Comparing file sizes instead of decoded audio. Two files can be within a kilobyte and still differ in loudness.
  • Leaving -q:a in the command. Switch to an explicit -b:a plus the default cbr rate control.
  • Treating -aac_coder twoloop as a permanent escape hatch. The upstream commit that made the new coder the default says the old coders will be removed soon, so it buys one release cycle, not forever.
  • Re-encoding your archive so old and new outputs match. You'll lose a generation of quality to fix a cosmetic inconsistency. Record a version boundary in your metadata instead.
  • Assuming the Vulkan and ASTC features exist in your container because they're in the release notes.

FAQ

Is FFmpeg 9.1 actually released yet?

FFmpeg 9.1 entered its release process on October 23, 2026 according to Phoronix, so it's a branch moving toward a release rather than a finished stable tarball. If your package manager hasn't offered it yet, you'd be building from the branch, which is a different risk profile than installing a point release.

Does -c:a aac change by default in FFmpeg 9.1, or do I have to opt in?

Plain -c:a aac changes by default in FFmpeg 9.1, because the NMR coder is now the default in upstream FFmpeg and the series merged to master on October 4, 2026. You don't opt in, you opt out.

Will my old and new AAC files still be bit-identical?

Old and new AAC files will not be bit-identical across the 9.0.x to 9.1 boundary, because a different encoder produced them. Any pipeline that dedupes by hash, diffs masters, or asserts a golden-file checksum needs a new baseline captured on the new binary.

What's the fastest way to roll back if 9.1 audio fails review?

Rolling back means reverting to the pinned digest of your 9.0.2 image and redeploying, which is only fast if you pinned a digest instead of a floating tag before you upgraded. Pin first, upgrade second.

If you'd rather not re-measure loudness across every FFmpeg release for the rest of the pipeline's life, the FFmpeg Micro quickstart runs presigned upload, transcode, poll and download from the dashboard, with a temporary API key created on the spot. The free tier needs no card and gives you 200 tokens at sign-up, about 33 minutes of input video at one token per 10 seconds, and failed jobs are never billed.

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.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free