ogg.c 32.1 KB
Newer Older
1
/*****************************************************************************
gbazin's avatar
   
gbazin committed
2
 * ogg.c: ogg muxer module for vlc
3
4
 *****************************************************************************
 * Copyright (C) 2001, 2002 VideoLAN
5
 * $Id: ogg.c,v 1.27 2003/12/14 18:44:50 gbazin Exp $
6
7
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
gbazin's avatar
   
gbazin committed
8
 *          Gildas Bazin <gbazin@netcourrier.com>
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 *
 * 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
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>
#include <string.h>
gbazin's avatar
   
gbazin committed
30
31
32
33

#ifdef HAVE_TIME_H
#   include <time.h>
#endif
34
35
36
37
38
39
40
41
42
43
44
45

#include <vlc/vlc.h>
#include <vlc/input.h>
#include <vlc/sout.h>

#include "codecs.h"

#include <ogg/ogg.h>

/*****************************************************************************
 * Exported prototypes
 *****************************************************************************/
gbazin's avatar
   
gbazin committed
46
47
static int  Open   ( vlc_object_t * );
static void Close  ( vlc_object_t * );
48

49
50
51
52
static int Capability(sout_mux_t *, int, void *, void * );
static int AddStream( sout_mux_t *, sout_input_t * );
static int DelStream( sout_mux_t *, sout_input_t * );
static int Mux      ( sout_mux_t * );
53

gbazin's avatar
   
gbazin committed
54
55
56
static sout_buffer_t *OggCreateHeader( sout_mux_t *, mtime_t );
static sout_buffer_t *OggCreateFooter( sout_mux_t *, mtime_t );

57
58
59
60
61
62
63
64
65
66
67
68
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
vlc_module_begin();
    set_description( _("Ogg/ogm muxer") );
    set_capability( "sout mux", 10 );
    add_shortcut( "ogg" );
    add_shortcut( "ogm" );
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
gbazin's avatar
   
gbazin committed
69
 * Misc declarations
70
71
72
 *****************************************************************************/
#define FREE( p ) if( p ) { free( p ); (p) = NULL; }

gbazin's avatar
   
gbazin committed
73
74
/* Structures used for OggDS headers used in ogm files */

75
76
#define PACKET_TYPE_HEADER   0x01
#define PACKET_TYPE_COMMENT  0x03
gbazin's avatar
   
gbazin committed
77
#define PACKET_IS_SYNCPOINT  0x08
78

79
80
81
82
typedef struct
#ifdef HAVE_ATTRIBUTE_PACKED
    __attribute__((__packed__))
#endif
83
84
85
{
    int32_t i_width;
    int32_t i_height;
gbazin's avatar
   
gbazin committed
86
} oggds_header_video_t;
87

88
89
90
91
typedef struct
#ifdef HAVE_ATTRIBUTE_PACKED
    __attribute__((__packed__))
#endif
92
93
94
95
{
    int16_t i_channels;
    int16_t i_block_align;
    int32_t i_avgbytespersec;
gbazin's avatar
   
gbazin committed
96
} oggds_header_audio_t;
97

98
99
100
101
typedef struct
#ifdef HAVE_ATTRIBUTE_PACKED
    __attribute__((__packed__))
#endif
102
103
104
105
106
107
108
109
110
111
112
113
114
115
{
    uint8_t i_packet_type;

    char stream_type[8];
    char sub_type[4];

    int32_t i_size;

    int64_t i_time_unit;
    int64_t i_samples_per_unit;
    int32_t i_default_len;

    int32_t i_buffer_size;
    int16_t i_bits_per_sample;
116
117
118

    int16_t i_padding_0; /* Because the original is using MSVC packing style */

119
120
    union
    {
gbazin's avatar
   
gbazin committed
121
122
        oggds_header_video_t video;
        oggds_header_audio_t audio;
123
124
    } header;

125
126
    int32_t i_padding_1; /* Because the original is using MSVC packing style */

gbazin's avatar
   
gbazin committed
127
} oggds_header_t;
128

gbazin's avatar
   
gbazin committed
129
/* Helper writer functions */
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

#define SetWLE( p, v ) _SetWLE( (uint8_t*)p, v)
static void _SetWLE( uint8_t *p, uint16_t i_dw )
{
    p[1] = ( i_dw >>  8 )&0xff;
    p[0] = ( i_dw       )&0xff;
}

#define SetDWLE( p, v ) _SetDWLE( (uint8_t*)p, v)
static void _SetDWLE( uint8_t *p, uint32_t i_dw )
{
    p[3] = ( i_dw >> 24 )&0xff;
    p[2] = ( i_dw >> 16 )&0xff;
    p[1] = ( i_dw >>  8 )&0xff;
    p[0] = ( i_dw       )&0xff;
}
#define SetQWLE( p, v ) _SetQWLE( (uint8_t*)p, v)
static void _SetQWLE( uint8_t *p, uint64_t i_qw )
{
    SetDWLE( p,   i_qw&0xffffffff );
    SetDWLE( p+4, ( i_qw >> 32)&0xffffffff );
}

gbazin's avatar
   
gbazin committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
 * TODO  move this function to src/stream_output.c (used by nearly all muxers)
 */
static int MuxGetStream( sout_mux_t *p_mux, int *pi_stream, mtime_t *pi_dts )
{
    mtime_t i_dts;
    int     i_stream;
    int     i;

    for( i = 0, i_dts = 0, i_stream = -1; i < p_mux->i_nb_inputs; i++ )
    {
        sout_fifo_t  *p_fifo;

        p_fifo = p_mux->pp_inputs[i]->p_fifo;

gbazin's avatar
   
gbazin committed
168
169
170
171
172
173
174
175
        /* We don't really need to have anything in the SPU fifo */
        if( p_mux->pp_inputs[i]->p_fmt->i_cat == SPU_ES &&
            p_fifo->i_depth == 0 ) continue;

        if( p_fifo->i_depth > 2 ||
            /* Special case for SPUs */
            ( p_mux->pp_inputs[i]->p_fmt->i_cat == SPU_ES &&
              p_fifo->i_depth > 0 ) )
gbazin's avatar
   
gbazin committed
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
        {
            sout_buffer_t *p_buf;

            p_buf = sout_FifoShow( p_fifo );
            if( i_stream < 0 || p_buf->i_dts < i_dts )
            {
                i_dts = p_buf->i_dts;
                i_stream = i;
            }
        }
        else
        {
            // wait that all fifo have at least 3 packets (3 vorbis headers)
            return( -1 );
        }
    }
    if( pi_stream )
    {
        *pi_stream = i_stream;
    }
    if( pi_dts )
    {
        *pi_dts = i_dts;
    }
    return( i_stream );
}

