Commit d20dd242 authored by Laurent Aimar's avatar Laurent Aimar

Added replay gain support (audio-replay-gain-mode track or album to activate

it, disabled by default)
parent ef2723ee
......@@ -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;
......
......@@ -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
......@@ -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)
*****************************************************************************/
......
......@@ -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] );
}
}
......
......@@ -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;
......
......@@ -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 * );
......
......@@ -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 );
}
/*****************************************************************************
......
......@@ -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 );
}
......@@ -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" );
......
......@@ -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 );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment