FFmpeg Can Remove Background Noise. arnndn Breaks in Docker.

Your talking-head clip has an air conditioner running under it, or a laptop fan, or the flat hiss of a $30 lav mic. FFmpeg can strip most of that with a single audio filter and no re-encode of the video track. The catch is that the filter most tutorials recommend is also the one most likely to fail the first time you run it inside a container.
Quick answer: FFmpeg removes background noise with three native audio filters:afftdn(FFT denoiser, tuned withnrandnf),anlmdn(non-local means, good for steady hiss), andarnndn(a recurrent neural network trained on speech). The command that fixes most clips isffmpeg -i in.mp4 -af "afftdn=nr=20:nf=-40" -c:v copy -c:a aac out.mp4, whilearnndnadditionally requires an external.rnnnmodel file that ships with no FFmpeg build and no public FFmpeg Docker image. If you'd rather not host an encoder, mount model files, or babysit a two-step denoise and loudness pass, send the same filter chain to FFmpeg Micro as one API call.
Three filters, three different noise problems
FFmpeg's three denoisers attack noise in completely different ways, so picking the wrong one feels like the technique doesn't work. afftdn works in the frequency domain and subtracts an estimated noise spectrum. anlmdn compares short patches of the waveform against nearby patches and averages what repeats. arnndn runs a small neural network that was trained to tell speech from everything else.
| Filter | Method | Best on | Needs |
|---|---|---|---|
| `afftdn` | FFT spectral subtraction | HVAC hum, tape hiss, mic self-noise | FFmpeg 4.1+ |
| `anlmdn` | Non-local means averaging | Steady broadband hiss, low-level grain | FFmpeg 4.2+ |
| `arnndn` | RNN speech model | Voice recordings with messy, varying noise | FFmpeg 4.3+ and a `.rnnn` file |
Check the build before you write the pipeline, because a base image pinned to FFmpeg 3.x has none of these and will tell you No such filter: 'afftdn' rather than anything about versions. Run ffmpeg -filters | grep -E 'afftdn|anlmdn|arnndn' first. The same class of surprise shows up with codecs, which is what an "Unknown encoder 'libx264'" error is really telling you.
Tune afftdn by measuring the noise floor, not by guessing
afftdn has two options that matter and a dozen that mostly don't. nr (noise reduction) defaults to 12 dB with a range of 0.01 to 97. nf (noise floor) defaults to -50 dB with a range of -80 to -20. Setting nf near the actual level of the silence in your file is what separates a clean result from a watery one.
Measure it instead of guessing. Trim a stretch of room tone before anyone speaks and read the level:
ffmpeg -ss 0 -t 0.8 -i interview.mp4 -vn -af volumedetect -f null - 2>&1 | grep _volume
# [Parsed_volumedetect_0 @ 0x7f8] mean_volume: -48.6 dB
# [Parsed_volumedetect_0 @ 0x7f8] max_volume: -41.2 dB
Feed that max_volume back in as nf, keep the video stream untouched, and re-encode only audio:
ffmpeg -i interview.mp4 -af "afftdn=nr=20:nf=-41" -c:v copy -c:a aac -b:a 192k clean.mp4
If the noise changes over the clip, a fridge kicking on halfway through, turn on floor tracking with afftdn=nr=10:nf=-80:tn=1. FFmpeg's own documentation also ships a profiling recipe that captures the noise print from the first fraction of a second, which is the closest thing FFmpeg has to the "select a noise sample" step in a desktop audio editor:
ffmpeg -i interview.mp4 \
-af "asendcmd=0.0 afftdn sn start,asendcmd=0.4 afftdn sn stop,afftdn=nr=20:nf=-40" \
-c:v copy clean.mp4
anlmdn is the one to try when afftdn leaves artifacts on a hiss-only source. Its defaults are conservative: strength s is 0.00001, patch p is 2 ms, research window r is 6 ms, smooth m is 11. Real cleanup usually starts around anlmdn=s=0.001:p=0.006.
arnndn needs a model file that ships with nothing
arnndn is the best-sounding option for voice, and it's the one that breaks in production, because the filter has no built-in weights. The m option takes a path to an external .rnnn file, and that file is not in the FFmpeg source tree, not in apt install ffmpeg, and not in any of the common FFmpeg Docker images. Locally it works because you cloned a model repo into your project folder months ago. In a container, you get this:
[Parsed_arnndn_0 @ 0x55d4a0] Failed to open model file: bd.rnnn
[AVFilterGraph @ 0x55d360] Error initializing filter 'arnndn' with args 'm=bd.rnnn'
Error reinitializing filters!
Failed to inject frame into filter network: Invalid argument
The models live in two community repos: GregorR/rnnoise-models, which publishes them in dated folders, and richardpl/arnndn-models, which keeps them flat as bd.rnnn, cb.rnnn, lq.rnnn, mp.rnnn, sh.rnnn, and std.rnnn. Each file is around 300 KB of plain text, so baking one into an image costs nothing. The GregorR repo also documents what each model was trained for, a detail almost no tutorial passes along:
| Model | Expected signal | Expected noise |
|---|---|---|
| `bd.rnnn` (beguiling-drafter) | Voice, including laughter | Recording environment |
| `sh.rnnn` (somnolent-hogwash) | Speech | Recording environment |
| `cb.rnnn` (conjoined-burgers) | General audio | Recording environment |
| `lq.rnnn` (leavened-quisling) | Voice | General |
| `mp.rnnn` (marathon-prescription) | General audio | General |
For a podcast, webinar, or UGC talking head, bd.rnnn is the default worth starting with. Bake it into the image so the path is stable:
FROM jrottenberg/ffmpeg:7.1-ubuntu
ADD https://raw.githubusercontent.com/richardpl/arnndn-models/master/bd.rnnn /models/bd.rnnn
RUN chmod 644 /models/bd.rnnn
Two more arnndn facts worth knowing before you ship it. The filter accepts exactly one sample rate, 48000 Hz, so make the resample explicit rather than letting FFmpeg insert it silently. And mix (default 1.0, range -1 to 1) blends the denoised signal back with the original, which is how you avoid the vacuum-sealed sound that full-strength RNN denoise gives a quiet room:
ffmpeg -i interview.mp4 \
-af "aresample=48000,arnndn=m=/models/bd.rnnn:mix=0.85" \
-c:v copy -c:a aac -b:a 192k clean.mp4
Containers are where this whole category of problem lives. If your runner is a slim or distroless image, the model file is one of several things you'll end up mounting, alongside the binary itself and fonts. The n8n distroless image fixes cover the same shape of problem.
Denoise before loudnorm, never after
Filter order changes the result, and the rule is denoise first, normalize second. loudnorm applies gain to hit a target integrated loudness, so it lifts your noise floor by exactly as much as it lifts the speech. Denoise after that and the noise you measured at -41 dB is now sitting at -29 dB, your nf value is wrong, and the filter either misses the noise or chews holes in the voice trying to reach it.
The full pass for a clip headed to social looks like this:
ffmpeg -i interview.mp4 \
-af "afftdn=nr=20:nf=-41,loudnorm=I=-16:TP=-1.5:LRA=11" \
-c:v copy -c:a aac -b:a 192k final.mp4
The same job as one API call, with no encoder to host and no model files to mount, is a POST to FFmpeg Micro. Note that -filter_complex isn't supported, but a single -af chain is one option and argument pair, which covers denoise plus loudness in the same job:
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"inputs": [{ "url": "https://storage.example.com/interview.mp4" }],
"outputFormat": "mp4",
"options": [
{ "option": "-af", "argument": "afftdn=nr=20:nf=-41,loudnorm=I=-16:TP=-1.5:LRA=11" },
{ "option": "-c:v", "argument": "copy" },
{ "option": "-c:a", "argument": "aac" }
]
}'
You get back a job id and a pending status, poll GET /v1/transcodes/:id, then pull the result from GET /v1/transcodes/:id/download. Signup gives you 200 tokens, roughly 33 minutes of video, with no credit card, and paid plans start at $19.99 a month for 2,500 tokens. The same pattern works from n8n, Make, or Zapier with an HTTP node, which is usually the point: the cleanup step stops being a Docker problem and becomes one request in a workflow you already have. If that workflow also mixes a music bed under the voice, do the ducking pass after denoise for the same reason loudnorm goes last.
Pitfalls that show up on real footage
Most denoise complaints trace back to five specific mistakes, not to the filters being weak.
- Re-encoding video for no reason. Audio-only work should always carry
-c:v copy, or a 4K source spends ten minutes in libx264 to fix a hiss. - Cranking
nrpast 30. High noise reduction produces the metallic, underwater artifact people blame on FFmpeg. Back off to 10 to 20 and accept some residual room tone. - Testing blind.
afftdn=om=nandanlmdn=o=noutput the noise the filter is removing instead of the cleaned signal. If you hear words in that render, you're cutting into the voice. - Resampling and forgetting to come back.
arnndnforces 48 kHz. If your delivery target is 44.1 kHz, addaresample=44100after the filter. - Denoising before you've checked there's an audio stream at all. A clip with no audio track fails at the map step, and that error reads like a corrupt file but isn't.
When denoising is the wrong call
Spectral denoise fixes steady, predictable noise. Wind buffeting, mic handling thumps, a door slam, or two people talking over each other are transient events that overlap the voice in both time and frequency, and no afftdn setting separates them. Clipped audio is a different failure entirely: the peaks are gone from the file, so denoise has nothing to recover.
For those, re-record if you can, or move the job to a dedicated spectral repair editor where a human paints over individual events. That's a manual workstation task, not a pipeline step, and pretending otherwise just burns compute on every clip in the batch.
FAQ
Does removing background noise with FFmpeg re-encode the video?
Removing background noise touches only the audio stream, so the video can be stream-copied with -c:v copy. FFmpeg decodes and re-encodes audio, then muxes it back against the original video packets, which is why a denoise pass on a 10-minute 4K file finishes in seconds rather than minutes.
What's the difference between afftdn and arnndn in FFmpeg?
afftdn estimates a noise spectrum and subtracts it, which works on any audio and needs no extra files. arnndn runs a neural network trained specifically to keep human speech, which sounds better on voice but requires an external .rnnn model file and only accepts 48 kHz input.
Where do I get the .rnnn model file for arnndn?
The .rnnn models come from two community GitHub repos, GregorR/rnnoise-models and richardpl/arnndn-models. Neither FFmpeg's source tree nor any distro package includes them, so download the file you want, roughly 300 KB each, and reference it by absolute path in arnndn=m=/path/to/bd.rnnn.
How much noise reduction is too much?
Noise reduction above about 30 dB in afftdn starts producing musical artifacts, the warbling metallic tone that makes voices sound processed. A nr value between 10 and 20, paired with an nf measured from actual silence in the file, removes hum without leaving fingerprints on the speech.
Can I remove background noise in n8n or Make without installing FFmpeg?
You can run the exact same filter chain through an HTTP request node by posting the job to a hosted FFmpeg API, which keeps n8n, Make, and Zapier workflows free of FFmpeg binaries, .rnnn model files, and custom Docker images. Cleaner audio also improves downstream steps like FFmpeg 8.0's Whisper filter or any transcription API, since noise inflates word error rates.
If you want the denoise and loudness pass to be one request instead of a container you maintain, sign up free and run the chain above against a real clip with the starter tokens.
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

FFmpeg cropdetect isn't one-shot. Detect black bars per file
ffmpeg cropdetect finds black bars on one file fine, then breaks in a batch. Sample past the intro, take the last crop line, round to even, crop per file.

The Whisper 25MB Limit Isn't a Time Limit. Re-encode First.
The Whisper 25MB limit is a byte cap, not a time cap. Re-encode first, split only when you must, and re-offset every timestamp so your SRT still lines up.

Your AI Dubbing Workflow Isn't Broken. The Dub Just Runs Long.
An AI dubbing workflow returns a translated track longer than the video. Measure both with ffprobe, fix the drift with atempo or apad, then re-mux cleanly.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free