/*****************************************************************************
 * Definitions of structures and functions used by this plugins 
 *****************************************************************************/
typedef struct
{
    int i_cat;
    int i_fourcc;

    int b_new;

    mtime_t i_dts;
    mtime_t i_length;
    int     i_packet_no;
gbazin's avatar
   
gbazin committed
216
    int     i_serial_no;
gbazin's avatar
   
gbazin committed
217
    int     i_keyframe_granule_shift; /* Theora only */
gbazin's avatar
   
gbazin committed
218
219
    ogg_stream_state os;

gbazin's avatar
   
gbazin committed
220
221
222
223
    oggds_header_t oggds_header;

    sout_buffer_t *pp_sout_headers[3];
    int           i_sout_headers;
gbazin's avatar
   
gbazin committed
224
225
226
227
228
229
230
231

} ogg_stream_t;

struct sout_mux_sys_t
{
    int     i_streams;

    mtime_t i_start_dts;
gbazin's avatar
   
gbazin committed
232
    int     i_next_serial_no;
gbazin's avatar
   
gbazin committed
233
234
235
236
237
238
239
240
241

    /* number of logical streams pending to be added */
    int i_add_streams;

    /* logical streams pending to be deleted */
    int i_del_streams;
    ogg_stream_t **pp_del_streams;
};

242
static void OggSetDate( sout_buffer_t *, mtime_t , mtime_t  );
gbazin's avatar
   
gbazin committed
243
244
static sout_buffer_t *OggStreamFlush( sout_mux_t *, ogg_stream_state *,
                                      mtime_t );
245
246

/*****************************************************************************
gbazin's avatar
   
gbazin committed
247
 * Open: Open muxer
248
249
250
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
251
252
    sout_mux_t      *p_mux = (sout_mux_t*)p_this;
    sout_mux_sys_t  *p_sys;
253

254
    msg_Info( p_mux, "Open" );
255

256
257
    p_sys                 = malloc( sizeof( sout_mux_sys_t ) );
    p_sys->i_streams      = 0;
gbazin's avatar
   
gbazin committed
258
259
260
    p_sys->i_add_streams  = 0;
    p_sys->i_del_streams  = 0;
    p_sys->pp_del_streams = 0;
261

262
263
264
265
266
267
    p_mux->p_sys        = p_sys;
    p_mux->pf_capacity  = Capability;
    p_mux->pf_addstream = AddStream;
    p_mux->pf_delstream = DelStream;
    p_mux->pf_mux       = Mux;
    p_mux->i_preheader  = 1;
268

gbazin's avatar
   
gbazin committed
269
270
271
272
273
274
    /* First serial number is random.
     * (Done like this because on win32 you need to seed the random number
     *  generator once per thread). */
    srand( (unsigned int)time( NULL ) );
    p_sys->i_next_serial_no = rand();

275
276
277
278
    return VLC_SUCCESS;
}

/*****************************************************************************
gbazin's avatar
   
gbazin committed
279
 * Close: Finalize ogg bitstream and close muxer
280
281
282
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
gbazin's avatar
   
gbazin committed
283
284
    sout_mux_t     *p_mux = (sout_mux_t*)p_this;
    sout_mux_sys_t *p_sys = p_mux->p_sys;
285

286
    msg_Info( p_mux, "Close" );
287

gbazin's avatar
   
gbazin committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
    if( p_sys->i_del_streams )
    {
        sout_buffer_t *p_og = NULL;
        mtime_t i_dts = -1;
        int i;

        /* Close the current ogg stream */
        msg_Dbg( p_mux, "writing footer" );
        sout_BufferChain( &p_og, OggCreateFooter( p_mux, 0 ) );

        /* Remove deleted logical streams */
        for( i = 0; i < p_sys->i_del_streams; i++ )
        {
            i_dts = p_sys->pp_del_streams[i]->i_dts;
            ogg_stream_clear( &p_sys->pp_del_streams[i]->os );
            FREE( p_sys->pp_del_streams[i] );
        }
        FREE( p_sys->pp_del_streams );
        p_sys->i_streams -= p_sys->i_del_streams;

        /* Write footer */
        OggSetDate( p_og, i_dts, 0 );
        sout_AccessOutWrite( p_mux->p_access, p_og );
    }

313
    free( p_sys );
314
315
}

gbazin's avatar
   
gbazin committed
316
317
static int Capability( sout_mux_t *p_mux, int i_query, void *p_args,
                       void *p_answer )
318
319
320
321
{
   switch( i_query )
   {
        case SOUT_MUX_CAP_GET_ADD_STREAM_ANY_TIME:
gbazin's avatar
   
gbazin committed
322
            *(vlc_bool_t*)p_answer = VLC_TRUE;
323
            return( SOUT_MUX_CAP_ERR_OK );
gbazin's avatar
   
gbazin committed
324
325
326
        case SOUT_MUX_CAP_GET_ADD_STREAM_WAIT:
            *(vlc_bool_t*)p_answer = VLC_TRUE;
            return( SOUT_MUX_CAP_ERR_OK );
327
328
329
330
331
        default:
            return( SOUT_MUX_CAP_ERR_UNIMPLEMENTED );
   }
}

gbazin's avatar
   
gbazin committed
332
333
334
/*****************************************************************************
 * AddStream: Add an elementary stream to the muxed stream
 *****************************************************************************/
335
static int AddStream( sout_mux_t *p_mux, sout_input_t *p_input )
336
{
gbazin's avatar
   
gbazin committed
337
338
    sout_mux_sys_t *p_sys = p_mux->p_sys;
    ogg_stream_t   *p_stream;
339

340
    msg_Dbg( p_mux, "adding input" );
gbazin's avatar
   
gbazin committed
341
342

    p_input->p_sys = (void *)p_stream = malloc( sizeof( ogg_stream_t ) );
343

344
    p_stream->i_cat       = p_input->p_fmt->i_cat;
345
    p_stream->i_fourcc    = p_input->p_fmt->i_codec;
gbazin's avatar
   
gbazin committed
346
    p_stream->i_serial_no = p_sys->i_next_serial_no++;
347
348
    p_stream->i_packet_no = 0;

gbazin's avatar
   
gbazin committed
349
350
    p_stream->i_sout_headers = 0;

351
    memset( &p_stream->oggds_header, 0, sizeof(p_stream->oggds_header) );
gbazin's avatar
   
gbazin committed
352
    p_stream->oggds_header.i_packet_type = PACKET_TYPE_HEADER;
353
    switch( p_input->p_fmt->i_cat )
354
    {
gbazin's avatar
   
gbazin committed
355
    case VIDEO_ES:
gbazin's avatar
   
gbazin committed
356
        switch( p_stream->i_fourcc )
gbazin's avatar
   
gbazin committed
357
        {
358
359
360
361
362
363
        case VLC_FOURCC( 'm', 'p', 'g', 'v' ):
        case VLC_FOURCC( 'm', 'p', '4', 'v' ):
        case VLC_FOURCC( 'D', 'I', 'V', '3' ):
        case VLC_FOURCC( 'M', 'J', 'P', 'G' ):
        case VLC_FOURCC( 'W', 'M', 'V', '1' ):
        case VLC_FOURCC( 'W', 'M', 'V', '2' ):
364
            memcpy( p_stream->oggds_header.stream_type, "video", 5 );
365
            if( p_stream->i_fourcc == VLC_FOURCC( 'm', 'p', '4', 'v' ) )
gbazin's avatar
   
gbazin committed
366
            {
gbazin's avatar
   
gbazin committed
367
                memcpy( p_stream->oggds_header.sub_type, "XVID", 4 );
gbazin's avatar
   
gbazin committed
368
            }
369
            else if( p_stream->i_fourcc == VLC_FOURCC( 'D', 'I', 'V', '3' ) )
370
            {
gbazin's avatar
   
gbazin committed
371
                memcpy( p_stream->oggds_header.sub_type, "DIV3", 4 );
372
            }
gbazin's avatar
   
gbazin committed
373
374
375
376
            else
            {
                memcpy(p_stream->oggds_header.sub_type,&p_stream->i_fourcc,4);
            }
gbazin's avatar
   
gbazin committed
377
378
379
            SetDWLE( &p_stream->oggds_header.i_size,
                     sizeof( oggds_header_t ) - 1);
            SetQWLE( &p_stream->oggds_header.i_time_unit,
gbazin's avatar
   
gbazin committed
380
                     I64C(10000000)/(int64_t)25 );  // FIXME (25fps)
gbazin's avatar
   
gbazin committed
381
            SetQWLE( &p_stream->oggds_header.i_samples_per_unit, 1 );
382
            SetDWLE( &p_stream->oggds_header.i_default_len, 1 ); /* ??? */
gbazin's avatar
   
gbazin committed
383
384
385
            SetDWLE( &p_stream->oggds_header.i_buffer_size, 1024*1024 );
            SetWLE( &p_stream->oggds_header.i_bits_per_sample, 0 );
            SetDWLE( &p_stream->oggds_header.header.video.i_width,
386
                     p_input->p_fmt->video.i_width );
gbazin's avatar
   
gbazin committed
387
            SetDWLE( &p_stream->oggds_header.header.video.i_height,
388
                     p_input->p_fmt->video.i_height );
389
            msg_Dbg( p_mux, "%4.4s stream", (char *)&p_stream->i_fourcc );
390
            break;
gbazin's avatar
   
gbazin committed
391

gbazin's avatar
   
gbazin committed
392
        case VLC_FOURCC( 't', 'h', 'e', 'o' ):
gbazin's avatar
   
gbazin committed
393
394
            msg_Dbg( p_mux, "theora stream" );
            break;
gbazin's avatar
   
gbazin committed
395

gbazin's avatar
   
gbazin committed
396
397
398
399
400
        default:
            FREE( p_input->p_sys );
            return( VLC_EGENERIC );
        }
        break;
gbazin's avatar
   
gbazin committed
401

gbazin's avatar
   
gbazin committed
402
    case AUDIO_ES:
gbazin's avatar
   
gbazin committed
403
        switch( p_stream->i_fourcc )
gbazin's avatar
   
gbazin committed
404
        {
405
406
        case VLC_FOURCC( 'm', 'p', 'g', 'a' ):
        case VLC_FOURCC( 'a', '5', '2', ' ' ):
407
            memcpy( p_stream->oggds_header.stream_type, "audio", 5 );
408
            if( p_stream->i_fourcc == VLC_FOURCC( 'm', 'p', 'g', 'a' ) )
gbazin's avatar
   
gbazin committed
409
            {
gbazin's avatar
   
gbazin committed
410
                memcpy( p_stream->oggds_header.sub_type, "55  ", 4 );
gbazin's avatar
   
gbazin committed
411
            }
412
            else if( p_stream->i_fourcc == VLC_FOURCC( 'a', '5', '2', ' ' ) )
413
            {
gbazin's avatar
   
gbazin committed
414
                memcpy( p_stream->oggds_header.sub_type, "2000", 4 );
415
            }
gbazin's avatar
   
gbazin committed
416
417
418
            SetDWLE( &p_stream->oggds_header.i_size,
                     sizeof( oggds_header_t ) - 1);
            SetQWLE( &p_stream->oggds_header.i_time_unit, 0 /* not used */ );
419
            SetDWLE( &p_stream->oggds_header.i_default_len, 1 );
gbazin's avatar
   
gbazin committed
420
421
            SetDWLE( &p_stream->oggds_header.i_buffer_size, 30*1024 );
            SetQWLE( &p_stream->oggds_header.i_samples_per_unit,
422
                     p_input->p_fmt->audio.i_rate );
gbazin's avatar
   
gbazin committed
423
424
            SetWLE( &p_stream->oggds_header.i_bits_per_sample, 0 );
            SetDWLE( &p_stream->oggds_header.header.audio.i_channels,
425
                     p_input->p_fmt->audio.i_channels );
gbazin's avatar
   
gbazin committed
426
            SetDWLE( &p_stream->oggds_header.header.audio.i_block_align,
427
                     p_input->p_fmt->audio.i_blockalign );
gbazin's avatar
   
gbazin committed
428
            SetDWLE( &p_stream->oggds_header.header.audio.i_avgbytespersec, 0);
gbazin's avatar
   
gbazin committed
429
            msg_Dbg( p_mux, "mpga/a52 stream" );
430
            break;
gbazin's avatar
   
gbazin committed
431
432

        case VLC_FOURCC( 'v', 'o', 'r', 'b' ):
gbazin's avatar
   
gbazin committed
433
434
            msg_Dbg( p_mux, "vorbis stream" );
            break;
gbazin's avatar
   
gbazin committed
435

gbazin's avatar
   
gbazin committed
436
437
438
439
        case VLC_FOURCC( 's', 'p', 'x', ' ' ):
            msg_Dbg( p_mux, "speex stream" );
            break;

gbazin's avatar
   
gbazin committed
440
441
442
443
        case VLC_FOURCC( 'f', 'l', 'a', 'c' ):
            msg_Dbg( p_mux, "flac stream" );
            break;

444
        default:
445
            FREE( p_input->p_sys );
446
            return( VLC_EGENERIC );
gbazin's avatar
   
gbazin committed
447
448
449
        }
        break;

gbazin's avatar
   
gbazin committed
450
451
452
453
    case SPU_ES:
        switch( p_stream->i_fourcc )
        {
        case VLC_FOURCC( 's', 'u','b', 't' ):
454
            memcpy( p_stream->oggds_header.stream_type, "text", 4 );
gbazin's avatar
   
gbazin committed
455
            msg_Dbg( p_mux, "subtitles stream" );
gbazin's avatar
   
gbazin committed
456
457
458
459
460
461
462
            break;

        default:
            FREE( p_input->p_sys );
            return( VLC_EGENERIC );
        }
        break;
gbazin's avatar
   
gbazin committed
463
464
465
    default:
        FREE( p_input->p_sys );
        return( VLC_EGENERIC );
466
467
    }

gbazin's avatar
   
gbazin committed
468
469
470
    p_stream->b_new = VLC_TRUE;

    p_sys->i_add_streams++;
471
472
473
474

    return( VLC_SUCCESS );
}

