Variable: AudioFilters
const AudioFilters: {
bass: AudioFilter;
compressor: AudioFilter;
crossfeed: AudioFilter;
custom: AudioFilter;
dynamicNormalizer: AudioFilter;
equalizer: AudioFilter;
graphicEqualizer: AudioFilter;
highpass: AudioFilter;
limiter: AudioFilter;
loudnorm: AudioFilter;
lowpass: AudioFilter;
treble: AudioFilter;
volume: AudioFilter;
};
Defined in: packages/player/src/filters.ts:748
Factories for the audio filters this library ships bindings for.
Where the ranges come from
Every factory validates its arguments and throws invalid-state rather than
letting mpv reject the whole chain with a generic message. Values the caller
omits are simply not written, so ffmpeg's own defaults apply.
Unless a doc comment says otherwise, a bound here is read off the
AVOption table of the filter in FFmpeg n8.1.2 — the tree our engine
binaries are actually built from (packages/player/android/libmpv.gradle
ffmpegVersion, packages/player/ios/libmpv.pin LIBMPV_FFMPEG_VERSION).
Every citation below is libavfilter/<file>:<line> at the n8.1.2 tag. The
AVOption table is the truth; doc/filters.texi is prose about it and is
cited only where it says something the table cannot.
Re-audited in full against n8.1.2 (2026-08-14). Nothing in the wrapped
surface moved between n6.0 — which these bounds were originally taken from,
and which the engine has not shipped since the rnmedia.5/rnmedia.4
engine move — and n8.1.2: the only changes to these seven option tables are
a cosmetic .unit = designated-initializer refactor and loudnorm gaining
a stats_file option this API does not expose. What was stale was the
label on the ranges, not the ranges.
Where a range is OURS
Three bounds in this file are deliberately not ffmpeg's, and each says so at its own definition:
- AudioFilters.volume — ffmpeg's
volumeoption is an expression string with no numeric bounds at all; ±100 dB is our sanity limit. - AudioFilters.graphicEqualizer — takes dB and converts, so
ffmpeg's linear
[0, 20]becomes a+26.02 dBceiling. - AudioFilters.dynamicNormalizer
gaussSize— ffmpeg warns and silently rounds an even window up to odd; we reject it instead.
Anything not listed here — including firequalizer and anequalizer, which
are compiled in but configured through ffmpeg expression strings rather than
scalars (af_anequalizer.c:84's params is one AV_OPT_TYPE_STRING) — is
reachable through AudioFilters.custom.
Type Declaration
bass()
readonly bass(options: ShelfOptions): AudioFilter;
Low-shelf boost/cut (ffmpeg bass).
Parameters
| Parameter | Type |
|---|---|
options | ShelfOptions |
Returns
Example
AudioFilters.bass({ frequency: 110, gain: 6 })
compressor()
readonly compressor(options?: CompressorOptions): AudioFilter;
Dynamic-range compressor (ffmpeg acompressor).
Parameters
| Parameter | Type |
|---|---|
options | CompressorOptions |
Returns
crossfeed()
readonly crossfeed(options?: CrossfeedOptions): AudioFilter;
Headphone crossfeed (ffmpeg crossfeed) — bleeds a filtered copy of each
channel into the other so hard-panned mixes stop feeling like two separate
sounds inside the head.
Parameters
| Parameter | Type |
|---|---|
options | CrossfeedOptions |
Returns
custom()
readonly custom(name: string, options?: Readonly<Record<string, string | number | boolean>>): AudioFilter;
Any other filter mpv or libavfilter knows, by name.
The escape hatch that keeps this module thin: firequalizer,
anequalizer, aformat, anull and mpv's own builtins are all reachable
without waiting for a typed wrapper. Options are written in object key
order; values are stringified as-is (numbers via String).
Parameters
| Parameter | Type |
|---|---|
name | string |
options | Readonly<Record<string, string | number | boolean>> |
Returns
Example
// Arbitrary-curve linear-phase EQ, ffmpeg expression syntax
AudioFilters.custom('firequalizer', {
gain_entry: 'entry(100,-3);entry(1000,0);entry(10000,4)',
})
dynamicNormalizer()
readonly dynamicNormalizer(options?: DynamicNormalizerOptions): AudioFilter;
Sliding-window loudness normaliser (ffmpeg dynaudnorm).
Prefer this over AudioFilters.loudnorm for live playback: it works
at the stream's own sample rate, whereas loudnorm forces the whole chain
through 192 kHz.
frameLengthMs and gaussSize map to AV_OPT_TYPE_INT options
(af_dynaudnorm.c:130-133) and are required to be integers here, because
av_opt_set would otherwise round a fractional value silently. gaussSize
must additionally be odd: that rejection is ours — ffmpeg logs
"filter size %d is invalid. Changing to an odd value." and ORs the low
bit in (af_dynaudnorm.c:165-167), i.e. it runs a different filter from
the one that was asked for, with the warning buried in mpv's log.
Parameters
| Parameter | Type |
|---|---|
options | DynamicNormalizerOptions |
Returns
equalizer()
readonly equalizer(options: EqualizerOptions): AudioFilter;
A single parametric peaking band (ffmpeg equalizer).
The building block of a parametric EQ: stack several to shape a curve.
Parameters
| Parameter | Type |
|---|---|
options | EqualizerOptions |
Returns
Example
AudioFilters.equalizer({ frequency: 5000, width: 2, gain: -12 })
graphicEqualizer()
readonly graphicEqualizer(options: GraphicEqualizerOptions): AudioFilter;
18-band graphic equaliser (ffmpeg superequalizer), gains in dB.
Band centres are GRAPHIC_EQUALIZER_BANDS. This is an FFT filter: it costs more CPU than a stack of AudioFilters.equalizer bands but gives a flat, phase-consistent response and maps 1:1 onto a slider UI.
Parameters
| Parameter | Type |
|---|---|
options | GraphicEqualizerOptions |
Returns
Example
// +12 dB on the bottom two bands, flat elsewhere
AudioFilters.graphicEqualizer({
gainsDb: [12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
})
highpass()
readonly highpass(options: PassOptions): AudioFilter;
Two-pole high-pass (ffmpeg highpass).
A 30–40 Hz high-pass is the cheapest way to stop a bass boost from wasting excursion on inaudible rumble.
Parameters
| Parameter | Type |
|---|---|
options | PassOptions |
Returns
limiter()
readonly limiter(options?: LimiterOptions): AudioFilter;
Look-ahead brickwall limiter (ffmpeg alimiter).
The honest tail of any chain that boosts: EQ gain plus ReplayGain can push peaks past full scale, and this is what stops that becoming clipping.
Parameters
| Parameter | Type |
|---|---|
options | LimiterOptions |
Returns
Remarks
What it costs when it is not working, which is most of the time — worth knowing, because equalizerPresetChain appends one by default:
- Below full scale it is sample-identical. The gain reduction is a
single running scalar that starts at 1 and is only ever moved by a
sample whose magnitude exceeds
limit; nothing else in the path scales (af_alimiter.c:102,180,236,239,269-275,289). No colouration, no compression of programme material, no level change. - It delays the signal by
attack, and does not compensate. The look-ahead buffer isattack × sampleRatesamples — 240 samples, 5.0 ms at 48 kHz on the default 5 ms attack — and ffmpeg'slatencyoption, which trims the primed silence and flushes the tail, defaults to off and is not exposed here (af_alimiter.c:376-379). So a chain that has just been built emits 5 ms of silence first, and the last 5 ms of the stream is never flushed. Inaudible for playback; it is still the one thing that is not transparent. - It runs in
double(FILTER_SINGLE_SAMPLEFMT(AV_SAMPLE_FMT_DBL)), so libavfilter insertsaresamplearound it. Lossless, not free.
loudnorm()
readonly loudnorm(options?: LoudnormOptions): AudioFilter;
EBU R128 loudness normalisation (ffmpeg loudnorm).
Prefer Player.setLoudnessNormalization — the managed toggle over
this same filter, which coexists with a chain set through
setAudioFilters instead of being clobbered by it, and whose TSDoc
carries the full cost sheet. This factory remains for hand-built chains
that need options the toggle does not expose (linear, offset).
Expensive on mobile, by construction. In its single-pass (dynamic)
mode loudnorm advertises exactly one input sample rate — 192 000 Hz
(FFmpeg 8.1.2 af_loudnorm.c:740,752; doc/filters.texi: "the audio
stream will be upsampled to 192 kHz") — so libavfilter resamples the
stream up to 192 kHz for this filter and back down afterwards, and the
filter buffers 3 s of lookahead (af_loudnorm.c:697,775). Note linear
is inert in live playback: ffmpeg's linear mode requires all four
measured_* values from a prior analysis pass (af_loudnorm.c:820-825),
which a live player cannot have. If all you want is "make quiet tracks
louder", AudioFilters.dynamicNormalizer or Player.setReplayGain
costs a fraction of it.
Parameters
| Parameter | Type |
|---|---|
options | LoudnormOptions |
Returns
lowpass()
readonly lowpass(options: PassOptions): AudioFilter;
Two-pole low-pass (ffmpeg lowpass).
Parameters
| Parameter | Type |
|---|---|
options | PassOptions |
Returns
treble()
readonly treble(options: ShelfOptions): AudioFilter;
High-shelf boost/cut (ffmpeg treble).
Parameters
| Parameter | Type |
|---|---|
options | ShelfOptions |
Returns
volume()
readonly volume(options: VolumeOptions): AudioFilter;
Fixed gain (ffmpeg volume), in dB.
This is chain-domain gain, distinct from Player.setVolume (mpv's output
volume) and from ReplayGain. Its job here is headroom: put
AudioFilters.volume({ gainDb: -6 }) at the head of a chain whose EQ
boosts 6 dB and nothing clips.
The ±100 dB bound is this library's, not libavfilter's — see VolumeOptions.gainDb.
Parameters
| Parameter | Type |
|---|---|
options | VolumeOptions |