normvol.c 8.37 KB
Newer Older
1
/*****************************************************************************
2
 * normvol.c: volume normalizer
3
 *****************************************************************************
4
 * Copyright (C) 2001, 2006 the VideoLAN team
5
 * $Id$
6
 *
7
 * Authors: Clément Stenac <zorglub@videolan.org>
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 *****************************************************************************/

/*
 * TODO:
 *
 * We should detect fast power increases and react faster to these
 * This way, we can increase the buffer size to get a more stable filter */


/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>                                      /* malloc(), free() */
#include <string.h>

#include <errno.h>                                                 /* ENOMEM */
#include <stdio.h>
#include <ctype.h>
#include <signal.h>

#include <math.h>


#include <vlc/vlc.h>
#include <vlc/aout.h>

#include <aout_internal.h>

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/

static int  Open     ( vlc_object_t * );
static void Close    ( vlc_object_t * );
static void DoWork   ( aout_instance_t * , aout_filter_t *,
                aout_buffer_t * , aout_buffer_t *);

59
typedef struct aout_filter_sys_t
60
{
61
62
63
    int i_nb;
    float *p_last;
    float f_max;
64
} aout_filter_sys_t;
65
66
67
68
69
70
71

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
#define BUFF_TEXT N_("Number of audio buffers" )
#define BUFF_LONGTEXT N_("This is the number of audio buffers on which the " \
                "power measurement is made. A higher number of buffers will " \
72
73
                "increase the response time of the filter to a spike " \
                "but will make it less sensitive to short variations." )
74
75
76

#define LEVEL_TEXT N_("Max level" )
#define LEVEL_LONGTEXT N_("If the average power over the last N buffers " \
zorglub's avatar
zorglub committed
77
               "is higher than this value, the volume will be normalized. " \
78
79
80
81
82
               "This value is a positive floating point number. A value " \
               "between 0.5 and 10 seems sensible." )

vlc_module_begin();
    set_description( _("Volume normalizer") );
83
    set_shortname( _("Volume normalizer") );
zorglub's avatar
zorglub committed
84
85
    set_category( CAT_AUDIO );
    set_subcategory( SUBCAT_AUDIO_AFILTER );
86
87
88
89
90
    add_shortcut( "volnorm" );
    add_integer( "norm-buff-size", 20 ,NULL ,BUFF_TEXT, BUFF_LONGTEXT,
                 VLC_TRUE);
    add_float( "norm-max-level", 2.0, NULL, LEVEL_TEXT,
               LEVEL_LONGTEXT, VLC_TRUE );
91
92
93
94
95
96
97
98
99
100
    set_capability( "audio filter", 0 );
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
 * Open: initialize and create stuff
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    aout_filter_t *p_filter = (aout_filter_t*)p_this;
101
    vlc_bool_t b_fit = VLC_TRUE;
102
    int i_channels;
103
    aout_filter_sys_t *p_sys;
104
105
106
107

    if( p_filter->input.i_format != VLC_FOURCC('f','l','3','2' ) ||
        p_filter->output.i_format != VLC_FOURCC('f','l','3','2') )
    {
108
109
110
        b_fit = VLC_FALSE;
        p_filter->input.i_format = VLC_FOURCC('f','l','3','2');
        p_filter->output.i_format = VLC_FOURCC('f','l','3','2');
111
        msg_Warn( p_filter, "bad input or output format" );
112
113
114
115
    }

    if ( !AOUT_FMTS_SIMILAR( &p_filter->input, &p_filter->output ) )
    {
116
117
118
        b_fit = VLC_FALSE;
        memcpy( &p_filter->output, &p_filter->input,
                sizeof(audio_sample_format_t) );
119
        msg_Warn( p_filter, "input and output formats are not similar" );
120
121
122
123
    }

    if ( ! b_fit )
    {
124
125
126
127
128
129
        return VLC_EGENERIC;
    }

    p_filter->pf_do_work = DoWork;
    p_filter->b_in_place = VLC_TRUE;

130
    i_channels = aout_FormatNbChannels( &p_filter->input );
131

132
    p_sys = p_filter->p_sys = malloc( sizeof( aout_filter_sys_t ) );
133
    p_sys->i_nb = var_CreateGetInteger( p_filter->p_parent, "norm-buff-size" );