gbazin's avatar
   
gbazin committed
475
476
477
/*****************************************************************************
 * DelStream: Delete an elementary stream from the muxed stream
 *****************************************************************************/
478
static int DelStream( sout_mux_t *p_mux, sout_input_t *p_input )
479
{
gbazin's avatar
   
gbazin committed
480
481
482
    sout_mux_sys_t *p_sys  = p_mux->p_sys;
    ogg_stream_t   *p_stream = (ogg_stream_t*)p_input->p_sys;
    sout_buffer_t  *p_og;
483

484
    msg_Dbg( p_mux, "removing input" );
485
486

    /* flush all remaining data */
487
    if( p_input->p_sys )
488
    {
gbazin's avatar
   
gbazin committed
489
490
        int i;

491
492
        if( !p_stream->b_new &&
            ( p_og = OggStreamFlush( p_mux, &p_stream->os, 0 ) ) )
493
494
495
496
        {
            OggSetDate( p_og, p_stream->i_dts, p_stream->i_length );
            sout_AccessOutWrite( p_mux->p_access, p_og );
        }
497

gbazin's avatar
   
gbazin committed
498
499
500
501
502
503
        for( i = 0; i < p_stream->i_sout_headers; i++ )
        {
            sout_BufferDelete( p_mux->p_sout, p_stream->pp_sout_headers[i] );
            p_stream->i_sout_headers = 0;
        }

gbazin's avatar
   
gbazin committed
504
505
        /* move input in delete queue */
        if( !p_stream->b_new )
506
        {
gbazin's avatar
   
gbazin committed
507
508
509
510
            p_sys->pp_del_streams = realloc( p_sys->pp_del_streams,
                                             (p_sys->i_del_streams + 1) *
                                             sizeof(ogg_stream_t *) );
            p_sys->pp_del_streams[p_sys->i_del_streams++] = p_stream;
511
512
513
        }
        else
        {
gbazin's avatar
   
gbazin committed
514
515
516
517
            /* Wasn't already added so get rid of it */
            ogg_stream_clear( &p_stream->os );
            FREE( p_stream );
            p_sys->i_add_streams--;
518
519
        }
    }
gbazin's avatar
   
gbazin committed
520
521
522
523

    p_input->p_sys = NULL;

    return( 0 );
524
525
}

gbazin's avatar
   
gbazin committed
526
527
528
/*****************************************************************************
 * Ogg bitstream manipulation routines
 *****************************************************************************/
529
static sout_buffer_t *OggStreamFlush( sout_mux_t *p_mux,
gbazin's avatar
   
gbazin committed
530
                                      ogg_stream_state *p_os, mtime_t i_pts )
