]> git.sesse.net Git - vlc/commitdiff
Added replay gain support (audio-replay-gain-mode track or album to activate
authorLaurent Aimar <fenrir@videolan.org>
Sun, 17 Jun 2007 13:54:20 +0000 (13:54 +0000)
committerLaurent Aimar <fenrir@videolan.org>
Sun, 17 Jun 2007 13:54:20 +0000 (13:54 +0000)
it, disabled by default)

include/vlc_aout.h
include/vlc_es.h
include/vlc_input.h
include/vlc_meta.h
modules/audio_mixer/float32.c
src/audio_output/aout_internal.h
src/audio_output/dec.c
src/audio_output/input.c
src/input/decoder.c
src/libvlc-module.c

index 06817b4209e7c540e22110a6d1e679c96656f080..b7ac04f8f01a0343b1f92dfa81761db49e53d1a7 100644 (file)
@@ -270,6 +270,8 @@ struct aout_input_t
 
     /* Mixer information */
     byte_t *                p_first_byte_to_mix;
+    audio_replay_gain_t     replay_gain;
+    float                   f_multiplier;
 
     /* If b_restart == 1, the input pipeline will be re-created. */
     vlc_bool_t              b_restart;
index ea81ac0e2dc31cbd359993877da5495a39795660..de43864f5a31562924f1b8beabf1ecd258c38f12 100644 (file)
@@ -44,6 +44,25 @@ struct video_palette_t
     uint8_t palette[256][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 */
+    vlc_bool_t 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 */
+    vlc_bool_t pb_gain[AUDIO_REPLAY_GAIN_MAX];
+    /* gain value in dB */
+    float      pf_gain[AUDIO_REPLAY_GAIN_MAX];
+} audio_replay_gain_t;
+
 /**
  * audio format description
  */
@@ -173,7 +192,8 @@ struct es_format_t
     int             i_extra_languages;
     extra_languages_t *p_extra_languages;
 
-    audio_format_t audio;
+    audio_format_t  audio;
+    audio_replay_gain_t audio_replay_gain;
     video_format_t video;
     subs_format_t  subs;
 
@@ -208,6 +228,7 @@ static inline void es_format_Init( es_format_t *fmt,
     fmt->p_extra_languages      = NULL;
 
     memset( &fmt->audio, 0, sizeof(audio_format_t) );
+    memset( &fmt->audio_replay_gain, 0, sizeof(audio_replay_gain_t) );
     memset( &fmt->video, 0, sizeof(video_format_t) );
     memset( &fmt->subs, 0, sizeof(subs_format_t) );
 
@@ -267,34 +288,30 @@ static inline void es_format_Copy( es_format_t *dst, es_format_t *src )
 static inline void es_format_Clean( es_format_t *fmt )
 {
     if( fmt->psz_language ) free( fmt->psz_language );
-    fmt->psz_language = NULL;
 
     if( fmt->psz_description ) free( fmt->psz_description );
-    fmt->psz_description = NULL;
 
     if( fmt->i_extra > 0 ) free( fmt->p_extra );
-    fmt->i_extra = 0;
-    fmt->p_extra = NULL;
 
     if( fmt->video.p_palette )
         free( fmt->video.p_palette );
-    fmt->video.p_palette = NULL;
 
     if( fmt->subs.psz_encoding ) free( fmt->subs.psz_encoding );
-    fmt->subs.psz_encoding = NULL;
 
-    if( fmt->i_extra_languages && fmt->p_extra_languages ) {
-        int i = 0;
-        while( i < fmt->i_extra_languages ) {
+    if( fmt->i_extra_languages > 0 && fmt->p_extra_languages )
+    {
+        int i;
+        for( i = 0; i < fmt->i_extra_languages; i++ )
+        {
             if( fmt->p_extra_languages[i].psz_language )
                 free( fmt->p_extra_languages[i].psz_language );
             if( fmt->p_extra_languages[i].psz_description )
                 free( fmt->p_extra_languages[i].psz_description );
-            i++;
         }
         free(fmt->p_extra_languages);
     }
-    fmt->i_extra_languages = 0;
-    fmt->p_extra_languages = NULL;
+
+    /* es_format_Clean can be called multiple times */
+    memset( fmt, 0, sizeof(*fmt) );
 }
 #endif
index 01838689e8e59184a6ead457965ad563d067bc5e..6870d521956686ff1a63a317ed08fe89063dd082 100644 (file)
@@ -195,6 +195,47 @@ VLC_EXPORT( input_item_t *, input_ItemNewWithType, ( vlc_object_t *, const char
 
 VLC_EXPORT( input_item_t *, input_ItemGetById, (playlist_t *, int ) );
 
+/*****************************************************************************
+ * Meta data helpers
+ *****************************************************************************/
+static inline void vlc_audio_replay_gain_MergeFromMeta( audio_replay_gain_t *p_dst,
+                                                        const vlc_meta_t *p_meta )
+{
+    int i;
+    if( !p_meta )
+        return;
+
+    for( i = 0; i < p_meta->i_extra; i++ )
+    {
+        const char *psz_name = p_meta->ppsz_extra_name[i];
+        const char *psz_value = p_meta->ppsz_extra_value[i];
+
+        if( !strcasecmp( psz_name, "REPLAYGAIN_TRACK_GAIN" ) ||
+            !strcasecmp( psz_name, "RG_RADIO" ) )
+        {
+            p_dst->pb_gain[AUDIO_REPLAY_GAIN_TRACK] = VLC_TRUE;
+            p_dst->pf_gain[AUDIO_REPLAY_GAIN_TRACK] = atof( psz_value );
+        }
+        else if( !strcasecmp( psz_name, "REPLAYGAIN_TRACK_PEAK" ) ||
+                 !strcasecmp( psz_name, "RG_PEAK" ) )
+        {
+            p_dst->pb_peak[AUDIO_REPLAY_GAIN_TRACK] = VLC_TRUE;
+            p_dst->pf_peak[AUDIO_REPLAY_GAIN_TRACK] = atof( psz_value );
+        }
+        else if( !strcasecmp( psz_name, "REPLAYGAIN_ALBUM_GAIN" ) ||
+                 !strcasecmp( psz_name, "RG_AUDIOPHILE" ) )
+        {
+            p_dst->pb_gain[AUDIO_REPLAY_GAIN_ALBUM] = VLC_TRUE;
+            p_dst->pf_gain[AUDIO_REPLAY_GAIN_ALBUM] = atof( psz_value );
+        }
+        else if( !strcasecmp( psz_name, "REPLAYGAIN_ALBUM_PEAK" ) )
+        {
+            p_dst->pb_peak[AUDIO_REPLAY_GAIN_ALBUM] = VLC_TRUE;
+            p_dst->pf_peak[AUDIO_REPLAY_GAIN_ALBUM] = atof( psz_value );
+        }
+    }
+}
+
 /*****************************************************************************
  * Seek point: (generalisation of chapters)
  *****************************************************************************/
index 49aeb40d28ed90640a62916a1ee0c44ddab71670..28bd4acfcea3fef70f7c8b1ec68a276841f0498d 100644 (file)
@@ -209,15 +209,15 @@ static inline void vlc_meta_Merge( vlc_meta_t *dst, const vlc_meta_t *src )
         int j;
         for( j = 0; j < dst->i_extra; j++ )
         {
-            if( !strcmp( dst->ppsz_extra_name[i], src->ppsz_extra_name[i] ) )
+            if( !strcmp( dst->ppsz_extra_name[j], src->ppsz_extra_name[i] ) )
             {
-                free( dst->ppsz_extra_value[i] );
-                dst->ppsz_extra_value[i] = strdup( src->ppsz_extra_value[i] );
+                free( dst->ppsz_extra_value[j] );
+                dst->ppsz_extra_value[j] = strdup( src->ppsz_extra_value[i] );
                 break;
             }
-            if( j >= dst->i_extra )
-                vlc_meta_AddExtra( dst, src->ppsz_extra_name[i], src->ppsz_extra_value[i] );
         }
+        if( j >= dst->i_extra )
+            vlc_meta_AddExtra( dst, src->ppsz_extra_name[i], src->ppsz_extra_value[i] );
     }
 }
 
index 78e7bbcd1b386939488a5002d6d21112958f513a..33e5315dc48aec0e5a68deb82d5ea7012049d9a6 100644 (file)
@@ -60,14 +60,20 @@ static int Create( vlc_object_t *p_this )
         return -1;
     }
 
+    /* Use the trivial mixer when we can */
     if ( p_aout->i_nb_inputs == 1 && p_aout->mixer.f_multiplier == 1.0 )
     {
-        /* Tell the trivial mixer to go for it. */
-        return -1;
+        int i;
+        for( i = 0; i < p_aout->i_nb_inputs; i++ )
+        {
+            if( p_aout->pp_inputs[i]->f_multiplier != 1.0 )
+                break;
+        }
+        if( i >= p_aout->i_nb_inputs )
+            return -1;
     }
 
     p_aout->mixer.pf_do_work = DoWork;
-
     return 0;
 }
 
@@ -109,15 +115,17 @@ static void MeanWords( float * p_out, const float * p_in, size_t i_nb_words,
  *****************************************************************************/
 static void DoWork( aout_instance_t * p_aout, aout_buffer_t * p_buffer )
 {
-    int i_nb_inputs = p_aout->i_nb_inputs;
-    float f_multiplier = p_aout->mixer.f_multiplier;
+    const int i_nb_inputs = p_aout->i_nb_inputs;
+    const float f_multiplier_global = p_aout->mixer.f_multiplier;
+    const int i_nb_channels = aout_FormatNbChannels( &p_aout->mixer.mixer );
     int i_input;
-    int i_nb_channels = aout_FormatNbChannels( &p_aout->mixer.mixer );
 
     for ( i_input = 0; i_input < i_nb_inputs; i_input++ )
     {
         int i_nb_words = p_buffer->i_nb_samples * i_nb_channels;
         aout_input_t * p_input = p_aout->pp_inputs[i_input];
+        float f_multiplier = f_multiplier_global * p_input->f_multiplier;
+
         float * p_out = (float *)p_buffer->p_buffer;
         float * p_in = (float *)p_input->p_first_byte_to_mix;
 
index 523f172ed28b8ceae129be2d935e35641cddba75..2f5244e487b9c2aabbcc75bc8ae2ea59991ee859 100644 (file)
@@ -124,8 +124,8 @@ int aout_VolumeNoneSet( aout_instance_t *, audio_volume_t );
 int aout_VolumeNoneInfos( aout_instance_t *, audio_volume_t * );
 
 /* From dec.c */
-#define aout_DecNew(a, b, c) __aout_DecNew(VLC_OBJECT(a), b, c)
-aout_input_t * __aout_DecNew( vlc_object_t *, aout_instance_t **, audio_sample_format_t * );
+#define aout_DecNew(a, b, c, d) __aout_DecNew(VLC_OBJECT(a), b, c, d)
+aout_input_t * __aout_DecNew( vlc_object_t *, aout_instance_t **, audio_sample_format_t *, audio_replay_gain_t * );
 int aout_DecDelete ( aout_instance_t *, aout_input_t * );
 aout_buffer_t * aout_DecNewBuffer( aout_instance_t *, aout_input_t *, size_t );
 void aout_DecDeleteBuffer( aout_instance_t *, aout_input_t *, aout_buffer_t * );
index 7057493a9827243bd1b40c5332e342de66407ad5..5ba82c316406327bac5c755e42e4c6cd27406da0 100644 (file)
@@ -45,7 +45,8 @@
  * aout_DecNew : create a decoder
  *****************************************************************************/
 static aout_input_t * DecNew( vlc_object_t * p_this, aout_instance_t * p_aout,
-                              audio_sample_format_t * p_format )
+                              audio_sample_format_t *p_format,
+                              audio_replay_gain_t *p_replay_gain )
 {
     aout_input_t * p_input;
     input_thread_t * p_input_thread;
@@ -82,6 +83,7 @@ static aout_input_t * DecNew( vlc_object_t * p_this, aout_instance_t * p_aout,
         msg_Err( p_aout, "out of memory" );
         goto error;
     }
+    memset( p_input, 0, sizeof(aout_input_t) );
 
     vlc_mutex_init( p_aout, &p_input->lock );
 
@@ -90,6 +92,8 @@ static aout_input_t * DecNew( vlc_object_t * p_this, aout_instance_t * p_aout,
     aout_FormatPrepare( p_format );
     memcpy( &p_input->input, p_format,
             sizeof(audio_sample_format_t) );
+    if( p_replay_gain )
+        p_input->replay_gain = *p_replay_gain;
 
     p_aout->pp_inputs[p_aout->i_nb_inputs] = p_input;
     p_aout->i_nb_inputs++;
@@ -166,7 +170,8 @@ error:
 
 aout_input_t * __aout_DecNew( vlc_object_t * p_this,
                               aout_instance_t ** pp_aout,
-                              audio_sample_format_t * p_format )
+                              audio_sample_format_t * p_format,
+                              audio_replay_gain_t *p_replay_gain )
 {
     if ( *pp_aout == NULL )
     {
@@ -191,7 +196,7 @@ aout_input_t * __aout_DecNew( vlc_object_t * p_this,
         }
     }
 
-    return DecNew( p_this, *pp_aout, p_format );
+    return DecNew( p_this, *pp_aout, p_format, p_replay_gain );
 }
 
 /*****************************************************************************
index 7dccea0ca49b769466ac77805fe0e0f8b5a3aa9c..5a3689dd594e0446f4ec1472f73eb385d4cfa9fe 100644 (file)
@@ -30,6 +30,7 @@
 #include <stdio.h>
 #include <stdlib.h>                            /* calloc(), malloc(), free() */
 #include <string.h>
+#include <math.h>
 
 #include <vlc_input.h>                 /* for input_thread_t and i_pts_delay */
 
@@ -51,7 +52,9 @@ static int VisualizationCallback( vlc_object_t *, char const *,
                                   vlc_value_t, vlc_value_t, void * );
 static int EqualizerCallback( vlc_object_t *, char const *,
                               vlc_value_t, vlc_value_t, void * );
-
+static int ReplayGainCallback( vlc_object_t *, char const *,
+                               vlc_value_t, vlc_value_t, void * );
+static void ReplayGainSelect( aout_instance_t *, aout_input_t * );
 /*****************************************************************************
  * aout_InputNew : allocate a new input and rework the filter pipeline
  *****************************************************************************/
@@ -162,6 +165,47 @@ int aout_InputNew( aout_instance_t * p_aout, aout_input_t * p_input )
         var_Change( p_aout, "audio-visual", VLC_VAR_SETTEXT, &text, NULL );
     }
 
+    if( var_Type( p_aout, "audio-replay-gain-mode" ) == 0 )
+    {
+        module_config_t *p_config;
+        int i;
+
+        p_config = config_FindConfig( VLC_OBJECT(p_aout), "audio-replay-gain-mode" );
+        if( p_config && p_config->i_list )
+        {
+            var_Create( p_aout, "audio-replay-gain-mode",
+                        VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+
+            text.psz_string = _("Replay gain");
+            var_Change( p_aout, "audio-replay-gain-mode", VLC_VAR_SETTEXT, &text, NULL );
+
+            for( i = 0; i < p_config->i_list; i++ )
+            {
+                val.psz_string = (char *)p_config->ppsz_list[i];
+                text.psz_string = (char *)p_config->ppsz_list_text[i];
+                var_Change( p_aout, "audio-replay-gain-mode", VLC_VAR_ADDCHOICE,
+                            &val, &text );
+            }
+
+            var_AddCallback( p_aout, "audio-replay-gain-mode", ReplayGainCallback, NULL );
+        }
+    }
+    if( var_Type( p_aout, "audio-replay-gain-preamp" ) == 0 )
+    {
+        var_Create( p_aout, "audio-replay-gain-preamp",
+                    VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
+    }
+    if( var_Type( p_aout, "audio-replay-gain-default" ) == 0 )
+    {
+        var_Create( p_aout, "audio-replay-gain-default",
+                    VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
+    }
+    if( var_Type( p_aout, "audio-replay-gain-peak-protection" ) == 0 )
+    {
+        var_Create( p_aout, "audio-replay-gain-peak-protection",
+                    VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
+    }
+
     var_Get( p_aout, "audio-filter", &val );
     psz_filters = val.psz_string;
     var_Get( p_aout, "audio-visual", &val );
@@ -366,6 +410,9 @@ int aout_InputNew( aout_instance_t * p_aout, aout_input_t * p_input )
                                     (int)(p_input->input.i_bytes_per_frame
                                      * p_input->input.i_rate
                                      / p_input->input.i_frame_length) );
+
+    ReplayGainSelect( p_aout, p_input );
+
     /* Success */
     p_input->b_error = VLC_FALSE;
     p_input->b_restart = VLC_FALSE;
@@ -658,6 +705,11 @@ static void inputFailure( aout_instance_t * p_aout, aout_input_t * p_input,
     var_Destroy( p_aout, "audio-filter" );
     var_Destroy( p_aout, "audio-visual" );
 
+    var_Destroy( p_aout, "audio-replay-gain-mode" );
+    var_Destroy( p_aout, "audio-replay-gain-default" );
+    var_Destroy( p_aout, "audio-replay-gain-preamp" );
+    var_Destroy( p_aout, "audio-replay-gain-peak-protection" );
+
     /* error flag */
     p_input->b_error = 1;
 }
@@ -817,3 +869,69 @@ static int EqualizerCallback( vlc_object_t *p_this, char const *psz_cmd,
 
     return VLC_SUCCESS;
 }
+
+static int ReplayGainCallback( vlc_object_t *p_this, char const *psz_cmd,
+                               vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    aout_instance_t *p_aout = (aout_instance_t *)p_this;
+    int i;
+
+    vlc_mutex_lock( &p_aout->mixer_lock );
+    for( i = 0; i < p_aout->i_nb_inputs; i++ )
+        ReplayGainSelect( p_aout, p_aout->pp_inputs[i] );
+    vlc_mutex_unlock( &p_aout->mixer_lock );
+
+    return VLC_SUCCESS;
+}
+
+static void ReplayGainSelect( aout_instance_t *p_aout, aout_input_t *p_input )
+{
+    char *psz_replay_gain = var_GetString( p_aout, "audio-replay-gain-mode" );
+    int i_mode;
+    int i_use;
+    float f_gain;
+
+    p_input->f_multiplier = 1.0;
+
+    if( !psz_replay_gain )
+        return;
+
+    /* Find select mode */
+    if( !strcmp( psz_replay_gain, "track" ) )
+        i_mode = AUDIO_REPLAY_GAIN_TRACK;
+    else if( !strcmp( psz_replay_gain, "album" ) )
+        i_mode = AUDIO_REPLAY_GAIN_ALBUM;
+    else
+        i_mode = AUDIO_REPLAY_GAIN_MAX;
+
+    /* If the select mode is not available, prefer the other one */
+    i_use = i_mode;
+    if( i_use != AUDIO_REPLAY_GAIN_MAX && !p_input->replay_gain.pb_gain[i_use] )
+    {
+        for( i_use = 0; i_use < AUDIO_REPLAY_GAIN_MAX; i_use++ )
+        {
+            if( p_input->replay_gain.pb_gain[i_use] )
+                break;
+        }
+    }
+
+    /* */
+    if( i_use != AUDIO_REPLAY_GAIN_MAX )
+        f_gain = p_input->replay_gain.pf_gain[i_use] + var_GetFloat( p_aout, "audio-replay-gain-preamp" );
+    else if( i_mode != AUDIO_REPLAY_GAIN_MAX )
+        f_gain = var_GetFloat( p_aout, "audio-replay-gain-default" );
+    else
+        f_gain = 0.0;
+    p_input->f_multiplier = pow( 10.0, f_gain / 20.0 );
+
+    /* */
+    if( p_input->replay_gain.pb_peak[i_use] &&
+        var_GetBool( p_aout, "audio-replay-gain-peak-protection" ) &&
+        p_input->replay_gain.pf_peak[i_use] * p_input->f_multiplier > 1.0 )
+    {
+        p_input->f_multiplier = 1.0f / p_input->replay_gain.pf_peak[i_use];
+    }
+
+    free( psz_replay_gain );
+}
+
index 6f1b76ae05f14a9d29f61165763f529eb571e3df..1b5facd8b6660f5bbc45f2dee21b5e52410988b8 100644 (file)
@@ -415,6 +415,24 @@ static decoder_t * CreateDecoder( input_thread_t *p_input,
         }
     }
 
+    /* Copy ourself the input replay gain */
+    if( fmt->i_cat == AUDIO_ES )
+    {
+        int i;
+        for( 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];
+            }
+        }
+    }
     return p_dec;
 }
 
@@ -886,7 +904,7 @@ static aout_buffer_t *aout_new_buffer( decoder_t *p_dec, int i_samples )
         }
 
         p_sys->p_aout_input =
-            aout_DecNew( p_dec, &p_sys->p_aout, &format );
+            aout_DecNew( p_dec, &p_sys->p_aout, &format, &p_dec->fmt_out.audio_replay_gain );
         if( p_sys->p_aout_input == NULL )
         {
             msg_Err( p_dec, "failed to create audio output" );
index d0aae160fc8df251b458e8ad6c434911d5242527..a13fa34dbbb30099fd68baee22a6a3b9cda12475 100644 (file)
@@ -220,6 +220,28 @@ static const char *ppsz_force_dolby_descriptions[] = { N_("Auto"), N_("On"), N_(
 #define AUDIO_VISUAL_LONGTEXT N_( \
     "This adds visualization modules (spectrum analyzer, etc.).")
 
+
+#define AUDIO_REPLAY_GAIN_MODE_TEXT N_( \
+    "Replay gain mode" )
+#define AUDIO_REPLAY_GAIN_MODE_LONGTEXT N_( \
+    "Select the replay gain mode" )
+#define AUDIO_REPLAY_GAIN_PREAMP_TEXT N_( \
+    "Replay preamp" )
+#define AUDIO_REPLAY_GAIN_PREAMP_LONGTEXT N_( \
+    "This allows you to change the default target level (89 dB) " \
+    "for stream with replay gain information" )
+#define AUDIO_REPLAY_GAIN_DEFAULT_TEXT N_( \
+    "Default replay gain" )
+#define AUDIO_REPLAY_GAIN_DEFAULT_LONGTEXT N_( \
+    "This is the gain used for stream without replay gain information" )
+#define AUDIO_REPLAY_GAIN_PEAK_PROTECTION_TEXT N_( \
+    "Peak protection" )
+#define AUDIO_REPLAY_GAIN_PEAK_PROTECTION_LONGTEXT N_( \
+    "Protect against sound clipping" )
+
+static const char *ppsz_replay_gain_mode[] = { "none", "track", "album" };
+static const char *ppsz_replay_gain_mode_text[] = { N_("None"), N_("Track"), N_("Album") };
+
 /*****************************************************************************
  * Video
  ****************************************************************************/
