file.c 12.3 KB
Newer Older
1 2 3
/*****************************************************************************
 * file.c : audio output which writes the samples to a file
 *****************************************************************************
4
 * Copyright (C) 2002 the VideoLAN team
5
 * $Id$
6 7
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
Gildas Bazin's avatar
 
Gildas Bazin committed
8
 *          Gildas Bazin <gbazin@netcourrier.com>
9 10 11 12 13
 *
 * 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.
Sam Hocevar's avatar
Sam Hocevar committed
14
 *
15 16 17 18 19 20 21
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33 34
#include <errno.h>

35
#include <vlc_common.h>
36
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
37 38
#include <vlc_aout.h>
#include <vlc_codecs.h> /* WAVEHEADER */
Christophe Mutricy's avatar
Christophe Mutricy committed
39
#include <vlc_charset.h>
40

41
#define FRAME_SIZE 2048
42
#define A52_FRAME_NB 1536
43

Gildas Bazin's avatar
 
Gildas Bazin committed
44 45 46 47 48 49 50 51 52
/*****************************************************************************
 * aout_sys_t: audio output method descriptor
 *****************************************************************************
 * This structure is part of the audio output thread descriptor.
 * It describes the direct sound specific properties of an audio device.
 *****************************************************************************/
struct aout_sys_t
{
    FILE     * p_file;
53
    bool b_add_wav_header;
Gildas Bazin's avatar
 
Gildas Bazin committed
54 55 56 57

    WAVEHEADER waveh;                      /* Wave header of the output file */
};

Gildas Bazin's avatar
 
Gildas Bazin committed
58
#define CHANNELS_MAX 6
59
static const int pi_channels_maps[CHANNELS_MAX+1] =
Gildas Bazin's avatar
 
Gildas Bazin committed
60 61 62 63 64 65 66 67 68 69 70 71 72
{
    0,
    AOUT_CHAN_CENTER,
    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
    AOUT_CHAN_CENTER | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARLEFT
     | AOUT_CHAN_REARRIGHT,
    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
     | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT,
    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
     | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE
};

73 74 75 76 77
/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
static int     Open        ( vlc_object_t * );
static void    Close       ( vlc_object_t * );
78
static void    Play        ( aout_instance_t * );
79 80 81 82

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Gildas Bazin's avatar
 
Gildas Bazin committed
83
#define FORMAT_TEXT N_("Output format")
Christophe Massiot's avatar
Christophe Massiot committed
84
#define FORMAT_LONGTEXT N_("One of \"u8\", \"s8\", \"u16\", \"s16\", " \
Gildas Bazin's avatar
 
Gildas Bazin committed
85 86
    "\"u16_le\", \"s16_le\", \"u16_be\", \"s16_be\", \"fixed32\", " \
    "\"float32\" or \"spdif\"")
87
#define CHANNELS_TEXT N_("Number of output channels")
Gildas Bazin's avatar
 
Gildas Bazin committed
88 89 90
#define CHANNELS_LONGTEXT N_("By default, all the channels of the incoming " \
    "will be saved but you can restrict the number of channels here.")

91
#define WAV_TEXT N_("Add WAVE header")
92
#define WAV_LONGTEXT N_("Instead of writing a raw file, you can add a WAV " \
93
                        "header to the file.")
94

95
static const char *const format_list[] = { "u8", "s8", "u16", "s16", "u16_le",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
96 97
                                     "s16_le", "u16_be", "s16_be", "fixed32",
                                     "float32", "spdif" };
98 99 100 101 102 103 104 105 106 107
static const int format_int[] = { VLC_FOURCC('u','8',' ',' '),
                                  VLC_FOURCC('s','8',' ',' '),
                                  AOUT_FMT_U16_NE, AOUT_FMT_S16_NE,
                                  VLC_FOURCC('u','1','6','l'),
                                  VLC_FOURCC('s','1','6','l'),
                                  VLC_FOURCC('u','1','6','b'),
                                  VLC_FOURCC('s','1','6','b'),
                                  VLC_FOURCC('f','i','3','2'),
                                  VLC_FOURCC('f','l','3','2'),
                                  VLC_FOURCC('s','p','i','f') };