531
{
gbazin's avatar
   
gbazin committed
532
533
    sout_buffer_t *p_og, *p_og_first = NULL;
    ogg_page      og;
534
535
536
537
538
539
540
541
542
543

    for( ;; )
    {
        /* flush all data */
        int i_result;
        int i_size;
        if( ( i_result = ogg_stream_flush( p_os, &og ) ) == 0 )
        {
            break;
        }
gbazin's avatar
   
gbazin committed
544

545
        i_size = og.header_len + og.body_len;
546
        p_og = sout_BufferNew( p_mux->p_sout, i_size);
547

gbazin's avatar
   
gbazin committed
548
549
        memcpy( p_og->p_buffer, og.header, og.header_len );
        memcpy( p_og->p_buffer + og.header_len, og.body, og.body_len );
550
551
552
553
554
555
556
557
558
559
560
561
        p_og->i_size    = i_size;
        p_og->i_dts     = 0;
        p_og->i_pts     = i_pts;
        p_og->i_length  = 0;

        i_pts   = 0; // write it only once

        sout_BufferChain( &p_og_first, p_og );
    }

    return( p_og_first );
}
gbazin's avatar
   
gbazin committed
562

563
static sout_buffer_t *OggStreamPageOut( sout_mux_t *p_mux,
gbazin's avatar
   
gbazin committed
564
                                        ogg_stream_state *p_os, mtime_t i_pts )
565
{
gbazin's avatar
   
gbazin committed
566
567
    sout_buffer_t *p_og, *p_og_first = NULL;
    ogg_page      og;
568
569
570
571
572
573
574
575
576
577

    for( ;; )
    {
        /* flush all data */
        int i_result;
        int i_size;
        if( ( i_result = ogg_stream_pageout( p_os, &og ) ) == 0 )
        {
            break;
        }
gbazin's avatar
   
gbazin committed
578

579
        i_size = og.header_len + og.body_len;
580
        p_og = sout_BufferNew( p_mux->p_sout, i_size);
581

gbazin's avatar
   
gbazin committed
582
583
        memcpy( p_og->p_buffer, og.header, og.header_len );
        memcpy( p_og->p_buffer + og.header_len, og.body, og.body_len );
584
585
586
587
588
589
590
591
592
593
594
595
        p_og->i_size    = i_size;
        p_og->i_dts     = 0;
        p_og->i_pts     = i_pts;
        p_og->i_length  = 0;

        i_pts   = 0; // write them only once

        sout_BufferChain( &p_og_first, p_og );
    }

    return( p_og_first );
}
gbazin's avatar
   
gbazin committed
596