134
135
    p_sys->f_max = var_CreateGetFloat( p_filter->p_parent, "norm-max-level" );

136
    if( p_sys->f_max <= 0 ) p_sys->f_max = 0.01;
137
138
139
140

    /* We need to store (nb_buffers+1)*nb_channels floats */
    p_sys->p_last = malloc( sizeof( float ) * (i_channels) *
                            (p_filter->p_sys->i_nb + 2) );
141
142
    memset( p_sys->p_last, 0 ,sizeof( float ) * (i_channels) *
            (p_filter->p_sys->i_nb + 2) );
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    return VLC_SUCCESS;
}

/*****************************************************************************
 * DoWork : normalizes and sends a buffer
 *****************************************************************************/
 static void DoWork( aout_instance_t *p_aout, aout_filter_t *p_filter,
                     aout_buffer_t *p_in_buf, aout_buffer_t *p_out_buf )
{
    float *pf_sum;
    float *pf_gain;
    float f_average = 0;
    int i, i_chan;

    int i_samples = p_in_buf->i_nb_samples;
    int i_channels = aout_FormatNbChannels( &p_filter->input );
    float *p_out = (float*)p_out_buf->p_buffer;
    float *p_in =  (float*)p_in_buf->p_buffer;

    struct aout_filter_sys_t *p_sys = p_filter->p_sys;

    pf_sum = (float *)malloc( sizeof(float) * i_channels );
    memset( pf_sum, 0, sizeof(float) * i_channels );

    pf_gain = (float *)malloc( sizeof(float) * i_channels );

    p_out_buf->i_nb_samples = p_in_buf->i_nb_samples;
    p_out_buf->i_nb_bytes = p_in_buf->i_nb_bytes;

172
173
174
    /* Calculate the average power level on this buffer */
    for( i = 0 ; i < i_samples; i++ )
    {
175
176
177
178
179
180
181
        for( i_chan = 0; i_chan < i_channels; i_chan++ )
        {
            float f_sample = p_in[i_chan];
            float f_square = pow( f_sample, 2 );
            pf_sum[i_chan] += f_square;
        }
        p_in += i_channels;
182
    }
183

184
    /* sum now contains for each channel the sigma(value²) */
185
186
    for( i_chan = 0; i_chan < i_channels; i_chan++ )
    {
187
188
189
190
191
        /* Shift our lastbuff */
        memmove( &p_sys->p_last[ i_chan * p_sys->i_nb],
                        &p_sys->p_last[i_chan * p_sys->i_nb + 1],
                 (p_sys->i_nb-1) * sizeof( float ) );

192
        /* Insert the new average : sqrt(sigma(value²)) */
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
        p_sys->p_last[ i_chan * p_sys->i_nb + p_sys->i_nb - 1] =
                sqrt( pf_sum[i_chan] );

        pf_sum[i_chan] = 0;

        /* Get the average power on the lastbuff */
        f_average = 0;
        for( i = 0; i < p_sys->i_nb ; i++)
        {
            f_average += p_sys->p_last[ i_chan * p_sys->i_nb + i ];
        }
        f_average = f_average / p_sys->i_nb;

        /* Seuil arbitraire */
        p_sys->f_max = var_GetFloat( p_aout, "norm-max-level" );

209
        //fprintf(stderr,"Average %f, max %f\n", f_average, p_sys->f_max );
210
211
212
213
214
215
216
217
218
219
220
        if( f_average > p_sys->f_max )
        {
             pf_gain[i_chan] = f_average / p_sys->f_max;
        }
        else
        {
           pf_gain[i_chan] = 1;
        }
    }

    /* Apply gain */
221
    for( i = 0; i < i_samples; i++)
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    {
        for( i_chan = 0; i_chan < i_channels; i_chan++ )
        {
            p_out[i_chan] /= pf_gain[i_chan];
        }
        p_out += i_channels;
    }

    free( pf_sum );
    free( pf_gain );

    return;
}

/**********************************************************************
 * Close
 **********************************************************************/
static void Close( vlc_object_t *p_this )
{
    aout_filter_t *p_filter = (aout_filter_t*)p_this;
242
    aout_filter_sys_t *p_sys = p_filter->p_sys;
243
244
245

    if( p_sys )
    {
246
        if( p_sys->p_last) free( p_sys->p_last );
247
248
249
        free( p_sys );
    }
}