108

Gildas Bazin's avatar
 
Gildas Bazin committed
109
#define FILE_TEXT N_("Output file")
110
#define FILE_LONGTEXT N_("File to which the audio samples will be written to. (\"-\" for stdout")
111 112

vlc_module_begin();
113 114
    set_description( N_("File audio output") );
    set_shortname( N_("File") );
115 116
    set_category( CAT_AUDIO );
    set_subcategory( SUBCAT_AUDIO_AOUT );
Gildas Bazin's avatar
 
Gildas Bazin committed
117

118
    add_string( "audiofile-format", "s16", NULL,
119
                FORMAT_TEXT, FORMAT_LONGTEXT, true );
120
        change_string_list( format_list, 0, 0 );
Gildas Bazin's avatar
 
Gildas Bazin committed
121
    add_integer( "audiofile-channels", 0, NULL,
122
                 CHANNELS_TEXT, CHANNELS_LONGTEXT, true );
Gildas Bazin's avatar
 
Gildas Bazin committed
123
    add_file( "audiofile-file", "audiofile.wav", NULL, FILE_TEXT,
124 125
              FILE_LONGTEXT, false );
    add_bool( "audiofile-wav", 1, NULL, WAV_TEXT, WAV_LONGTEXT, true );
Gildas Bazin's avatar
 
Gildas Bazin committed
126

127 128
    set_capability( "audio output", 0 );
    add_shortcut( "file" );
Gildas Bazin's avatar
 
Gildas Bazin committed
129
    add_shortcut( "audiofile" );
130 131 132 133 134 135 136 137 138
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
 * Open: open a dummy audio device
 *****************************************************************************/