597
static sout_buffer_t *OggCreateHeader( sout_mux_t *p_mux, mtime_t i_dts )
598
599
600
601
602
603
{
    sout_buffer_t *p_hdr = NULL;
    sout_buffer_t *p_og;
    ogg_packet    op;
    int i;

gbazin's avatar
   
gbazin committed
604
605
    /* Write header for each stream. All b_o_s (beginning of stream) packets
     * must appear first in the ogg stream so we take care of them first. */
606
    for( i = 0; i < p_mux->i_nb_inputs; i++ )
607
    {
gbazin's avatar
   
gbazin committed
608
609
        ogg_stream_t *p_stream = (ogg_stream_t*)p_mux->pp_inputs[i]->p_sys;
        p_stream->b_new = VLC_FALSE;
610

gbazin's avatar
   
gbazin committed
611
612
613
        msg_Dbg( p_mux, "creating header for %4.4s",
                 (char *)&p_stream->i_fourcc );

gbazin's avatar
   
gbazin committed
614
        ogg_stream_init( &p_stream->os, p_stream->i_serial_no );
gbazin's avatar
   
gbazin committed
615
616
        p_stream->i_packet_no = 0;

gbazin's avatar
   
gbazin committed
617
        if( p_stream->i_fourcc == VLC_FOURCC( 'v', 'o', 'r', 'b' ) ||
gbazin's avatar
   
gbazin committed
618
            p_stream->i_fourcc == VLC_FOURCC( 's', 'p', 'x', ' ' ) ||
gbazin's avatar
   
gbazin committed
619
            p_stream->i_fourcc == VLC_FOURCC( 't', 'h', 'e', 'o' ) )
620
        {
gbazin's avatar
   
gbazin committed
621
            /* Special case, headers are already there in the
gbazin's avatar
   
gbazin committed
622
             * incoming stream or we backed them up earlier */
gbazin's avatar
   
gbazin committed
623

gbazin's avatar
   
gbazin committed
624
            /* first packet in order: vorbis/speex/theora info */
gbazin's avatar
   
gbazin committed
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
            if( !p_stream->i_sout_headers )
            {
                p_og = sout_FifoGet( p_mux->pp_inputs[i]->p_fifo );
                op.packet = p_og->p_buffer;
                op.bytes  = p_og->i_size;
                op.b_o_s  = 1;
                op.e_o_s  = 0;
                op.granulepos = 0;
                op.packetno = p_stream->i_packet_no++;
                ogg_stream_packetin( &p_stream->os, &op );
                p_stream->pp_sout_headers[0] =
                    OggStreamFlush( p_mux, &p_stream->os, 0 );
                p_stream->i_sout_headers++;
            }
            p_og = sout_BufferDuplicate( p_mux->p_sout,
                                         p_stream->pp_sout_headers[0] );
gbazin's avatar
   
gbazin committed
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655

            /* Get keyframe_granule_shift for theora granulepos calculation */
            if( p_stream->i_fourcc == VLC_FOURCC( 't', 'h', 'e', 'o' ) )
            {
                int i_keyframe_frequency_force = 1 << (op.packet[36] >> 3);

                /* granule_shift = i_log( frequency_force -1 ) */
                p_stream->i_keyframe_granule_shift = 0;
                i_keyframe_frequency_force--;
                while( i_keyframe_frequency_force )
                {
                    p_stream->i_keyframe_granule_shift++;
                    i_keyframe_frequency_force >>= 1;
                }
            }
656
        }
gbazin's avatar
   
gbazin committed
657
658
659
660
661
662
663
664
665
666
667
668
        else if( p_stream->i_fourcc == VLC_FOURCC( 'f', 'l', 'a', 'c' ) )
        {
            /* flac stream marker (yeah, only that in the 1st packet) */
            op.packet = "fLaC";
            op.bytes  = 4;
            op.b_o_s  = 1;
            op.e_o_s  = 0;
            op.granulepos = 0;
            op.packetno = p_stream->i_packet_no++;
            ogg_stream_packetin( &p_stream->os, &op );
            p_og = OggStreamFlush( p_mux, &p_stream->os, 0 );
        }
669
670
671
        else
        {
            /* ds header */
gbazin's avatar
   
gbazin committed
672
673
            op.packet = (uint8_t*)&p_stream->oggds_header;
            op.bytes  = sizeof( oggds_header_t );
674
675
676
677
678
            op.b_o_s  = 1;
            op.e_o_s  = 0;
            op.granulepos = 0;
            op.packetno = p_stream->i_packet_no++;
            ogg_stream_packetin( &p_stream->os, &op );
gbazin's avatar
   
gbazin committed
679
            p_og = OggStreamFlush( p_mux, &p_stream->os, 0 );
gbazin's avatar
   
gbazin committed
680
681
682
683
684
685
686
687
        }

        sout_BufferChain( &p_hdr, p_og );
    }

    /* Take care of the non b_o_s headers */
    for( i = 0; i < p_mux->i_nb_inputs; i++ )
    {
gbazin's avatar
   
gbazin committed
688
        ogg_stream_t *p_stream = (ogg_stream_t*)p_mux->pp_inputs[i]->p_sys;
gbazin's avatar
   
gbazin committed
689

gbazin's avatar
   
gbazin committed
690
        if( p_stream->i_fourcc == VLC_FOURCC( 'v', 'o', 'r', 'b' ) ||
gbazin's avatar
   
gbazin committed
691
            p_stream->i_fourcc == VLC_FOURCC( 's', 'p', 'x', ' ' ) ||
gbazin's avatar
   
gbazin committed
692
            p_stream->i_fourcc == VLC_FOURCC( 't', 'h', 'e', 'o' ) )
gbazin's avatar
   
gbazin committed
693
694
695
696
697
698
        {
            /* Special case, headers are already there in the incoming stream.
             * We need to gather them an mark them as headers. */
            int j;
            for( j = 0; j < 2; j++ )
            {
gbazin's avatar
   
gbazin committed
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
                if( p_stream->i_sout_headers < j + 2 )
                {
                    /* next packets in order: comments and codebooks */
                    p_og = sout_FifoGet( p_mux->pp_inputs[i]->p_fifo );
                    op.packet = p_og->p_buffer;
                    op.bytes  = p_og->i_size;
                    op.b_o_s  = 0;
                    op.e_o_s  = 0;
                    op.granulepos = 0;
                    op.packetno = p_stream->i_packet_no++;
                    ogg_stream_packetin( &p_stream->os, &op );
                    p_stream->pp_sout_headers[j+1] =
                        OggStreamFlush( p_mux, &p_stream->os, 0 );
                    p_stream->i_sout_headers++;
                }

                p_og = sout_BufferDuplicate( p_mux->p_sout,
                                             p_stream->pp_sout_headers[j+1] );
                sout_BufferChain( &p_hdr, p_og );
gbazin's avatar
   
gbazin committed
718
719
            }
        }
gbazin's avatar
   
gbazin committed
720
        else if( p_stream->i_fourcc != VLC_FOURCC( 'f', 'l', 'a', 'c' ) )
gbazin's avatar
   
gbazin committed
721
722
723
724
        {
            uint8_t com[128];
            int     i_com;

725
726
            /* comment */
            com[0] = PACKET_TYPE_COMMENT;
gbazin's avatar
   
gbazin committed
727
            i_com = snprintf( &com[1], 128, VERSION" stream output" ) + 1;
728
729
730
731
732
733
734
            op.packet = com;
            op.bytes  = i_com;
            op.b_o_s  = 0;
            op.e_o_s  = 0;
            op.granulepos = 0;
            op.packetno = p_stream->i_packet_no++;
            ogg_stream_packetin( &p_stream->os, &op );
gbazin's avatar
   
gbazin committed
735
736
            p_og = OggStreamFlush( p_mux, &p_stream->os, 0 );
            sout_BufferChain( &p_hdr, p_og );
737
        }
738

gbazin's avatar
   
gbazin committed
739
740
741
        /* Special case for mp4v and flac */
        if( ( p_stream->i_fourcc == VLC_FOURCC( 'm', 'p', '4', 'v' ) ||
              p_stream->i_fourcc == VLC_FOURCC( 'f', 'l', 'a', 'c' ) ) &&
742
            p_mux->pp_inputs[i]->p_fmt->i_extra )
743
        {
gbazin's avatar
   
gbazin committed
744
745
746
            /* Send a packet with the VOL data for mp4v
             * or STREAMINFO for flac */
            msg_Dbg( p_mux, "writing extra data" );
747
748
            op.bytes  = p_mux->pp_inputs[i]->p_fmt->i_extra;
            op.packet = p_mux->pp_inputs[i]->p_fmt->p_extra;
gbazin's avatar
   
gbazin committed
749
750
751
752
753
754
            if( p_stream->i_fourcc == VLC_FOURCC( 'f', 'l', 'a', 'c' ) )
            {
                /* Skip the flac stream marker */
                ((uint8_t *)op.bytes) -= 4;
                ((uint8_t *)op.packet) += 4;
            }
755
756
757
758
759
760
761
762
            op.b_o_s  = 0;
            op.e_o_s  = 0;
            op.granulepos = 0;
            op.packetno = p_stream->i_packet_no++;
            ogg_stream_packetin( &p_stream->os, &op );
            p_og = OggStreamFlush( p_mux, &p_stream->os, 0 );
            sout_BufferChain( &p_hdr, p_og );
        }
763
764
    }

765
766
767
768
769
    /* set HEADER flag */
    for( p_og = p_hdr; p_og != NULL; p_og = p_og->p_next )
    {
        p_og->i_flags |= SOUT_BUFFER_FLAGS_HEADER;
    }
770
771
772
    return( p_hdr );
}

gbazin's avatar
   
gbazin committed
773
774
775
776
777
778
779
780
static sout_buffer_t *OggCreateFooter( sout_mux_t *p_mux, mtime_t i_dts )
{
    sout_mux_sys_t *p_sys = p_mux->p_sys;
    sout_buffer_t *p_hdr = NULL;
    sout_buffer_t *p_og;
    ogg_packet    op;
    int i;

gbazin's avatar
   
gbazin committed
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
    /* flush all remaining data */
    for( i = 0; i < p_mux->i_nb_inputs; i++ )
    {
        ogg_stream_t *p_stream = p_mux->pp_inputs[i]->p_sys;

        /* skip newly added streams */
        if( p_stream->b_new ) continue;

        if( ( p_og = OggStreamFlush( p_mux, &p_stream->os, 0 ) ) )
        {
            OggSetDate( p_og, p_stream->i_dts, p_stream->i_length );
            sout_AccessOutWrite( p_mux->p_access, p_og );
        }
    }

gbazin's avatar
   
gbazin committed
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
    /* Write eos packets for each stream. */
    for( i = 0; i < p_mux->i_nb_inputs; i++ )
    {
        ogg_stream_t *p_stream = p_mux->pp_inputs[i]->p_sys;

        /* skip newly added streams */
        if( p_stream->b_new ) continue;

        op.packet = NULL;
        op.bytes  = 0;
        op.b_o_s  = 0;
        op.e_o_s  = 1;
        op.granulepos = -1;
        op.packetno = p_stream->i_packet_no++;
        ogg_stream_packetin( &p_stream->os, &op );

        p_og = OggStreamFlush( p_mux, &p_stream->os, 0 );
        sout_BufferChain( &p_hdr, p_og );
gbazin's avatar
   
gbazin committed
814
        ogg_stream_clear( &p_stream->os );
gbazin's avatar
   
gbazin committed
815
816
817
818
819
820
821
822
823
824
825
826
827
828
    }

    for( i = 0; i < p_sys->i_del_streams; i++ )
    {
        op.packet = NULL;
        op.bytes  = 0;
        op.b_o_s  = 0;
        op.e_o_s  = 1;
        op.granulepos = -1;
        op.packetno = p_sys->pp_del_streams[i]->i_packet_no++;
        ogg_stream_packetin( &p_sys->pp_del_streams[i]->os, &op );

        p_og = OggStreamFlush( p_mux, &p_sys->pp_del_streams[i]->os, 0 );
        sout_BufferChain( &p_hdr, p_og );
gbazin's avatar
   
gbazin committed
829
        ogg_stream_clear( &p_sys->pp_del_streams[i]->os );
gbazin's avatar
   
gbazin committed
830
831
832
833
834
    }

    return( p_hdr );
}

835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
static void OggSetDate( sout_buffer_t *p_og, mtime_t i_dts, mtime_t i_length )
{
    int i_count;
    sout_buffer_t *p_tmp;
    mtime_t i_delta;

    for( p_tmp = p_og, i_count = 0; p_tmp != NULL; p_tmp = p_tmp->p_next )
    {
        i_count++;
    }
    i_delta = i_length / i_count;

    for( p_tmp = p_og; p_tmp != NULL; p_tmp = p_tmp->p_next )
    {
        p_tmp->i_dts    = i_dts;
        p_tmp->i_length = i_delta;

        i_dts += i_delta;
    }
}

gbazin's avatar
   
gbazin committed
856
857
858
/*****************************************************************************
 * Mux: multiplex available data in input fifos into the Ogg bitstream
 *****************************************************************************/
gbazin's avatar
   
