/* 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;
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
*/
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;
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) );
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
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)
*****************************************************************************/
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] );
}
}
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;
}
*****************************************************************************/
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;
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 * );
* 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;
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 );
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++;
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 )
{
}
}
- return DecNew( p_this, *pp_aout, p_format );
+ return DecNew( p_this, *pp_aout, p_format, p_replay_gain );
}
/*****************************************************************************
#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 */
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
*****************************************************************************/
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 );
(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;
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;
}
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 );
+}
+
}
}
+ /* 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;
}
}
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" );
#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
****************************************************************************/
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 );