static int Open( vlc_object_t * p_this )
{
    aout_instance_t * p_aout = (aout_instance_t *)p_this;
Gildas Bazin's avatar
 
Gildas Bazin committed
139
    char * psz_name, * psz_format;
140
    const char * const * ppsz_compare = format_list;
Gildas Bazin's avatar
 
Gildas Bazin committed
141 142 143 144 145 146 147 148 149
    vlc_value_t val;
    int i_channels, i = 0;

    var_Create( p_this, "audiofile-file", VLC_VAR_STRING|VLC_VAR_DOINHERIT );
    var_Get( p_this, "audiofile-file", &val );
    psz_name = val.psz_string;
    if( !psz_name || !*psz_name )
    {
        msg_Err( p_aout, "you need to specify an output file name" );
150
        free( psz_name );
Gildas Bazin's avatar
 
Gildas Bazin committed
151 152
        return VLC_EGENERIC;
    }
Christophe Massiot's avatar
Christophe Massiot committed
153

Gildas Bazin's avatar
 
Gildas Bazin committed
154
    /* Allocate structure */
Gildas Bazin's avatar
 
Gildas Bazin committed
155 156
    p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
    if( p_aout->output.p_sys == NULL )
Rémi Duraffort's avatar
Rémi Duraffort committed
157
        return VLC_ENOMEM;
158

159 160 161 162 163
    if( !strcmp( psz_name, "-" ) )
        p_aout->output.p_sys->p_file = stdout;
    else
        p_aout->output.p_sys->p_file = utf8_fopen( psz_name, "wb" );

164
    free( psz_name );
Gildas Bazin's avatar
 
Gildas Bazin committed
165 166 167
    if ( p_aout->output.p_sys->p_file == NULL )
    {
        free( p_aout->output.p_sys );
Gildas Bazin's avatar
 
Gildas Bazin committed
168
        return VLC_EGENERIC;
Gildas Bazin's avatar
 
Gildas Bazin committed
169
    }
170 171 172

    p_aout->output.pf_play = Play;

Gildas Bazin's avatar
 
Gildas Bazin committed
173 174 175 176 177
    /* Audio format */
    var_Create( p_this, "audiofile-format", VLC_VAR_STRING|VLC_VAR_DOINHERIT );
    var_Get( p_this, "audiofile-format", &val );
    psz_format = val.psz_string;

178 179 180 181 182 183 184 185 186 187 188
    while ( *ppsz_compare != NULL )
    {
        if ( !strncmp( *ppsz_compare, psz_format, strlen(*ppsz_compare) ) )
        {
            break;
        }
        ppsz_compare++; i++;
    }

    if ( *ppsz_compare == NULL )
    {
189
        msg_Err( p_aout, "cannot understand the format string (%s)",
190
                 psz_format );
191 192
        if( p_aout->output.p_sys->p_file != stdout )
            fclose( p_aout->output.p_sys->p_file );
Gildas Bazin's avatar
 
Gildas Bazin committed
193
        free( p_aout->output.p_sys );
Rémi Duraffort's avatar
Rémi Duraffort committed
194
        free( psz_format );
Gildas Bazin's avatar
 
Gildas Bazin committed
195
        return VLC_EGENERIC;
196
    }
Rémi Duraffort's avatar
Rémi Duraffort committed
197
    free( psz_format );
198 199

    p_aout->output.output.i_format = format_int[i];
200
    if ( AOUT_FMT_NON_LINEAR( &p_aout->output.output ) )
201 202
    {
        p_aout->output.i_nb_samples = A52_FRAME_NB;
203 204
        p_aout->output.output.i_bytes_per_frame = AOUT_SPDIF_SIZE;
        p_aout->output.output.i_frame_length = A52_FRAME_NB;
205
        aout_VolumeNoneInit( p_aout );
206 207 208 209
    }
    else
    {
        p_aout->output.i_nb_samples = FRAME_SIZE;
210
        aout_VolumeSoftInit( p_aout );
211
    }
Gildas Bazin's avatar
 
Gildas Bazin committed
212

Gildas Bazin's avatar
 
Gildas Bazin committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    /* Channels number */
    var_Create( p_this, "audiofile-channels",
                VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
    var_Get( p_this, "audiofile-channels", &val );
    i_channels = val.i_int;

    if( i_channels > 0 && i_channels <= CHANNELS_MAX )
    {
        p_aout->output.output.i_physical_channels =
            pi_channels_maps[i_channels];
    }

    /* WAV header */
    var_Create( p_this, "audiofile-wav", VLC_VAR_BOOL|VLC_VAR_DOINHERIT );
    var_Get( p_this, "audiofile-wav", &val );
    p_aout->output.p_sys->b_add_wav_header = val.b_bool;
Gildas Bazin's avatar
 
Gildas Bazin committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267

    if( p_aout->output.p_sys->b_add_wav_header )
    {
        /* Write wave header */
        WAVEHEADER *wh = &p_aout->output.p_sys->waveh;

        memset( wh, 0, sizeof(wh) );

        switch( p_aout->output.output.i_format )
        {
        case VLC_FOURCC('f','l','3','2'):
            wh->Format     = WAVE_FORMAT_IEEE_FLOAT;
            wh->BitsPerSample = sizeof(float) * 8;
            break;
        case VLC_FOURCC('u','8',' ',' '):
            wh->Format     = WAVE_FORMAT_PCM;
            wh->BitsPerSample = 8;
            break;
        case VLC_FOURCC('s','1','6','l'):
        default:
            wh->Format     = WAVE_FORMAT_PCM;
            wh->BitsPerSample = 16;
            break;
        }

        wh->MainChunkID = VLC_FOURCC('R', 'I', 'F', 'F');
        wh->Length = 0;                    /* temp, to be filled in as we go */
        wh->ChunkTypeID = VLC_FOURCC('W', 'A', 'V', 'E');
        wh->SubChunkID = VLC_FOURCC('f', 'm', 't', ' ');
        wh->SubChunkLength = 16;

        wh->Modus = aout_FormatNbChannels( &p_aout->output.output );
        wh->SampleFreq = p_aout->output.output.i_rate;
        wh->BytesPerSample = wh->Modus * ( wh->BitsPerSample / 8 );
        wh->BytesPerSec = wh->BytesPerSample * wh->SampleFreq;

        wh->DataChunkID = VLC_FOURCC('d', 'a', 't', 'a');
        wh->DataLength = 0;                /* temp, to be filled in as we go */

Gildas Bazin's avatar
 
Gildas Bazin committed
268 269 270 271 272 273 274 275 276
        /* Header -> little endian format */
        SetWLE( &wh->Format, wh->Format );
        SetWLE( &wh->BitsPerSample, wh->BitsPerSample );
        SetDWLE( &wh->SubChunkLength, wh->SubChunkLength );
        SetWLE( &wh->Modus, wh->Modus );
        SetDWLE( &wh->SampleFreq, wh->SampleFreq );
        SetWLE( &wh->BytesPerSample, wh->BytesPerSample );
        SetDWLE( &wh->BytesPerSec, wh->BytesPerSec );

Gildas Bazin's avatar
 
Gildas Bazin committed
277 278 279
        if( fwrite( wh, sizeof(WAVEHEADER), 1,
                    p_aout->output.p_sys->p_file ) != 1 )
        {
280
            msg_Err( p_aout, "write error (%m)" );
Gildas Bazin's avatar
 
Gildas Bazin committed
281 282 283
        }
    }

284 285 286
    return 0;
}

Christophe Massiot's avatar
Christophe Massiot committed
287 288 289 290 291 292 293
/*****************************************************************************
 * Close: close our file
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
    aout_instance_t * p_aout = (aout_instance_t *)p_this;

Gildas Bazin's avatar
 
Gildas Bazin committed
294 295 296 297 298 299 300 301 302 303 304
    msg_Dbg( p_aout, "closing audio file" );

    if( p_aout->output.p_sys->b_add_wav_header )
    {
        /* Update Wave Header */
        p_aout->output.p_sys->waveh.Length =
            p_aout->output.p_sys->waveh.DataLength + sizeof(WAVEHEADER) - 4;

        /* Write Wave Header */
        if( fseek( p_aout->output.p_sys->p_file, 0, SEEK_SET ) )
        {
305
            msg_Err( p_aout, "seek error (%m)" );
Gildas Bazin's avatar
 
Gildas Bazin committed
306
        }
Gildas Bazin's avatar
 
Gildas Bazin committed
307 308 309 310 311 312 313

        /* Header -> little endian format */
        SetDWLE( &p_aout->output.p_sys->waveh.Length,
                 p_aout->output.p_sys->waveh.Length );
        SetDWLE( &p_aout->output.p_sys->waveh.DataLength,
                 p_aout->output.p_sys->waveh.DataLength );

Gildas Bazin's avatar
 
Gildas Bazin committed
314 315 316
        if( fwrite( &p_aout->output.p_sys->waveh, sizeof(WAVEHEADER), 1,
                    p_aout->output.p_sys->p_file ) != 1 )
        {
317
            msg_Err( p_aout, "write error (%m)" );
Gildas Bazin's avatar
 
Gildas Bazin committed
318 319 320
        }
    }

