Skip to main content

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:

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

ParameterType
optionsShelfOptions

Returns

AudioFilter

Example

AudioFilters.bass({ frequency: 110, gain: 6 })

compressor()

readonly compressor(options?: CompressorOptions): AudioFilter;

Dynamic-range compressor (ffmpeg acompressor).

Parameters

ParameterType
optionsCompressorOptions

Returns

AudioFilter

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

ParameterType
optionsCrossfeedOptions

Returns

AudioFilter

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

ParameterType
namestring
optionsReadonly<Record<string, string | number | boolean>>

Returns

AudioFilter

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

ParameterType
optionsDynamicNormalizerOptions

Returns

AudioFilter

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

ParameterType
optionsEqualizerOptions

Returns

AudioFilter

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

ParameterType
optionsGraphicEqualizerOptions

Returns

AudioFilter

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

ParameterType
optionsPassOptions

Returns

AudioFilter

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

ParameterType
optionsLimiterOptions

Returns

AudioFilter

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 is attack × sampleRate samples — 240 samples, 5.0 ms at 48 kHz on the default 5 ms attack — and ffmpeg's latency option, 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 inserts aresample around 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

ParameterType
optionsLoudnormOptions

Returns

AudioFilter

lowpass()

readonly lowpass(options: PassOptions): AudioFilter;

Two-pole low-pass (ffmpeg lowpass).

Parameters

ParameterType
optionsPassOptions

Returns

AudioFilter

treble()

readonly treble(options: ShelfOptions): AudioFilter;

High-shelf boost/cut (ffmpeg treble).

Parameters

ParameterType
optionsShelfOptions

Returns

AudioFilter

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

ParameterType
optionsVolumeOptions

Returns

AudioFilter