gbazin committed
859
static int Mux( sout_mux_t *p_mux )
860
{
gbazin's avatar
   
gbazin committed
861
862
863
864
    sout_mux_sys_t *p_sys = p_mux->p_sys;
    sout_buffer_t  *p_og = NULL;
    int            i_stream;
    mtime_t        i_dts;
865

gbazin's avatar
   
gbazin committed
866
    if( p_sys->i_add_streams || p_sys->i_del_streams )
867
    {
gbazin's avatar
   
gbazin committed
868
        /* Open new ogg stream */
869
        if( MuxGetStream( p_mux, &i_stream, &i_dts) < 0 )
870
        {
gbazin's avatar
   
gbazin committed
871
            msg_Dbg( p_mux, "waiting for data..." );
872
873
            return( VLC_SUCCESS );
        }
gbazin's avatar
   
gbazin committed
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890

        if( p_sys->i_streams )
        {
            /* Close current ogg stream */
            int i;

            msg_Dbg( p_mux, "writing footer" );
            sout_BufferChain( &p_og, OggCreateFooter( p_mux, 0 ) );

            /* Remove deleted logical streams */
            for( i = 0; i < p_sys->i_del_streams; i++ )
            {
                FREE( p_sys->pp_del_streams[i] );
            }
            FREE( p_sys->pp_del_streams );
            p_sys->i_streams = 0;
        }
891
892

        msg_Dbg( p_mux, "writing header" );
gbazin's avatar
   
gbazin committed
893
894
895
896
        p_sys->i_start_dts = i_dts;
        p_sys->i_streams = p_mux->i_nb_inputs;
        p_sys->i_del_streams = 0;
        p_sys->i_add_streams = 0;
897
        sout_BufferChain( &p_og, OggCreateHeader( p_mux, i_dts ) );
gbazin's avatar
   
gbazin committed
898
899
900
901
902

        /* Write header and/or footer */
        OggSetDate( p_og, i_dts, 0 );
        sout_AccessOutWrite( p_mux->p_access, p_og );
        p_og = NULL;
903
904
905
906
    }

    for( ;; )
    {
gbazin's avatar
   
gbazin committed
907
908
909
910
        sout_input_t  *p_input;
        ogg_stream_t  *p_stream;
        sout_buffer_t *p_data;
        ogg_packet    op;
911

912
        if( MuxGetStream( p_mux, &i_stream, &i_dts) < 0 )
913
914
915
        {
            return( VLC_SUCCESS );
        }
gbazin's avatar
   
gbazin committed
916

917
918
        p_input  = p_mux->pp_inputs[i_stream];
        p_stream = (ogg_stream_t*)p_input->p_sys;
gbazin's avatar
   
gbazin committed
919
        p_data   = sout_FifoGet( p_input->p_fifo );
920

gbazin's avatar
   
gbazin committed
921
        if( p_stream->i_fourcc != VLC_FOURCC( 'v', 'o', 'r', 'b' ) &&
gbazin's avatar
   
gbazin committed
922
            p_stream->i_fourcc != VLC_FOURCC( 'f', 'l', 'a', 'c' ) &&
gbazin's avatar
   
gbazin committed
923
            p_stream->i_fourcc != VLC_FOURCC( 's', 'p', 'x', ' ' ) &&
gbazin's avatar
   
gbazin committed
924
            p_stream->i_fourcc != VLC_FOURCC( 't', 'h', 'e', 'o' ) )
gbazin's avatar
   
gbazin committed
925
926
927
928
929
930
931
932
933
934
        {
            sout_BufferReallocFromPreHeader( p_mux->p_sout, p_data, 1 );
            p_data->p_buffer[0] = PACKET_IS_SYNCPOINT;      // FIXME
        }

        op.packet   = p_data->p_buffer;
        op.bytes    = p_data->i_size;
        op.b_o_s    = 0;
        op.e_o_s    = 0;
        op.packetno = p_stream->i_packet_no++;
935
936
937

        if( p_stream->i_cat == AUDIO_ES )
        {
gbazin's avatar
   
gbazin committed
938
            if( p_stream->i_fourcc == VLC_FOURCC( 'v', 'o', 'r', 'b' ) ||
gbazin's avatar
   
gbazin committed
939
                p_stream->i_fourcc == VLC_FOURCC( 'f', 'l', 'a', 'c' ) ||
gbazin's avatar
   
gbazin committed
940
                p_stream->i_fourcc == VLC_FOURCC( 's', 'p', 'x', ' ' ) )
gbazin's avatar
   
gbazin committed
941
942
943
            {
                /* number of sample from begining + current packet */
                op.granulepos =
gbazin's avatar
   
gbazin committed
944
945
                    ( i_dts - p_sys->i_start_dts + p_data->i_length ) *
                    (mtime_t)p_input->p_fmt->audio.i_rate / I64C(1000000);
gbazin's avatar
   
gbazin committed
946
947
948
949
950
            }
            else
            {
                /* number of sample from begining */
                op.granulepos = ( i_dts - p_sys->i_start_dts ) *
gbazin's avatar
   
gbazin committed
951
                    p_stream->oggds_header.i_samples_per_unit / I64C(1000000);
gbazin's avatar
   
gbazin committed
952
            }
953
954
955
        }
        else if( p_stream->i_cat == VIDEO_ES )
        {
gbazin's avatar
   
gbazin committed
956
957
            if( p_stream->i_fourcc == VLC_FOURCC( 't', 'h', 'e', 'o' ) )
            {
gbazin's avatar
   
gbazin committed
958
                /* FIXME, we assume only keyframes and 25fps */
gbazin's avatar
   
gbazin committed
959
                op.granulepos = ( ( i_dts - p_sys->i_start_dts ) * I64C(25)
gbazin's avatar
   
gbazin committed
960
                    / I64C(1000000) ) << p_stream->i_keyframe_granule_shift;
gbazin's avatar
   
gbazin committed
961
962
            }
            else
gbazin's avatar
   
gbazin committed
963
964
                op.granulepos = ( i_dts - p_sys->i_start_dts ) * I64C(10) /
                    p_stream->oggds_header.i_time_unit;
965
        }
gbazin's avatar
   
gbazin committed
966
967
968
969
970
        else if( p_stream->i_cat == SPU_ES )
        {
            /* granulepos is in milisec */
            op.granulepos = ( i_dts - p_sys->i_start_dts ) / 1000;
        }
gbazin's avatar
   
gbazin committed
971

972
973
        ogg_stream_packetin( &p_stream->os, &op );

gbazin's avatar
   
gbazin committed
974
975
        if( p_stream->i_cat == SPU_ES ||
            p_stream->i_fourcc == VLC_FOURCC( 's', 'p', 'x', ' ' ) )
gbazin's avatar
   
gbazin committed
976
        {
gbazin's avatar
   
gbazin committed
977
978
            /* Subtitles or Speex packets are quite small so they 
             * need to be flushed to be sent on time */
gbazin's avatar
   
gbazin committed
979
980
981
982
983
984
985
986
            sout_BufferChain( &p_og, OggStreamFlush( p_mux, &p_stream->os,
                                                     p_data->i_dts ) );
        }
        else
        {
            sout_BufferChain( &p_og, OggStreamPageOut( p_mux, &p_stream->os,
                                                       p_data->i_dts ) );
        }
987
988
989
990
991

        if( p_og )
        {
            OggSetDate( p_og, p_stream->i_dts, p_stream->i_length );
            p_stream->i_dts = -1;
992
993
            p_stream->i_length = 0;

994
            sout_AccessOutWrite( p_mux->p_access, p_og );
995
996
997
998
999
1000
1001

            p_og = NULL;
        }
        else
        {
            if( p_stream->i_dts < 0 )
            {
1002
                p_stream->i_dts = p_data->i_dts;
1003
1004
1005
1006
            }
            p_stream->i_length += p_data->i_length;
        }

1007
        sout_BufferDelete( p_mux->p_sout, p_data );
1008
1009
1010
1011
    }

    return( VLC_SUCCESS );
}