metadata: read R128 gain tags in Opus files

RFC 7845 specifies R128_TRACK_GAIN and R128_ALBUM_GAIN for loudness
normalization in Opus files, but Rockbox only understood the
REPLAYGAIN_* tags.  Parse the R128 tags as Q7.8 dB values relative to
-23 LUFS and add 5 dB to match the ReplayGain reference level.  If a
file has both kinds of tags, the R128 tags take priority.

Fixes FS#13624

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I0ef08a7897ec5ee8d1e5d99e7d9ea3901847fa52
This commit is contained in:
Michael Giacomelli 2026-10-08 23:01:48 -04:00
parent 27a8ca3f55
commit 5880b6111c

View file

@ -138,6 +138,26 @@ static long get_replaygain(const char* str)
return fp_atof(str, FP_BITS);
}
/* Get the gain in Q19.12 format from an Opus R128 gain tag (RFC 7845).
* Returns false if the tag is not a valid integer.
*
* str Gain in dB as a Q7.8 integer, relative to -23 LUFS.
*/
static bool get_r128_gain(const char* str, long* gain)
{
char* end;
long q8 = strtol(str, &end, 10);
if (end == str || q8 < -32768 || q8 > 32767)
{
return false;
}
/* ReplayGain targets -18 LUFS, so add 5 dB to match its loudness */
*gain = q8 * (FP_ONE / 256) + 5 * FP_ONE;
return true;
}
/* Get the peak volume in Q7.24 format.
*
* str Peak volume. Full scale is specified as "1.0". Returns 0 for no peak.
@ -158,7 +178,8 @@ long get_replaygain_int(long int_gain)
/* Parse a ReplayGain tag conforming to the "VorbisGain standard". If a
* valid tag is found, update mp3entry struct accordingly. Existing values
* are not overwritten.
* are not overwritten, except by the R128 gain tags in Opus files, which
* take priority over REPLAYGAIN tags.
*
* key Name of the tag.
* value Value of the tag.
@ -170,9 +191,30 @@ void parse_replaygain(const char* key, const char* value,
static const char *rg_options[] = {"replaygain_track_gain", "rg_radio",
"replaygain_album_gain", "rg_audiophile",
"replaygain_track_peak", "rg_peak",
"replaygain_album_peak", NULL};
"replaygain_album_peak",
"r128_track_gain", "r128_album_gain",
NULL};
int rg_op = string_option(key, rg_options, true);
long gain;
if (rg_op == 7 || rg_op == 8)
{ /*r128_track_gain||r128_album_gain, only defined for Opus*/
if (entry->codectype == AFMT_OPUS && get_r128_gain(value, &gain))
{
if (rg_op == 7)
{
entry->track_level = gain;
entry->track_gain = convert_gain(gain);
}
else
{
entry->album_level = gain;
entry->album_gain = convert_gain(gain);
}
}
return;
}
if ((rg_op == 0 || rg_op == 1) && !entry->track_gain)
{ /*replaygain_track_gain||rg_radio*/