Skip to content

Fix clipping with Opus files without ReplayGain tags when there's less than 5 dB headroom#2526

Open
horazont wants to merge 2 commits into
MusicPlayerDaemon:masterfrom
horazont:fix/opus-clipping
Open

Fix clipping with Opus files without ReplayGain tags when there's less than 5 dB headroom#2526
horazont wants to merge 2 commits into
MusicPlayerDaemon:masterfrom
horazont:fix/opus-clipping

Conversation

@horazont

@horazont horazont commented Jun 27, 2026

Copy link
Copy Markdown

Kindly see commit messages for what & why.

Here's some reproducer info:

Reproducer file

  1. Open audacity and make sure the project sample rate is set to 48 kHz
  2. Generate -> Tone...
    • Waveform = Sine
    • Frequency = 440 Hz
    • Amplitude = 1
    • Duration = 1m
  3. File -> Export Audio
    • Channels = mono
    • Sample rate = 48000 Hz
    • Encoding = signed 16-bit PCM
  4. Run opusenc on the resulting file

The file is also available here for download: https://sotecware.net/files/persistent/mpd-clipping.opus

Reproducer config

cat ~/.config/mpd/mpd.conf
music_directory "/home/horazont/tmp/music"
playlist_directory "/home/horazont/.local/share/mpd/playlists"
log_file "/home/horazont/.local/share/mpd/log.txt"
pid_file "/run/user/1000/mpd.pid"
state_file "/home/horazont/.config/mpd/state"
sticker_file "/home/horazont/.config/mpd/sticker.sql"
bind_to_address "::"
zeroconf_enabled "yes"
zeroconf_name "Music Player"
input {
	plugin "curl"
}

audio_output {
	type	"pulse"
	name	"Default"
	mixer_type "software"
}

replaygain "auto"

filesystem_charset "UTF-8"
id3v1_encoding	"UTF-8"

Replay gain must be enabled and mpd's volume must be at 100% for the effect to be noticeable.

Let me know if you want more of the reproducer info included in the commit message.

horazont added 2 commits June 27, 2026 15:09
The playback gain in the Opus header is not related to any reference
like ReplayGain or EBU R128. It thus does not need converting into the
ReplayGain "space".

This patch thus removes the 5 dB offset from the fallback path in
MPDOpusDecoder::HandleAudio (where the Opus header gain is converted
into ReplayGain metadata).

Otherwise, an Opus file without replay gain tags and with an output
gain of 0 dB would get a gain of 5 dB. This leads to clipping if the
audio data has less than 5 dB of headroom.

The clipping can be reproduced with something as simple as an amplitude
1.0 sine file. One way to create such a file is via audacity:

1. Generate -> Tone...
   - Waveform = Sine
   - Frequency = 440 Hz
   - Amplitude = 1
   - Duration = 1m
2. File -> Export Audio
   - Channels = mono
   - Sample rate = 48000 Hz
   - Encoding = signed 16-bit PCM
3. Run opusenc on the resulting file
Logically, the offset belongs to the conversion of Opus Ebu R128 metadata
to the internal ReplayGain structures.

Movin the code does not change change the logic at all (as addition is
commutative), but by moving the conversion closer to the source of the
data and avoiding EBU R128-scaled data as far as possible, I hope to
reduce the chance of future logic errors.
@horazont horazont force-pushed the fix/opus-clipping branch from 8dff7a0 to 8fdd025 Compare June 27, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant