[vlc-commits] [Git][videolan/vlc][master] 7 commits: meta: case-insensitive replay gain tag parsing

Steve Lhomme (@robUx4) gitlab at videolan.org
Sun Mar 9 07:40:26 UTC 2025



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
14247db3 by Robert Stone at 2025-03-09T07:22:33+00:00
meta: case-insensitive replay gain tag parsing

- - - - -
6b089c6a by Robert Stone at 2025-03-09T07:22:33+00:00
demux: es: use common replay gain parser

- - - - -
e55a6c24 by Robert Stone at 2025-03-09T07:22:33+00:00
demux: mp4: use common replay gain parser

- - - - -
06b28313 by Robert Stone at 2025-03-09T07:22:33+00:00
demux: mp4: fix iTunes SoundCheck gain calculation

Use max from the first pair (mW/dBm) with a 1000.0 divisor to
match Apple's behavior. Verified with iTunNORM (`00001000 00000100`),
which yields -6.12 dB; min would produce +15.92 dB.

The negative sign in the formula -10 * log10(x/1000) causes larger values
to produce increasingly negative gain. Using the max from the louder
channel results in greater attenuation.

- - - - -
f1d0f4e2 by Robert Stone at 2025-03-09T07:22:33+00:00
demux: xiph: use common replay gain parser

- - - - -
694405be by Robert Stone at 2025-03-09T07:22:33+00:00
demux: aiff: add support for meta tags

- - - - -
f4abe288 by Robert Stone at 2025-03-09T07:22:33+00:00
libvlc-module: add section for replay gain

- - - - -


23 changed files:

- include/meson.build
- include/vlc_es.h
- + include/vlc_replay_gain.h
- modules/codec/vorbis.c
- modules/demux/aiff.c
- modules/demux/flac.c
- modules/demux/mp4/meta.c
- modules/demux/mp4/mp4.c
- modules/demux/mpeg/es.c
- modules/demux/ogg.c
- modules/demux/xiph_metadata.c
- modules/demux/xiph_metadata.h
- src/Makefile.am
- src/audio_output/volume.c
- src/input/decoder.c
- src/input/es_out.c
- src/input/input_internal.h
- src/input/meta.c
- + src/input/replay_gain.c
- src/input/test/es_out.c
- src/libvlc-module.c
- src/libvlccore.sym
- src/meson.build


Changes:

=====================================
include/meson.build
=====================================
@@ -92,6 +92,7 @@ install_headers(
     'vlc_probe.h',
     'vlc_rand.h',
     'vlc_renderer_discovery.h',
+    'vlc_replay_gain.h',
     'vlc_services_discovery.h',
     'vlc_sort.h',
     'vlc_sout.h',


=====================================
include/vlc_es.h
=====================================
@@ -26,6 +26,7 @@
 #include <vlc_common.h>
 #include <vlc_fourcc.h>
 #include <vlc_viewpoint.h>
+#include <vlc_replay_gain.h>
 
 /**
  * \file
@@ -46,26 +47,6 @@ struct video_palette_t
     uint8_t palette[VIDEO_PALETTE_COLORS_MAX][4];  /**< 4-byte RGBA/YUVA palette */
 };
 
-/**
- * audio replay gain description
- */
-#define AUDIO_REPLAY_GAIN_MAX (2)
-#define AUDIO_REPLAY_GAIN_TRACK (0)
-#define AUDIO_REPLAY_GAIN_ALBUM (1)
-typedef struct
-{
-    /* true if we have the peak value */
-    bool pb_peak[AUDIO_REPLAY_GAIN_MAX];
-    /* peak value where 1.0 means full sample value */
-    float      pf_peak[AUDIO_REPLAY_GAIN_MAX];
-
-    /* true if we have the gain value */
-    bool pb_gain[AUDIO_REPLAY_GAIN_MAX];
-    /* gain value in dB */
-    float      pf_gain[AUDIO_REPLAY_GAIN_MAX];
-} audio_replay_gain_t;
-
-
 /**
  * Audio channel type
  */


=====================================
include/vlc_replay_gain.h
=====================================
@@ -0,0 +1,166 @@
+/*****************************************************************************
+ * vlc_replay_gain.h : common replay gain code
+ *****************************************************************************
+ * Copyright © 2002-2004 VLC authors and VideoLAN
+ * Copyright © 2011-2012 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_REPLAY_GAIN_H
+#define VLC_REPLAY_GAIN_H 1
+
+#include <vlc_common.h>
+
+/**
+ * \file vlc_replay_gain.h
+ * \defgroup replay_gain Replay Gain
+ * \ingroup input
+ * Functions to read replay gain tags.
+ *
+ * @{
+ */
+
+/** Index for track values */
+#define AUDIO_REPLAY_GAIN_TRACK (0)
+/** Index for album values */
+#define AUDIO_REPLAY_GAIN_ALBUM (1)
+/** Number of replay gain types */
+#define AUDIO_REPLAY_GAIN_MAX (2)
+
+/**
+ * Audio replay gain
+ */
+typedef struct
+{
+    bool    pb_reference_loudness;          /**< true if we have the reference loudness */
+    float   pf_reference_loudness;          /**< reference loudness in LUFS */
+    bool    pb_gain[AUDIO_REPLAY_GAIN_MAX]; /**< true if we have the gain value */
+    float   pf_gain[AUDIO_REPLAY_GAIN_MAX]; /**< gain value in dB */
+    bool    pb_peak[AUDIO_REPLAY_GAIN_MAX]; /**< true if we have the peak value */
+    float   pf_peak[AUDIO_REPLAY_GAIN_MAX]; /**< peak value where 1.0 means full sample value */
+} audio_replay_gain_t;
+
+/**
+ * Extracts replay gain info from metadata and copies it into a replay gain structure.
+ * Supports both capitalized and lowercase metadata tags.
+ *
+ * \param p_dst Destination replay gain structure to fill
+ * \param p_meta Metadata structure to extract values from
+ * \return VLC_SUCCESS if either an album or track gain was found,
+ *         VLC_EGENERIC if no gain was found,
+ *         VLC_EINVAL if either argument is null
+ */
+VLC_API int vlc_replay_gain_CopyFromMeta( audio_replay_gain_t *p_dst, const vlc_meta_t *p_meta );
+
+/**
+ * Calculates the replay gain multiplier according to the Replay Gain 2.0 Specification.
+ * User preferences control mode, pre-amp, default gain, and peak protection.
+ *
+ * \param obj calling vlc object
+ * \param p_rg replay gain structure
+ * \return linear gain multiplier
+ */
+float replay_gain_CalcMultiplier( vlc_object_t *obj, const audio_replay_gain_t *p_rg );
+
+/**
+ * Merges replay gain structures
+ *
+ * Only copies gain/peak/reference loudness values that are:
+ * - Set in the source
+ * - Not set in the destination
+ *
+ * \param p_dst Destination replay gain structure
+ * \param p_src Source replay gain structure
+ */
+static inline void replay_gain_Merge( audio_replay_gain_t *p_dst, const audio_replay_gain_t *p_src )
+{
+    if( !p_dst || !p_src )
+        return;
+
+    if( !p_dst->pb_reference_loudness && p_src->pb_reference_loudness )
+    {
+        p_dst->pb_reference_loudness = p_src->pb_reference_loudness;
+        p_dst->pf_reference_loudness = p_src->pf_reference_loudness;
+    }
+
+    for( size_t i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
+    {
+        if( !p_dst->pb_gain[i] && p_src->pb_gain[i] )
+        {
+            p_dst->pb_gain[i] = p_src->pb_gain[i];
+            p_dst->pf_gain[i] = p_src->pf_gain[i];
+        }
+        if( !p_dst->pb_peak[i] && p_src->pb_peak[i] )
+        {
+            p_dst->pb_peak[i] = p_src->pb_peak[i];
+            p_dst->pf_peak[i] = p_src->pf_peak[i];
+        }
+    }
+}
+
+/**
+ * Compares two replay gain structures
+ *
+ * \param p_a First replay gain structure
+ * \param p_b Second replay gain structure
+ * \return true if any gain/peak/reference loudness values or their validity flags differ
+ */
+static inline bool replay_gain_Compare( const audio_replay_gain_t *p_a, const audio_replay_gain_t *p_b )
+{
+    if( !p_a || !p_b )
+        return true;
+
+    if( p_a->pb_reference_loudness != p_b->pb_reference_loudness ||
+        p_a->pf_reference_loudness != p_b->pf_reference_loudness )
+        return true;
+
+   for( size_t i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
+   {
+        if( p_a->pb_gain[i] != p_b->pb_gain[i] ||
+            p_a->pb_peak[i] != p_b->pb_peak[i] )
+            return true;
+
+        if( ( p_a->pb_gain[i] && p_a->pf_gain[i] != p_b->pf_gain[i] ) ||
+            ( p_a->pb_peak[i] && p_a->pf_peak[i] != p_b->pf_peak[i] ) )
+            return true;
+    }
+    return false;
+}
+
+/**
+ * Reset replay gain structure values
+ *
+ * \param p_dst Replay gain structure
+ */
+static inline void replay_gain_Reset( audio_replay_gain_t *p_rg )
+{
+    if( !p_rg )
+        return;
+
+    p_rg->pb_reference_loudness = false;
+    p_rg->pf_reference_loudness = 0.f;
+
+    for( size_t i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
+    {
+        p_rg->pb_gain[i] = false;
+        p_rg->pf_gain[i] = 0.f;
+
+        p_rg->pb_peak[i] = false;
+        p_rg->pf_peak[i] = 0.f;
+    }
+}
+/** @} */
+#endif


=====================================
modules/codec/vorbis.c
=====================================
@@ -38,6 +38,7 @@
 #include <vlc_aout.h>
 #include <vlc_input_item.h>
 #include <vlc_sout.h>
+#include <vlc_replay_gain.h>
 #include "../demux/xiph.h"
 
 #include <ogg/ogg.h>
@@ -405,6 +406,36 @@ static int ProcessHeaders( decoder_t *p_dec )
     }
     ParseVorbisComments( p_dec );
 
+    int i_ret = vlc_replay_gain_CopyFromMeta( &p_dec->fmt_out.audio_replay_gain, p_dec->p_description );
+
+    /* use replay gain if available; otherwise see vorbisgain(1) */
+    if( i_ret != VLC_SUCCESS && p_dec->p_description != NULL )
+    {
+        audio_replay_gain_t *p_arg = &p_dec->fmt_out.audio_replay_gain;
+        replay_gain_Reset( p_arg );
+
+        const char *track_gain = vlc_meta_GetExtra( p_dec->p_description, "RG_RADIO" );
+        if( track_gain )
+        {
+            p_arg->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = true;
+            p_arg->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = vlc_strtof_c( track_gain, NULL );
+        }
+
+        const char *track_peak = vlc_meta_GetExtra( p_dec->p_description, "RG_PEAK" );
+        if( track_peak )
+        {
+            p_arg->pb_peak[AUDIO_REPLAY_GAIN_TRACK] = true;
+            p_arg->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = vlc_strtof_c( track_peak, NULL );
+        }
+
+        const char *album_gain = vlc_meta_GetExtra( p_dec->p_description, "RG_AUDIOPHILE" );
+        if( album_gain )
+        {
+            p_arg->pb_gain[AUDIO_REPLAY_GAIN_ALBUM] = true;
+            p_arg->pf_gain[AUDIO_REPLAY_GAIN_ALBUM] = vlc_strtof_c( album_gain, NULL );
+        }
+    }
+
     /* The next packet in order is the codebooks header
      * We need to watch out that this packet is not missing as a
      * missing or corrupted header is fatal. */
@@ -620,38 +651,7 @@ static void ParseVorbisComments( decoder_t *p_dec )
             *psz_value = '\0';
             psz_value++;
 
-            if( !strcasecmp( psz_name, "REPLAYGAIN_TRACK_GAIN" ) ||
-                !strcasecmp( psz_name, "RG_RADIO" ) )
-            {
-                audio_replay_gain_t *r = &p_dec->fmt_out.audio_replay_gain;
-
-                r->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = true;
-                r->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( psz_value );
-            }
-            else if( !strcasecmp( psz_name, "REPLAYGAIN_TRACK_PEAK" ) ||
-                     !strcasecmp( psz_name, "RG_PEAK" ) )
-            {
-                audio_replay_gain_t *r = &p_dec->fmt_out.audio_replay_gain;
-
-                r->pb_peak[AUDIO_REPLAY_GAIN_TRACK] = true;
-                r->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( psz_value );
-            }
-            else if( !strcasecmp( psz_name, "REPLAYGAIN_ALBUM_GAIN" ) ||
-                     !strcasecmp( psz_name, "RG_AUDIOPHILE" ) )
-            {
-                audio_replay_gain_t *r = &p_dec->fmt_out.audio_replay_gain;
-
-                r->pb_gain[AUDIO_REPLAY_GAIN_ALBUM] = true;
-                r->pf_gain[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( psz_value );
-            }
-            else if( !strcasecmp( psz_name, "REPLAYGAIN_ALBUM_PEAK" ) )
-            {
-                audio_replay_gain_t *r = &p_dec->fmt_out.audio_replay_gain;
-
-                r->pb_peak[AUDIO_REPLAY_GAIN_ALBUM] = true;
-                r->pf_peak[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( psz_value );
-            }
-            else if( !strcasecmp( psz_name, "METADATA_BLOCK_PICTURE" ) )
+            if( !strcasecmp( psz_name, "METADATA_BLOCK_PICTURE" ) )
             { /* Do nothing, for now */ }
             else
             {


=====================================
modules/demux/aiff.c
=====================================
@@ -32,6 +32,7 @@
 #include <vlc_plugin.h>
 #include <vlc_demux.h>
 #include <vlc_aout.h>
+#include <vlc_meta.h>
 #include <limits.h>
 
 #include "mp4/coreaudio.h"
@@ -44,12 +45,13 @@
  * Module descriptor
  *****************************************************************************/
 static int  Open    ( vlc_object_t * );
+static void Close   ( vlc_object_t * );
 
 vlc_module_begin ()
     set_subcategory( SUBCAT_INPUT_DEMUX )
     set_description( N_("AIFF demuxer" ) )
     set_capability( "demux", 10 )
-    set_callback( Open )
+    set_callbacks( Open, Close )
     add_shortcut( "aiff" )
     add_file_extension("aiff")
 vlc_module_end ()
@@ -78,6 +80,7 @@ typedef struct
 
     bool        b_reorder;
     uint8_t     pi_chan_table[AOUT_CHAN_MAX];
+    vlc_meta_t  *p_meta;
     vlc_fourcc_t audio_fourcc;
 } demux_sys_t;
 
@@ -103,12 +106,66 @@ static unsigned int GetF80BE( const uint8_t p[10] )
     return i_mantissa;
 }
 
+/*****************************************************************************
+ * ReadTextChunk: reads aiff text chunks - name, author, copyright, annotation
+ *****************************************************************************/
+static int ReadTextChunk( demux_t *p_demux,  uint64_t i_chunk_size, uint32_t i_data_size )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    const uint8_t *p_peek;
+
+    ssize_t ret = vlc_stream_Peek( p_demux->s, &p_peek, i_chunk_size );
+    if( ret == -1 || (size_t) ret != i_chunk_size )
+        return VLC_EGENERIC;
+
+    static const struct {
+        const char  psz_name[4];
+        int         i_meta;
+    } p_dsc[4] = {
+        { "NAME", vlc_meta_Title },
+        { "AUTH", vlc_meta_Artist },
+        { "(c) ", vlc_meta_Copyright },
+        { "ANNO", vlc_meta_Description }
+    };
+
+    for( size_t i = 0; i < ARRAY_SIZE( p_dsc ); i++ )
+    {
+        if( memcmp(p_peek, p_dsc[i].psz_name, 4) )
+            continue;
+
+        char *psz_value = malloc( i_data_size + 1 );
+
+        if( unlikely(psz_value == NULL) )
+            return VLC_ENOMEM;
+
+        if( !p_sys->p_meta )
+        {
+            p_sys->p_meta = vlc_meta_New();
+            if( unlikely(p_sys->p_meta == NULL) )
+            {
+                free( psz_value );
+                return VLC_ENOMEM;
+            }
+        }
+
+        memcpy( psz_value, (char*)&p_peek[8], i_data_size );
+        psz_value[i_data_size] = '\0';
+        vlc_meta_Set( p_sys->p_meta, p_dsc[i].i_meta, psz_value );
+        free( psz_value );
+
+        return VLC_SUCCESS;
+    }
+
+    return VLC_EGENERIC;
+}
+
 /*****************************************************************************
  * Open
  *****************************************************************************/
 static int Open( vlc_object_t *p_this )
 {
     demux_t     *p_demux = (demux_t*)p_this;
+    demux_sys_t *p_sys;
 
     const uint8_t *p_peek;
 
@@ -121,19 +178,24 @@ static int Open( vlc_object_t *p_this )
     if( vlc_stream_Read( p_demux->s, NULL, 12 ) != 12 )
         return VLC_EGENERIC;
 
-    /* Fill p_demux field */
-    demux_sys_t *p_sys = vlc_obj_calloc( p_this, 1, sizeof (*p_sys) );
-    es_format_Init( &p_sys->fmt, AUDIO_ES, VLC_FOURCC( 't', 'w', 'o', 's' ) );
+    p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
+
+    if( unlikely(p_sys == NULL) )
+        return VLC_ENOMEM;
+
+    p_demux->p_sys = p_sys;
+
     p_sys->i_time = 0;
     p_sys->i_ssnd_pos = -1;
     p_sys->b_reorder = false;
+    es_format_Init( &p_sys->fmt, AUDIO_ES, VLC_FOURCC( 't', 'w', 'o', 's' ) );
 
     const uint32_t *pi_channels_in = NULL;
 
     for( ;; )
     {
         if( vlc_stream_Peek( p_demux->s, &p_peek, 8 ) < 8 )
-            return VLC_EGENERIC;
+            break;
 
         uint32_t i_data_size = GetDWBE( &p_peek[4] );
         uint64_t i_chunk_size = UINT64_C( 8 ) + i_data_size + ( i_data_size & 1 );
@@ -184,13 +246,13 @@ static int Open( vlc_object_t *p_this )
                                           &p_sys->fmt.audio.i_channels,
                                           &pi_channels_in ) != VLC_SUCCESS )
                 msg_Warn( p_demux, "discarding chan mapping" );
-
         }
-
-        if( p_sys->i_ssnd_pos >= 12 && p_sys->fmt.audio.i_channels != 0 )
+        else
         {
-            /* We have found the 2 needed chunks */
-            break;
+            int i_ret = ReadTextChunk( p_demux, i_chunk_size, i_data_size );
+
+            if( unlikely(i_ret == VLC_ENOMEM) )
+                return VLC_ENOMEM;
         }
 
         /* consume chunk data */
@@ -228,7 +290,7 @@ static int Open( vlc_object_t *p_this )
     p_sys->i_ssnd_fsize = p_sys->fmt.audio.i_channels *
                           ((p_sys->fmt.audio.i_bitspersample + 7) / 8);
 
-    if( p_sys->i_ssnd_fsize <= 0 || p_sys->fmt.audio.i_rate == 0 )
+    if( p_sys->i_ssnd_fsize <= 0 || p_sys->fmt.audio.i_rate == 0 || p_sys->i_ssnd_pos < 12 )
     {
         msg_Err( p_demux, "invalid audio parameters" );
         return VLC_EGENERIC;
@@ -255,7 +317,6 @@ static int Open( vlc_object_t *p_this )
 
     p_demux->pf_demux = Demux;
     p_demux->pf_control = Control;
-    p_demux->p_sys = p_sys;
 
     return VLC_SUCCESS;
 }
@@ -307,6 +368,18 @@ static int Demux( demux_t *p_demux )
     return VLC_DEMUXER_SUCCESS;
 }
 
+/*****************************************************************************
+ * Close: frees unused data
+ *****************************************************************************/
+static void Close( vlc_object_t * p_this )
+{
+    demux_t     *p_demux = (demux_t*)p_this;
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    if( p_sys->p_meta )
+        vlc_meta_Delete( p_sys->p_meta );
+}
+
 /*****************************************************************************
  * Control:
  *****************************************************************************/
@@ -374,6 +447,15 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             }
             return VLC_EGENERIC;
         }
+        case DEMUX_GET_META:
+        {
+            vlc_meta_t *p_meta = va_arg( args, vlc_meta_t * );
+            if( !p_sys->p_meta )
+                return VLC_EGENERIC;
+            vlc_meta_Merge( p_meta, p_sys->p_meta );
+            return VLC_SUCCESS;
+        }
+
         case DEMUX_SET_TIME:
         case DEMUX_GET_FPS:
             return VLC_EGENERIC;


=====================================
modules/demux/flac.c
=====================================
@@ -36,6 +36,7 @@
 #include <vlc_input.h>                /* vlc_input_attachment, vlc_seekpoint */
 #include <vlc_codec.h>                /* decoder_t */
 #include <vlc_charset.h>              /* EnsureUTF8 */
+#include <vlc_replay_gain.h>
 
 #include <assert.h>
 #include <limits.h>
@@ -744,6 +745,8 @@ static int  ParseHeaders( demux_t *p_demux, es_format_t *p_fmt )
             i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 4+i_len );
             if( i_peek == 4+i_len )
                 ParseComment( p_demux, p_peek, i_peek );
+
+            vlc_replay_gain_CopyFromMeta( &p_fmt->audio_replay_gain, p_sys->p_meta );
         }
         else if( i_type == META_PICTURE )
         {
@@ -818,7 +821,7 @@ static void ParseComment( demux_t *p_demux, const uint8_t *p_data, size_t i_data
     vorbis_ParseComment( NULL, &p_sys->p_meta, &p_data[4], i_data - 4,
         &p_sys->i_attachments, &p_sys->attachments,
         &p_sys->i_cover_score, &p_sys->i_cover_idx,
-        &p_sys->i_title_seekpoints, &p_sys->pp_title_seekpoints, NULL, NULL );
+        &p_sys->i_title_seekpoints, &p_sys->pp_title_seekpoints );
 }
 
 static void ParsePicture( demux_t *p_demux, const uint8_t *p_data, size_t i_data )


=====================================
modules/demux/mp4/meta.c
=====================================
@@ -395,9 +395,10 @@ static void iTUNTripletCallback( const char *psz_key,
                               "%*"PRIX32" %*"PRIX32" %"PRIX32" %"PRIX32,
                               &values[0], &values[1], &values[2], &values[3] ) == 4 )
         {
-            values[0] = __MIN(values[0], values[1]); /* left / right volume */
+            values[0] = __MAX(values[0], values[1]); /* left / right volume */
             if( values[0] )
-                data.NORM.volume_adjust =  -10.0 * log10( values[0] / 10000.0 );
+                data.NORM.volume_adjust = -10.0 * log10( values[0] / 1000.0 );
+
             values[2] = __MAX(values[2], values[3]); /* left / right peak */
             if( values[2] )
                 data.NORM.peak = values[2] / 32768.0;
@@ -420,6 +421,13 @@ static void iTUNTripletCallback( const char *psz_key,
         }
         ctx->ituncb( &data, ctx->priv );
     }
+    else if( !strncasecmp(psz_key, "REPLAYGAIN_", 11) )
+    {
+        char *psz_val = StringConvert( p_data );
+        if( psz_val )
+            vlc_meta_SetExtra( ctx->p_meta, psz_key, psz_val );
+        free( psz_val );
+    }
 }
 
 static void SetupmdirMeta( vlc_meta_t *p_meta, const MP4_Box_t *p_box, void *priv )
@@ -618,4 +626,3 @@ void SetupMeta( vlc_meta_t *p_meta, const MP4_Box_t *p_udta,
         }
     }
 }
-


=====================================
modules/demux/mp4/mp4.c
=====================================
@@ -36,8 +36,10 @@
 #include <vlc_plugin.h>
 #include <vlc_dialog.h>
 #include <vlc_url.h>
+#include <vlc_replay_gain.h>
 #include <assert.h>
 #include <limits.h>
+#include <math.h>
 #include "meta.h"
 #include "attachments.h"
 #include "heif.h"
@@ -1048,6 +1050,8 @@ static int Open( vlc_object_t * p_this )
 
     p_sys->context.i_lastseqnumber = UINT32_MAX;
     p_sys->i_attachments = -1;
+    p_sys->qt.f_replay_gain_norm = NAN;
+    p_sys->qt.f_replay_gain_peak = NAN;
 
     p_demux->p_sys = p_sys;
 
@@ -3258,37 +3262,36 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
                     (float)p_fmt->video.i_frame_rate_base;
             break;
         case AUDIO_ES:
+        {
+            int i_ret = VLC_EGENERIC;
             if( p_sys->p_meta )
             {
-                audio_replay_gain_t *p_arg = &p_fmt->audio_replay_gain;
-                const char *psz_meta = vlc_meta_GetExtra( p_sys->p_meta, "replaygain_track_gain" );
-                if( psz_meta )
-                {
-                    double f_gain = vlc_atof_c( psz_meta );
-                    p_arg->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = f_gain;
-                    p_arg->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = f_gain != 0;
-                }
-                psz_meta = vlc_meta_GetExtra( p_sys->p_meta, "replaygain_track_peak" );
-                if( psz_meta )
-                {
-                    double f_gain = vlc_atof_c( psz_meta );
-                    p_arg->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = f_gain;
-                    p_arg->pb_peak[AUDIO_REPLAY_GAIN_TRACK] = f_gain > 0;
-                }
+                i_ret = vlc_replay_gain_CopyFromMeta( &p_fmt->audio_replay_gain, p_sys->p_meta );
             }
 
-            if( p_sys->qt.f_replay_gain_peak > 0 )
+            /* use replay gain if available; otherwise use sound check */
+            if( i_ret != VLC_SUCCESS )
             {
                 audio_replay_gain_t *p_arg = &p_fmt->audio_replay_gain;
-                if( !p_arg->pb_gain[AUDIO_REPLAY_GAIN_TRACK] )
+                replay_gain_Reset( p_arg );
+
+                if( isfinite(p_sys->qt.f_replay_gain_norm) )
                 {
-                    p_arg->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = p_sys->qt.f_replay_gain_norm;
                     p_arg->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = true;
+                    p_arg->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = p_sys->qt.f_replay_gain_norm;
                 }
-                if( !p_arg->pb_peak[AUDIO_REPLAY_GAIN_TRACK] )
+
+                if( isfinite(p_sys->qt.f_replay_gain_peak) )
                 {
-                    p_arg->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = p_sys->qt.f_replay_gain_peak;
                     p_arg->pb_peak[AUDIO_REPLAY_GAIN_TRACK] = true;
+                    p_arg->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = p_sys->qt.f_replay_gain_peak;
+                }
+
+                if( p_arg->pb_gain[AUDIO_REPLAY_GAIN_TRACK] )
+                {
+                    /* sound check uses -16 LUFS as the reference level */
+                    p_arg->pb_reference_loudness = true;
+                    p_arg->pf_reference_loudness = -16.f;
                 }
             }
 
@@ -3358,6 +3361,7 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
                     break;
             }
             break;
+        }
         default:
             break;
     }


=====================================
modules/demux/mpeg/es.c
=====================================
@@ -30,6 +30,8 @@
 # include "config.h"
 #endif
 
+#include <math.h>
+
 #include <vlc_common.h>
 #include <vlc_arrays.h>
 #include <vlc_plugin.h>
@@ -37,6 +39,7 @@
 #include <vlc_codec.h>
 #include <vlc_codecs.h>
 #include <vlc_input.h>
+#include <vlc_replay_gain.h>
 
 #include "../../packetizer/a52.h"
 #include "../../packetizer/dts_header.h"
@@ -362,8 +365,7 @@ typedef struct
     struct mpga_frameheader_s mpgah;
     struct xing_info_s xing;
 
-    float rgf_replay_gain[AUDIO_REPLAY_GAIN_MAX];
-    float rgf_replay_peak[AUDIO_REPLAY_GAIN_MAX];
+    audio_replay_gain_t audio_replay_gain;
 
     sync_table_t mllt;
     struct
@@ -435,6 +437,9 @@ static int OpenCommon( demux_t *p_demux,
     p_sys->f_fps = var_InheritFloat( p_demux, "es-fps" );
     p_sys->p_packetized_data = NULL;
     p_sys->chapters.i_current = 0;
+    p_sys->xing.f_peak_signal = NAN;
+    p_sys->xing.f_radio_replay_gain = NAN;
+    p_sys->xing.f_audiophile_replay_gain = NAN;
     TAB_INIT(p_sys->chapters.i_count, p_sys->chapters.p_entry);
 
     if( vlc_stream_Seek( p_demux->s, p_sys->i_stream_offset ) )
@@ -468,19 +473,7 @@ static int OpenCommon( demux_t *p_demux,
     }
 
     es_format_t *p_fmt = &p_sys->p_packetizer->fmt_out;
-    for( int i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
-    {
-        if ( p_sys->rgf_replay_gain[i] != 0.0 )
-        {
-            p_fmt->audio_replay_gain.pb_gain[i] = true;
-            p_fmt->audio_replay_gain.pf_gain[i] = p_sys->rgf_replay_gain[i];
-        }
-        if ( p_sys->rgf_replay_peak[i] != 0.0 )
-        {
-            p_fmt->audio_replay_gain.pb_peak[i] = true;
-            p_fmt->audio_replay_gain.pf_peak[i] = p_sys->rgf_replay_peak[i];
-        }
-    }
+    replay_gain_Merge( &p_fmt->audio_replay_gain, &p_sys->audio_replay_gain );
 
     for( ;; )
     {
@@ -1270,30 +1263,7 @@ static int ID3TAG_Parse_Handler( uint32_t i_tag, const uint8_t *p_payload, size_
             bool b_updated;
             if( ID3HandleTag( p_payload, i_payload, i_tag, p_meta, &b_updated ) )
             {
-                char ** ppsz_keys = vlc_meta_CopyExtraNames( p_meta );
-                if( ppsz_keys )
-                {
-                    for( size_t i = 0; ppsz_keys[i]; ++i )
-                    {
-                        float *pf = NULL;
-                        if(     !strcasecmp( ppsz_keys[i], "REPLAYGAIN_TRACK_GAIN" ) )
-                            pf = &p_sys->rgf_replay_gain[AUDIO_REPLAY_GAIN_TRACK];
-                        else if( !strcasecmp( ppsz_keys[i], "REPLAYGAIN_TRACK_PEAK" ) )
-                            pf = &p_sys->rgf_replay_peak[AUDIO_REPLAY_GAIN_TRACK];
-                        else if( !strcasecmp( ppsz_keys[i], "REPLAYGAIN_ALBUM_GAIN" ) )
-                            pf = &p_sys->rgf_replay_gain[AUDIO_REPLAY_GAIN_ALBUM];
-                        else if( !strcasecmp( ppsz_keys[i], "REPLAYGAIN_ALBUM_PEAK" ) )
-                            pf = &p_sys->rgf_replay_peak[AUDIO_REPLAY_GAIN_ALBUM];
-                        if( pf )
-                        {
-                            const char *psz_val = vlc_meta_GetExtra( p_meta, ppsz_keys[i] );
-                            if( psz_val )
-                                *pf = vlc_atof_c( psz_val );
-                        }
-                        free( ppsz_keys[i] );
-                    }
-                    free( ppsz_keys );
-                }
+                vlc_replay_gain_CopyFromMeta( &p_sys->audio_replay_gain, p_meta );
             }
             vlc_meta_Delete( p_meta );
         }
@@ -1421,9 +1391,23 @@ static int MpgaInit( demux_t *p_demux )
             p_sys->b_estimate_bitrate = false;
         }
 
-        p_sys->rgf_replay_peak[AUDIO_REPLAY_GAIN_TRACK] = xing->f_peak_signal;
-        p_sys->rgf_replay_gain[AUDIO_REPLAY_GAIN_TRACK] = xing->f_radio_replay_gain;
-        p_sys->rgf_replay_gain[AUDIO_REPLAY_GAIN_ALBUM] = xing->f_audiophile_replay_gain;
+        if( isfinite(xing->f_radio_replay_gain) )
+        {
+            p_sys->audio_replay_gain.pb_gain[AUDIO_REPLAY_GAIN_TRACK] = true;
+            p_sys->audio_replay_gain.pf_gain[AUDIO_REPLAY_GAIN_TRACK] = xing->f_radio_replay_gain;
+        }
+
+        if( isfinite(xing->f_peak_signal) )
+        {
+            p_sys->audio_replay_gain.pb_peak[AUDIO_REPLAY_GAIN_TRACK] = true;
+            p_sys->audio_replay_gain.pf_peak[AUDIO_REPLAY_GAIN_TRACK] = xing->f_peak_signal;
+        }
+
+        if( isfinite(xing->f_audiophile_replay_gain) )
+        {
+            p_sys->audio_replay_gain.pb_gain[AUDIO_REPLAY_GAIN_ALBUM] = true;
+            p_sys->audio_replay_gain.pf_gain[AUDIO_REPLAY_GAIN_ALBUM] = xing->f_audiophile_replay_gain;
+        }
 
         msg_Dbg( p_demux, "Using '%4.4s' infotag"
                           "(%"PRIu32" bytes, %"PRIu32" frames, %u samples/frame)",


=====================================
modules/demux/ogg.c
=====================================
@@ -39,6 +39,7 @@
 #include <vlc_demux.h>
 #include <vlc_meta.h>
 #include <vlc_input.h>
+#include <vlc_replay_gain.h>
 
 #include <ogg/ogg.h>
 
@@ -2569,18 +2570,12 @@ static void Ogg_ExtractComments( demux_t *p_demux, es_format_t *p_fmt,
     demux_sys_t *p_ogg = p_demux->p_sys;
     int i_cover_score = 0;
     int i_cover_idx = 0;
-    float pf_replay_gain[AUDIO_REPLAY_GAIN_MAX];
-    float pf_replay_peak[AUDIO_REPLAY_GAIN_MAX];
-    for(int i=0; i< AUDIO_REPLAY_GAIN_MAX; i++ )
-    {
-        pf_replay_gain[i] = 0;
-        pf_replay_peak[i] = 0;
-    }
+
     vorbis_ParseComment( p_fmt, &p_ogg->p_meta, p_headers, i_headers,
                          &p_ogg->i_attachments, &p_ogg->attachments,
                          &i_cover_score, &i_cover_idx,
-                         &p_ogg->i_seekpoints, &p_ogg->pp_seekpoints,
-                         &pf_replay_gain, &pf_replay_peak );
+                         &p_ogg->i_seekpoints, &p_ogg->pp_seekpoints );
+
     if( p_ogg->p_meta != NULL && i_cover_idx < p_ogg->i_attachments )
     {
         char psz_url[128];
@@ -2589,19 +2584,33 @@ static void Ogg_ExtractComments( demux_t *p_demux, es_format_t *p_fmt,
         vlc_meta_Set( p_ogg->p_meta, vlc_meta_ArtworkURL, psz_url );
     }
 
-    for ( int i=0; i<AUDIO_REPLAY_GAIN_MAX;i++ )
+    int i_ret = vlc_replay_gain_CopyFromMeta( &p_fmt->audio_replay_gain, p_ogg->p_meta );
+
+    /* use replay gain if available; otherwise use IETF RFC7845 §5.2.1 */
+    if( i_ret != VLC_SUCCESS && p_ogg->p_meta != NULL )
     {
-        if ( pf_replay_gain[i] != 0 )
+        audio_replay_gain_t *p_arg = &p_fmt->audio_replay_gain;
+        replay_gain_Reset( p_arg );
+
+        const char *track_gain = vlc_meta_GetExtra( p_ogg->p_meta, "R128_TRACK_GAIN" );
+        if( track_gain )
         {
-            p_fmt->audio_replay_gain.pb_gain[i] = true;
-            p_fmt->audio_replay_gain.pf_gain[i] = pf_replay_gain[i];
-            msg_Dbg( p_demux, "setting replay gain %d to %f", i, pf_replay_gain[i] );
+            p_arg->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = true;
+            p_arg->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = vlc_strtof_c( track_gain, NULL ) / 256.f;
         }
-        if ( pf_replay_peak[i] != 0 )
+
+        const char *album_gain = vlc_meta_GetExtra( p_ogg->p_meta, "R128_ALBUM_GAIN" );
+        if( album_gain )
+        {
+            p_arg->pb_gain[AUDIO_REPLAY_GAIN_ALBUM] = true;
+            p_arg->pf_gain[AUDIO_REPLAY_GAIN_ALBUM] = vlc_strtof_c( album_gain, NULL ) / 256.f;
+        }
+
+        if( track_gain || album_gain )
         {
-            p_fmt->audio_replay_gain.pb_peak[i] = true;
-            p_fmt->audio_replay_gain.pf_peak[i] = pf_replay_peak[i];
-            msg_Dbg( p_demux, "setting replay peak %d to %f", i, pf_replay_gain[i] );
+            /* EBU R128 uses -23 LUFS as the reference level */
+            p_arg->pb_reference_loudness = true;
+            p_arg->pf_reference_loudness = -23.f;
         }
     }
 


=====================================
modules/demux/xiph_metadata.c
=====================================
@@ -327,9 +327,7 @@ void vorbis_ParseComment( es_format_t *p_fmt, vlc_meta_t **pp_meta,
         const uint8_t *p_data, size_t i_data,
         int *i_attachments, input_attachment_t ***attachments,
         int *i_cover_score, int *i_cover_idx,
-        int *i_seekpoint, seekpoint_t ***ppp_seekpoint,
-        float (* ppf_replay_gain)[AUDIO_REPLAY_GAIN_MAX],
-        float (* ppf_replay_peak)[AUDIO_REPLAY_GAIN_MAX] )
+        int *i_seekpoint, seekpoint_t ***ppp_seekpoint )
 {
     if( i_data < 8 )
         return;
@@ -465,27 +463,6 @@ void vorbis_ParseComment( es_format_t *p_fmt, vlc_meta_t **pp_meta,
                     *i_attachments, *attachments, p_attachment );
             }
         }
-        else if ( ppf_replay_gain && ppf_replay_peak && !strncmp(psz_comment, "REPLAYGAIN_", 11) )
-        {
-            char *p = strchr( psz_comment, '=' );
-            if (!p) goto next_comment;
-            if ( !strncasecmp(psz_comment, "REPLAYGAIN_TRACK_GAIN=", 22) )
-            {
-                (*ppf_replay_gain)[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( ++p );
-            }
-            else if ( !strncasecmp(psz_comment, "REPLAYGAIN_ALBUM_GAIN=", 22) )
-            {
-                (*ppf_replay_gain)[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( ++p );
-            }
-            else if ( !strncasecmp(psz_comment, "REPLAYGAIN_ALBUM_PEAK=", 22) )
-            {
-                (*ppf_replay_peak)[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( ++p );
-            }
-            else if ( !strncasecmp(psz_comment, "REPLAYGAIN_TRACK_PEAK=", 22) )
-            {
-                (*ppf_replay_peak)[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( ++p );
-            }
-        }
         else if( !strncasecmp(psz_comment, "CHAPTER", 7) )
         {
             unsigned int i_chapt;


=====================================
modules/demux/xiph_metadata.h
=====================================
@@ -35,9 +35,7 @@ void vorbis_ParseComment( es_format_t *p_fmt, vlc_meta_t **pp_meta,
         const uint8_t *p_data, size_t i_data,
         int *i_attachments, input_attachment_t ***attachments,
         int *i_cover_score, int *i_cover_idx,
-        int *i_seekpoint, seekpoint_t ***ppp_seekpoint,
-        float (* ppf_replay_gain)[AUDIO_REPLAY_GAIN_MAX],
-        float (* ppf_replay_peak)[AUDIO_REPLAY_GAIN_MAX] );
+        int *i_seekpoint, seekpoint_t ***ppp_seekpoint );
 
 static const struct {
   const char *psz_tag;


=====================================
src/Makefile.am
=====================================
@@ -97,6 +97,7 @@ pluginsinclude_HEADERS.h = \
 	../include/vlc_queue.h \
 	../include/vlc_rand.h \
 	../include/vlc_renderer_discovery.h \
+	../include/vlc_replay_gain.h \
 	../include/vlc_services_discovery.h \
 	../include/vlc_sort.h \
 	../include/vlc_sout.h \
@@ -290,6 +291,7 @@ libvlccore_la_SOURCES = \
 	input/meta.c \
 	input/attachment.c \
 	input/parse.c \
+	input/replay_gain.c \
 	player/player.c \
 	player/player.h \
 	player/input.c \


=====================================
src/audio_output/volume.c
=====================================
@@ -32,6 +32,7 @@
 #include <vlc_modules.h>
 #include <vlc_aout.h>
 #include <vlc_aout_volume.h>
+#include <vlc_replay_gain.h>
 #include "aout_internal.h"
 
 struct aout_volume
@@ -134,63 +135,12 @@ int aout_volume_Amplify(aout_volume_t *vol, block_t *block)
     return 0;
 }
 
-/*** Replay gain ***/
-static float aout_ReplayGainSelect(vlc_object_t *obj, const char *str,
-                                   const audio_replay_gain_t *replay_gain)
-{
-    unsigned mode = AUDIO_REPLAY_GAIN_MAX;
-
-    if (likely(str != NULL))
-    {   /* Find selectrf mode */
-        if (!strcmp (str, "track"))
-            mode = AUDIO_REPLAY_GAIN_TRACK;
-        else
-        if (!strcmp (str, "album"))
-            mode = AUDIO_REPLAY_GAIN_ALBUM;
-    }
-
-    /* */
-    float multiplier;
-
-    if (mode == AUDIO_REPLAY_GAIN_MAX)
-    {
-        multiplier = 1.f;
-    }
-    else
-    {
-        float gain;
-
-        /* If the selectrf mode is not available, prefer the other one */
-        if (!replay_gain->pb_gain[mode] && replay_gain->pb_gain[!mode])
-            mode = !mode;
-
-        if (replay_gain->pb_gain[mode])
-            gain = replay_gain->pf_gain[mode]
-                 + var_InheritFloat (obj, "audio-replay-gain-preamp");
-        else
-            gain = var_InheritFloat (obj, "audio-replay-gain-default");
-
-        multiplier = powf (10.f, gain / 20.f);
-
-        if (var_InheritBool (obj, "audio-replay-gain-peak-protection"))
-            multiplier = fminf (multiplier, replay_gain->pb_peak[mode]
-                                            ? 1.f / replay_gain->pf_peak[mode]
-                                            : 1.f);
-    }
-
-    /* Command line / configuration gain */
-    multiplier *= var_InheritFloat (obj, "gain");
-
-    return multiplier;
-}
-
 static int ReplayGainCallback (vlc_object_t *obj, char const *var,
                                vlc_value_t oldval, vlc_value_t val, void *data)
 {
     aout_volume_t *vol = data;
-    float multiplier = aout_ReplayGainSelect(obj, val.psz_string,
-                                             &vol->replay_gain);
+    float multiplier = replay_gain_CalcMultiplier(obj, &vol->replay_gain);
     atomic_store_explicit(&vol->gain_factor, multiplier, memory_order_relaxed);
-    VLC_UNUSED(var); VLC_UNUSED(oldval);
+    VLC_UNUSED(var); VLC_UNUSED(oldval); VLC_UNUSED(val);
     return VLC_SUCCESS;
 }


=====================================
src/input/decoder.c
=====================================
@@ -45,6 +45,7 @@
 #include <vlc_picture_pool.h>
 #include <vlc_tracer.h>
 #include <vlc_list.h>
+#include <vlc_replay_gain.h>
 
 #include "audio_output/aout_internal.h"
 #include "stream_output/stream_output.h"
@@ -495,20 +496,6 @@ static void MouseEvent( const vlc_mouse_t *newmouse, void *user_data )
 /*****************************************************************************
  * Buffers allocation callbacks for the decoders
  *****************************************************************************/
-static bool aout_replaygain_changed( const audio_replay_gain_t *a,
-                                     const audio_replay_gain_t *b )
-{
-    for( size_t i=0; i<AUDIO_REPLAY_GAIN_MAX; i++ )
-    {
-        if( a->pb_gain[i] != b->pb_gain[i] ||
-            a->pb_peak[i] != b->pb_peak[i] ||
-            (a->pb_gain[i] && a->pf_gain[i] != b->pf_gain[i]) ||
-            (a->pb_peak[i] && a->pf_peak[i] != b->pf_peak[i]) )
-            return true;
-    }
-    return false;
-}
-
 static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
 {
     vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
@@ -532,8 +519,8 @@ static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
     }
 
     /* Check if only replay gain has changed */
-    if( aout_replaygain_changed( &p_dec->fmt_in->audio_replay_gain,
-                                 &p_owner->fmt.audio_replay_gain ) )
+    if( replay_gain_Compare( &p_dec->fmt_in->audio_replay_gain,
+                             &p_owner->fmt.audio_replay_gain ) )
     {
         p_dec->fmt_out.audio_replay_gain = p_dec->fmt_in->audio_replay_gain;
         if( p_owner->p_aout )
@@ -2065,19 +2052,7 @@ CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
     /* Copy ourself the input replay gain */
     if( fmt->i_cat == AUDIO_ES )
     {
-        for( unsigned i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
-        {
-            if( !p_dec->fmt_out.audio_replay_gain.pb_peak[i] )
-            {
-                p_dec->fmt_out.audio_replay_gain.pb_peak[i] = fmt->audio_replay_gain.pb_peak[i];
-                p_dec->fmt_out.audio_replay_gain.pf_peak[i] = fmt->audio_replay_gain.pf_peak[i];
-            }
-            if( !p_dec->fmt_out.audio_replay_gain.pb_gain[i] )
-            {
-                p_dec->fmt_out.audio_replay_gain.pb_gain[i] = fmt->audio_replay_gain.pb_gain[i];
-                p_dec->fmt_out.audio_replay_gain.pf_gain[i] = fmt->audio_replay_gain.pf_gain[i];
-            }
-        }
+        replay_gain_Merge( &p_dec->fmt_out.audio_replay_gain, &fmt->audio_replay_gain );
     }
 
     /* */


=====================================
src/input/es_out.c
=====================================
@@ -44,6 +44,7 @@
 #include <vlc_decoder.h>
 #include <vlc_memstream.h>
 #include <vlc_tracer.h>
+#include <vlc_replay_gain.h>
 
 #include "input_internal.h"
 #include "./source.h"
@@ -2037,22 +2038,10 @@ static void EsOutFillEsFmt(es_out_sys_t *p_sys, es_format_t *fmt)
         audio_replay_gain_t rg;
         memset( &rg, 0, sizeof(rg) );
         vlc_mutex_lock( &input_priv(p_input)->p_item->lock );
-        vlc_audio_replay_gain_MergeFromMeta( &rg, input_priv(p_input)->p_item->p_meta );
+        vlc_replay_gain_CopyFromMeta( &rg, input_priv(p_input)->p_item->p_meta );
         vlc_mutex_unlock( &input_priv(p_input)->p_item->lock );
+        replay_gain_Merge( &fmt->audio_replay_gain, &rg );
 
-        for( int i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
-        {
-            if( !fmt->audio_replay_gain.pb_peak[i] )
-            {
-                fmt->audio_replay_gain.pb_peak[i] = rg.pb_peak[i];
-                fmt->audio_replay_gain.pf_peak[i] = rg.pf_peak[i];
-            }
-            if( !fmt->audio_replay_gain.pb_gain[i] )
-            {
-                fmt->audio_replay_gain.pb_gain[i] = rg.pb_gain[i];
-                fmt->audio_replay_gain.pf_gain[i] = rg.pf_gain[i];
-            }
-        }
         break;
     }
 


=====================================
src/input/input_internal.h
=====================================
@@ -707,10 +707,6 @@ void vlc_object_InitInputConfig(vlc_object_t *obj,
 int subtitles_Detect( input_thread_t *, char *, const char *, input_item_slave_t ***, int * );
 int subtitles_Filter( const char *);
 
-/* meta.c */
-void vlc_audio_replay_gain_MergeFromMeta( audio_replay_gain_t *p_dst,
-                                          const vlc_meta_t *p_meta );
-
 /* stats.c */
 typedef struct input_rate_t
 {


=====================================
src/input/meta.c
=====================================
@@ -320,39 +320,3 @@ error:
     vlc_object_delete(p_export);
     return VLC_EGENERIC;
 }
-
-void vlc_audio_replay_gain_MergeFromMeta( audio_replay_gain_t *p_dst,
-                                          const vlc_meta_t *p_meta )
-{
-    const char * psz_value;
-
-    if( !p_meta )
-        return;
-
-    if( (psz_value = vlc_meta_GetExtra(p_meta, "REPLAYGAIN_TRACK_GAIN")) ||
-        (psz_value = vlc_meta_GetExtra(p_meta, "RG_RADIO")) )
-    {
-        p_dst->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = true;
-        p_dst->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( psz_value );
-    }
-
-    if( (psz_value = vlc_meta_GetExtra(p_meta, "REPLAYGAIN_TRACK_PEAK" )) ||
-             (psz_value = vlc_meta_GetExtra(p_meta, "RG_PEAK" )) )
-    {
-        p_dst->pb_peak[AUDIO_REPLAY_GAIN_TRACK] = true;
-        p_dst->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( psz_value );
-    }
-
-    if( (psz_value = vlc_meta_GetExtra(p_meta, "REPLAYGAIN_ALBUM_GAIN" )) ||
-             (psz_value = vlc_meta_GetExtra(p_meta, "RG_AUDIOPHILE" )) )
-    {
-        p_dst->pb_gain[AUDIO_REPLAY_GAIN_ALBUM] = true;
-        p_dst->pf_gain[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( psz_value );
-    }
-
-    if( (psz_value = vlc_meta_GetExtra(p_meta, "REPLAYGAIN_ALBUM_PEAK" )) )
-    {
-        p_dst->pb_peak[AUDIO_REPLAY_GAIN_ALBUM] = true;
-        p_dst->pf_peak[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( psz_value );
-    }
-}


=====================================
src/input/replay_gain.c
=====================================
@@ -0,0 +1,199 @@
+/*****************************************************************************
+ * replay_gain.c : common replay gain code
+ *****************************************************************************
+ * Copyright © 2002-2004 VLC authors and VideoLAN
+ * Copyright © 2011-2012 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <math.h>
+
+#include <vlc_replay_gain.h>
+#include <vlc_meta.h>
+#include <vlc_charset.h>
+#include <vlc_variables.h>
+
+int vlc_replay_gain_CopyFromMeta( audio_replay_gain_t *p_dst, const vlc_meta_t *p_meta )
+{
+    if( !p_dst || !p_meta )
+        return VLC_EINVAL;
+
+    /* replay gain presence flags */
+    enum audio_replay_gain_flags {
+        TRACK_GAIN = 0x01,
+        TRACK_PEAK = 0x02,
+        ALBUM_GAIN = 0x04,
+        ALBUM_PEAK = 0x08,
+        FLAGS_MASK = 0x0F,
+        GAINS_MASK = TRACK_GAIN | ALBUM_GAIN,
+    };
+
+    static const struct {
+        int mode;
+        enum audio_replay_gain_flags flag;
+        const char *tags[2];
+    } rg_meta[4] = {
+        { AUDIO_REPLAY_GAIN_TRACK, TRACK_GAIN,
+            { "REPLAYGAIN_TRACK_GAIN", "replaygain_track_gain" }
+        },
+        { AUDIO_REPLAY_GAIN_TRACK, TRACK_PEAK,
+            { "REPLAYGAIN_TRACK_PEAK", "replaygain_track_peak" }
+        },
+        { AUDIO_REPLAY_GAIN_ALBUM, ALBUM_GAIN,
+            { "REPLAYGAIN_ALBUM_GAIN", "replaygain_album_gain" }
+        },
+        { AUDIO_REPLAY_GAIN_ALBUM, ALBUM_PEAK,
+            { "REPLAYGAIN_ALBUM_PEAK", "replaygain_album_peak" }
+        }
+    };
+
+    enum audio_replay_gain_flags found = 0;
+
+    for( size_t i = 0; i < ARRAY_SIZE( rg_meta ) && found != FLAGS_MASK; i++ )
+    {
+        if( found & rg_meta[i].flag )
+            continue;
+
+        for( size_t j = 0; j < ARRAY_SIZE( rg_meta[i].tags ); j++ )
+        {
+            const char *psz_meta = vlc_meta_GetExtra( p_meta, rg_meta[i].tags[j] );
+            if( psz_meta )
+            {
+                float f_value = vlc_strtof_c( psz_meta, NULL );
+
+                if( rg_meta[i].flag & GAINS_MASK )
+                {
+                    p_dst->pb_gain[rg_meta[i].mode] = true;
+                    p_dst->pf_gain[rg_meta[i].mode] = f_value;
+                }
+                else
+                {
+                    p_dst->pb_peak[rg_meta[i].mode] = true;
+                    p_dst->pf_peak[rg_meta[i].mode] = f_value;
+                }
+
+                found |= rg_meta[i].flag;
+                break;
+            }
+        }
+    }
+
+    static const char *rg_loudness[2] = {
+        "REPLAYGAIN_REFERENCE_LOUDNESS",
+        "replaygain_reference_loudness"
+    };
+    /* Only look for reference loudness if a track or album gain was found */
+    for( size_t i = 0; i < ARRAY_SIZE( rg_loudness ) && ( found & GAINS_MASK ); i++ )
+    {
+        const char *psz_meta = vlc_meta_GetExtra( p_meta, rg_loudness[i] );
+        if( psz_meta )
+        {
+            p_dst->pb_reference_loudness = true;
+            p_dst->pf_reference_loudness = vlc_strtof_c( psz_meta, NULL );
+            break;
+        }
+    }
+
+    /* Success if either a track or album gain was found. Peak defaults to 1.0 when absent */
+    return ( found & GAINS_MASK ) ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+float replay_gain_CalcMultiplier( vlc_object_t *p_obj, const audio_replay_gain_t *p_rg )
+{
+    unsigned mode = AUDIO_REPLAY_GAIN_MAX;
+
+    char *psz_mode = var_InheritString( p_obj, "audio-replay-gain-mode" );
+    if( likely(psz_mode != NULL) )
+    {   /* Find selected mode */
+        if (!strcmp (psz_mode, "track"))
+            mode = AUDIO_REPLAY_GAIN_TRACK;
+        else if( !strcmp( psz_mode, "album" ) )
+            mode = AUDIO_REPLAY_GAIN_ALBUM;
+        free( psz_mode );
+    }
+
+    /* Command line / configuration gain */
+    const float config_gain = var_InheritFloat( p_obj, "gain" );
+
+    if( mode == AUDIO_REPLAY_GAIN_MAX )
+        return config_gain;
+
+    const float preamp_gain = var_InheritFloat( p_obj, "audio-replay-gain-preamp" );
+    const float default_gain = var_InheritFloat( p_obj, "audio-replay-gain-default" );
+    const bool peak_protection = var_InheritBool( p_obj, "audio-replay-gain-peak-protection" );
+
+    /* If the selected mode is not available, prefer the other one */
+    if( !p_rg->pb_gain[mode] && p_rg->pb_gain[!mode] )
+        mode = !mode;
+
+    float gain;
+    if( p_rg->pb_gain[mode] )
+    {
+        /* replay gain uses -18 LUFS as the reference level */
+        const float rg_ref_lufs = -18.f;
+        float rg_ref_lufs_delta =   0.f;
+
+        if( p_rg->pb_reference_loudness )
+            rg_ref_lufs_delta = rg_ref_lufs - p_rg->pf_reference_loudness;
+
+        gain = p_rg->pf_gain[mode] + preamp_gain + rg_ref_lufs_delta;
+        msg_Dbg( p_obj, "replay gain: mode %s, gain %.2f dB, pre-amp %.2f dB, reference loudness %.2f LUFS",
+                        mode ? "album" : "track",
+                        p_rg->pf_gain[mode],
+                        preamp_gain,
+                        rg_ref_lufs - rg_ref_lufs_delta );
+    }
+    else
+    {
+        gain = default_gain;
+        msg_Dbg( p_obj, "replay gain: mode default, gain %.2f dB", gain );
+    }
+
+    float multiplier = powf( 10.f, gain / 20.f );
+
+    /* Skip peak protection for default gain case, as the default peak value of 1.0 would limit gains greater than 0 dB */
+    if( p_rg->pb_gain[mode] && peak_protection )
+    {
+        /* Use peak of 1.0 if peak value is missing or invalid */
+        float peak = p_rg->pb_peak[mode] && p_rg->pf_peak[mode] > 0.f ? p_rg->pf_peak[mode] : 1.f;
+        float peak_limit = 1.f / peak;
+
+        /* To avoid clipping, max gain multiplier must be <= 1.0 / peak
+         * e.g.: peak of 0.5 -> max gain +6.02 dB (can double)
+         * e.g.: peak of 1.0 -> max gain  0    dB (unity gain)
+         * e.g.: peak of 1.5 -> max gain -3.52 dB (reduce by third)
+         * e.g.: peak of 2.0 -> max gain -6.02 dB (reduce by half)
+         */
+        if( multiplier > peak_limit )
+        {
+            multiplier = peak_limit;
+            msg_Dbg( p_obj, "replay gain: peak protection reducing gain from %.2f dB to %.2f dB (peak %.6f)",
+                            gain,
+                            20.f * log10f( multiplier ),
+                            peak );
+        }
+    }
+
+    /* apply configuration gain */
+    multiplier *= config_gain;
+    msg_Dbg( p_obj, "replay gain: applying %.2f dB", 20.f * log10f( multiplier ) );
+
+    return multiplier;
+}


=====================================
src/input/test/es_out.c
=====================================
@@ -299,12 +299,6 @@ sout_stream_t *sout_NewInstance(vlc_object_t *p_parent, const char *psz_dest)
     return NULL;
 }
 
-void vlc_audio_replay_gain_MergeFromMeta(audio_replay_gain_t *p_dst,
-                                         const struct vlc_meta_t *p_meta)
-{
-    (void)p_dst; (void)p_meta;
-}
-
 static input_source_t *InputSourceNew(void)
 {
     input_source_t *in = calloc(1, sizeof(*in));


=====================================
src/libvlc-module.c
=====================================
@@ -1617,6 +1617,8 @@ vlc_module_begin ()
                  VOLUME_STEP_LONGTEXT )
         change_float_range( 1., AOUT_VOLUME_DEFAULT )
     add_bool( "volume-save", true, VOLUME_SAVE_TEXT, NULL )
+    add_bool( "audio-time-stretch", true, AUDIO_TIME_STRETCH_TEXT,
+                AUDIO_TIME_STRETCH_LONGTEXT )
 #if defined(__ANDROID__) || defined(__APPLE__) || defined(_WIN32)
     add_bool( "spdif", false, SPDIF_TEXT, SPDIF_LONGTEXT )
 #else
@@ -1633,7 +1635,7 @@ vlc_module_begin ()
                  DESYNC_LONGTEXT )
         change_safe ()
 
-    /* FIXME TODO create a subcat replay gain ? */
+    set_section( N_("Replay gain") , NULL )
     add_string( "audio-replay-gain-mode", ppsz_replay_gain_mode[0], AUDIO_REPLAY_GAIN_MODE_TEXT,
                 AUDIO_REPLAY_GAIN_MODE_LONGTEXT )
         change_string_list( ppsz_replay_gain_mode, ppsz_replay_gain_mode_text )
@@ -1644,9 +1646,6 @@ vlc_module_begin ()
     add_bool( "audio-replay-gain-peak-protection", true,
               AUDIO_REPLAY_GAIN_PEAK_PROTECTION_TEXT, AUDIO_REPLAY_GAIN_PEAK_PROTECTION_LONGTEXT )
 
-    add_bool( "audio-time-stretch", true,
-              AUDIO_TIME_STRETCH_TEXT, AUDIO_TIME_STRETCH_LONGTEXT )
-
     set_subcategory( SUBCAT_AUDIO_AOUT )
     add_module("aout", "audio output", "any", AOUT_TEXT, AOUT_LONGTEXT)
         change_short('A')


=====================================
src/libvlccore.sym
=====================================
@@ -1004,6 +1004,7 @@ vlc_playlist_Pause
 vlc_playlist_Resume
 vlc_playlist_Export
 vlc_playlist_SetMediaStoppedAction
+vlc_replay_gain_CopyFromMeta
 vlc_intf_GetMainPlaylist
 vlc_media_source_Hold
 vlc_media_source_Release


=====================================
src/meson.build
=====================================
@@ -168,6 +168,7 @@ libvlccore_sources_base = files(
     'input/input_interface.h',
     'input/vlm_internal.h',
     'input/vlm_event.h',
+    'input/replay_gain.c',
     'input/resource.h',
     'input/resource.c',
     'input/services_discovery.c',



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/3435039ab4b4687441774d949d0c7fbd5935dac6...f4abe288bb35e04e9540cfe1978d7132b5e23b53

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/3435039ab4b4687441774d949d0c7fbd5935dac6...f4abe288bb35e04e9540cfe1978d7132b5e23b53
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list