321 322
    if( p_aout->output.p_sys->p_file != stdout )
        fclose( p_aout->output.p_sys->p_file );
Gildas Bazin's avatar
 
Gildas Bazin committed
323
    free( p_aout->output.p_sys );
Christophe Massiot's avatar
Christophe Massiot committed
324 325
}

326 327 328
/*****************************************************************************
 * Play: pretend to play a sound
 *****************************************************************************/
329
static void Play( aout_instance_t * p_aout )
330
{
331 332 333 334
    aout_buffer_t * p_buffer;

    p_buffer = aout_FifoPop( p_aout, &p_aout->output.fifo );

335
    if( fwrite( p_buffer->p_buffer, p_buffer->i_nb_bytes, 1,
Gildas Bazin's avatar
 
Gildas Bazin committed
336
                p_aout->output.p_sys->p_file ) != 1 )
337
    {
338
        msg_Err( p_aout, "write error (%m)" );
339 340
    }

Gildas Bazin's avatar
 
Gildas Bazin committed
341 342 343 344 345 346
    if( p_aout->output.p_sys->b_add_wav_header )
    {
        /* Update Wave Header */
        p_aout->output.p_sys->waveh.DataLength += p_buffer->i_nb_bytes;
    }

347 348
    aout_BufferFree( p_buffer );
}