@@ -1292,6 +1314,18 @@ vlc_module_begin();
         change_integer_list( pi_force_dolby_values, ppsz_force_dolby_descriptions, 0 );
     add_integer( "audio-desync", 0, NULL, DESYNC_TEXT,
                  DESYNC_LONGTEXT, VLC_TRUE );
+
+    /* FIXME TODO create a subcat replay gain ? */
+    add_string( "audio-replay-gain-mode", ppsz_replay_gain_mode[0], NULL, AUDIO_REPLAY_GAIN_MODE_TEXT,
+                AUDIO_REPLAY_GAIN_MODE_LONGTEXT, VLC_FALSE );
+        change_string_list( ppsz_replay_gain_mode, ppsz_replay_gain_mode_text, 0 );
+    add_float( "audio-replay-gain-preamp", 0.0, NULL,
+               AUDIO_REPLAY_GAIN_PREAMP_TEXT, AUDIO_REPLAY_GAIN_PREAMP_LONGTEXT, VLC_FALSE );
+    add_float( "audio-replay-gain-default", -7.0, NULL,
+               AUDIO_REPLAY_GAIN_DEFAULT_TEXT, AUDIO_REPLAY_GAIN_DEFAULT_LONGTEXT, VLC_FALSE );
+    add_bool( "audio-replay-gain-peak-protection", VLC_TRUE, NULL,
+              AUDIO_REPLAY_GAIN_PEAK_PROTECTION_TEXT, AUDIO_REPLAY_GAIN_PEAK_PROTECTION_LONGTEXT, VLC_TRUE );
+
     set_subcategory( SUBCAT_AUDIO_AOUT );
     add_module( "aout", "audio output", NULL, NULL, AOUT_TEXT, AOUT_LONGTEXT,
                 VLC_TRUE );