duplicate.c 13.9 KB
Newer Older
1
/*****************************************************************************
2
 * duplicate.c: duplicate stream output module
3
 *****************************************************************************
4
 * Copyright (C) 2003-2004 the VideoLAN team
5
 * $Id$
6
 *
7
 * Author: Laurent Aimar <fenrir@via.ecp.fr>
8
 *
9 10 11
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
12 13 14 15
 * (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
16 17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
19 20 21
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23 24 25 26 27
 *****************************************************************************/

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

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

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
34 35
#include <vlc_sout.h>
#include <vlc_block.h>
36 37

/*****************************************************************************
38
 * Module descriptor
39 40 41 42
 *****************************************************************************/
static int      Open    ( vlc_object_t * );
static void     Close   ( vlc_object_t * );

43 44 45
vlc_module_begin ()
    set_description( N_("Duplicate stream output") )
    set_capability( "sout stream", 50 )
46
    add_shortcut( "duplicate", "dup" )
47 48 49 50
    set_category( CAT_SOUT )
    set_subcategory( SUBCAT_SOUT_STREAM )
    set_callbacks( Open, Close )
vlc_module_end ()
51

52 53 54 55

/*****************************************************************************
 * Exported prototypes
 *****************************************************************************/
56
static sout_stream_id_sys_t *Add( sout_stream_t *, const es_format_t * );
57
static void              Del ( sout_stream_t *, sout_stream_id_sys_t * );
58
static int               Send( sout_stream_t *, sout_stream_id_sys_t *,
59 60
                               block_t* );

61 62 63 64
struct sout_stream_sys_t
{
    int             i_nb_streams;
    sout_stream_t   **pp_streams;
65

66 67 68
    int             i_nb_last_streams;
    sout_stream_t   **pp_last_streams;

69 70
    int             i_nb_select;
    char            **ppsz_select;
71 72
};

73
struct sout_stream_id_sys_t
74 75 76 77 78
{
    int                 i_nb_ids;
    void                **pp_ids;
};

79
static bool ESSelected( const es_format_t *fmt, char *psz_select );
80

81 82 83 84 85 86 87
/*****************************************************************************
 * Open:
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    sout_stream_t     *p_stream = (sout_stream_t*)p_this;
    sout_stream_sys_t *p_sys;
88
    config_chain_t        *p_cfg;
89

90
    msg_Dbg( p_stream, "creating 'duplicate'" );
91 92

    p_sys = malloc( sizeof( sout_stream_sys_t ) );
93 94
    if( !p_sys )
        return VLC_ENOMEM;
95

96
    TAB_INIT( p_sys->i_nb_streams, p_sys->pp_streams );
97
    TAB_INIT( p_sys->i_nb_last_streams, p_sys->pp_last_streams );
98
    TAB_INIT( p_sys->i_nb_select, p_sys->ppsz_select );
99 100 101 102 103

    for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
    {
        if( !strncmp( p_cfg->psz_name, "dst", strlen( "dst" ) ) )
        {
104
            sout_stream_t *s, *p_last;
105 106

            msg_Dbg( p_stream, " * adding `%s'", p_cfg->psz_value );
107 108
            s = sout_StreamChainNew( p_stream->p_sout, p_cfg->psz_value,
                p_stream->p_next, &p_last );
109

110 111 112
            if( s )
            {
                TAB_APPEND( p_sys->i_nb_streams, p_sys->pp_streams, s );
113 114
                TAB_APPEND( p_sys->i_nb_last_streams, p_sys->pp_last_streams,
                    p_last );
115 116 117 118 119 120 121 122
                TAB_APPEND( p_sys->i_nb_select,  p_sys->ppsz_select, NULL );
            }
        }
        else if( !strncmp( p_cfg->psz_name, "select", strlen( "select" ) ) )
        {
            char *psz = p_cfg->psz_value;
            if( p_sys->i_nb_select > 0 && psz && *psz )
            {
123 124 125 126 127 128 129 130 131 132 133 134
                char **ppsz_select = &p_sys->ppsz_select[p_sys->i_nb_select - 1];

                if( *ppsz_select )
                {
                    msg_Err( p_stream, " * ignore selection `%s' (it already has `%s')",
                             psz, *ppsz_select );
                }
                else
                {
                    msg_Dbg( p_stream, " * apply selection `%s'", psz );
                    *ppsz_select = strdup( psz );
                }
135
            }
136
        }
137 138 139 140
        else
        {
            msg_Err( p_stream, " * ignore unknown option `%s'", p_cfg->psz_name );
        }
141 142 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
    }

    if( p_sys->i_nb_streams == 0 )
    {
        msg_Err( p_stream, "no destination given" );
        free( p_sys );

        return VLC_EGENERIC;
    }

    p_stream->pf_add    = Add;
    p_stream->pf_del    = Del;
    p_stream->pf_send   = Send;

    p_stream->p_sys     = p_sys;

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
    sout_stream_t     *p_stream = (sout_stream_t*)p_this;
    sout_stream_sys_t *p_sys = p_stream->p_sys;

168
    msg_Dbg( p_stream, "closing a duplication" );
169
    for( int i = 0; i < p_sys->i_nb_streams; i++ )
170
    {
171
        sout_StreamChainDelete(p_sys->pp_streams[i], p_sys->pp_last_streams[i]);
172
        free( p_sys->ppsz_select[i] );
173
    }
174
    free( p_sys->pp_streams );
175
    free( p_sys->pp_last_streams );
176
    free( p_sys->ppsz_select );
177 178 179 180

    free( p_sys );
}

181 182 183
/*****************************************************************************
 * Add:
 *****************************************************************************/
184
static sout_stream_id_sys_t * Add( sout_stream_t *p_stream, const es_format_t *p_fmt )
185 186
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
187
    sout_stream_id_sys_t  *id;
188
    int i_stream, i_valid_streams = 0;
189

190
    id = malloc( sizeof( sout_stream_id_sys_t ) );
191 192 193
    if( !id )
        return NULL;

194
    TAB_INIT( id->i_nb_ids, id->pp_ids );
195

196
    msg_Dbg( p_stream, "duplicated a new stream codec=%4.4s (es=%d group=%d)",
197 198
             (char*)&p_fmt->i_codec, p_fmt->i_id, p_fmt->i_group );

199 200
    for( i_stream = 0; i_stream < p_sys->i_nb_streams; i_stream++ )
    {
201
        void *id_new = NULL;
202

Laurent Aimar's avatar
Laurent Aimar committed
203
        if( ESSelected( p_fmt, p_sys->ppsz_select[i_stream] ) )
204 205 206
        {
            sout_stream_t *out = p_sys->pp_streams[i_stream];

207
            id_new = (void*)sout_StreamIdAdd( out, p_fmt );
208 209 210 211 212 213 214 215 216 217 218 219 220 221
            if( id_new )
            {
                msg_Dbg( p_stream, "    - added for output %d", i_stream );
                i_valid_streams++;
            }
            else
            {
                msg_Dbg( p_stream, "    - failed for output %d", i_stream );
            }
        }
        else
        {
            msg_Dbg( p_stream, "    - ignored for output %d", i_stream );
        }
222 223

        /* Append failed attempts as well to keep track of which pp_id
224
         * belongs to which duplicated stream */
225
        TAB_APPEND( id->i_nb_ids, id->pp_ids, id_new );
226 227
    }

228
    if( i_valid_streams <= 0 )
229
    {
230
        Del( p_stream, id );
231 232 233 234 235 236
        return NULL;
    }

    return id;
}

237 238 239
/*****************************************************************************
 * Del:
 *****************************************************************************/
240
static void Del( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
241 242 243 244 245 246
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    int               i_stream;

    for( i_stream = 0; i_stream < p_sys->i_nb_streams; i_stream++ )
    {
247
        if( id->pp_ids[i_stream] )
248
        {
249
            sout_stream_t *out = p_sys->pp_streams[i_stream];
250
            sout_StreamIdDel( out, id->pp_ids[i_stream] );
251 252 253 254 255 256 257
        }
    }

    free( id->pp_ids );
    free( id );
}

258 259 260
/*****************************************************************************
 * Send:
 *****************************************************************************/
261
static int Send( sout_stream_t *p_stream, sout_stream_id_sys_t *id,
262
                 block_t *p_buffer )
263 264
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
265
    sout_stream_t     *p_dup_stream;
266 267
    int               i_stream;

268 269
    /* Loop through the linked list of buffers */
    while( p_buffer )
270
    {
271
        block_t *p_next = p_buffer->p_next;
272

273 274 275
        p_buffer->p_next = NULL;

        for( i_stream = 0; i_stream < p_sys->i_nb_streams - 1; i_stream++ )
276
        {
277 278 279 280
            p_dup_stream = p_sys->pp_streams[i_stream];

            if( id->pp_ids[i_stream] )
            {
281
                block_t *p_dup = block_Duplicate( p_buffer );
282

283
                if( p_dup )
284
                    sout_StreamIdSend( p_dup_stream, id->pp_ids[i_stream], p_dup );
285
            }
286 287
        }

288 289 290
        if( i_stream < p_sys->i_nb_streams && id->pp_ids[i_stream] )
        {
            p_dup_stream = p_sys->pp_streams[i_stream];
291
            sout_StreamIdSend( p_dup_stream, id->pp_ids[i_stream], p_buffer );
292 293 294
        }
        else
        {
295
            block_Release( p_buffer );
296
        }
297

298 299
        p_buffer = p_next;
    }
300 301
    return VLC_SUCCESS;
}
302 303 304 305

/*****************************************************************************
 * Divers
 *****************************************************************************/
306
static bool NumInRange( const char *psz_range, int i_num )
307
{
308 309 310 311 312 313 314 315
    int beginRange, endRange;
    int res = sscanf(psz_range, "%d-%d", &beginRange, &endRange);
    if (res == 0)
        return false;
    else if (res == 1)
        return beginRange == i_num;
    return (i_num >= beginRange && i_num <= endRange)
        || (beginRange > endRange && (i_num <= beginRange && i_num >= endRange));
316 317
}

318
static bool ESSelected( const es_format_t *fmt, char *psz_select )
319 320 321 322
{
    char  *psz_dup;
    char  *psz;

Antoine Cellerier's avatar
Antoine Cellerier committed
323
    /* We have tri-state variable : no tested (-1), failed(0), succeed(1) */
324 325 326 327 328 329 330
    int i_cat = -1;
    int i_es  = -1;
    int i_prgm= -1;

    /* If empty all es are selected */
    if( psz_select == NULL || *psz_select == '\0' )
    {
331
        return true;
332 333
    }
    psz_dup = strdup( psz_select );
334 335 336
    if( !psz_dup )
        return false;
    psz = psz_dup;
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369

    /* If non empty, parse the selection:
     * We have selection[,selection[,..]] where following selection are recognized:
     *      (no(-))audio
     *      (no(-))spu
     *      (no(-))video
     *      (no(-))es=[start]-[end] or es=num
     *      (no(-))prgm=[start]-[end] or prgm=num (program works too)
     *      if a negative test failed we exit directly
     */
    while( psz && *psz )
    {
        char *p;

        /* Skip space */
        while( *psz == ' ' || *psz == '\t' ) psz++;

        /* Search end */
        p = strchr( psz, ',' );
        if( p == psz )
        {
            /* Empty */
            psz = p + 1;
            continue;
        }
        if( p )
        {
            *p++ = '\0';
        }

        if( !strncmp( psz, "no-audio", strlen( "no-audio" ) ) ||
            !strncmp( psz, "noaudio", strlen( "noaudio" ) ) )
        {
370
            if( i_cat == -1 )
371
            {
372
                i_cat = fmt->i_cat != AUDIO_ES ? 1 : 0;
373 374 375 376 377
            }
        }
        else if( !strncmp( psz, "no-video", strlen( "no-video" ) ) ||
                 !strncmp( psz, "novideo", strlen( "novideo" ) ) )
        {
378
            if( i_cat == -1 )
379
            {
380
                i_cat = fmt->i_cat != VIDEO_ES ? 1 : 0;
381 382 383 384 385
            }
        }
        else if( !strncmp( psz, "no-spu", strlen( "no-spu" ) ) ||
                 !strncmp( psz, "nospu", strlen( "nospu" ) ) )
        {
386
            if( i_cat == -1 )
387
            {
388
                i_cat = fmt->i_cat != SPU_ES ? 1 : 0;
389 390 391 392
            }
        }
        else if( !strncmp( psz, "audio", strlen( "audio" ) ) )
        {
393
            if( i_cat == -1 )
394 395 396 397 398 399
            {
                i_cat = fmt->i_cat == AUDIO_ES ? 1 : 0;
            }
        }
        else if( !strncmp( psz, "video", strlen( "video" ) ) )
        {
400
            if( i_cat == -1 )
401 402 403 404 405 406
            {
                i_cat = fmt->i_cat == VIDEO_ES ? 1 : 0;
            }
        }
        else if( !strncmp( psz, "spu", strlen( "spu" ) ) )
        {
407
            if( i_cat == -1 )
408 409 410 411 412 413 414 415 416
            {
                i_cat = fmt->i_cat == SPU_ES ? 1 : 0;
            }
        }
        else if( strchr( psz, '=' ) != NULL )
        {
            char *psz_arg = strchr( psz, '=' );
            *psz_arg++ = '\0';

417
            if( !strcmp( psz, "no-es" ) || !strcmp( psz, "noes" ) )
418
            {
419
                if( i_es == -1 )
420
                {
421
                    i_es = NumInRange( psz_arg, fmt->i_id ) ? 0 : -1;
422 423 424 425
                }
            }
            else if( !strcmp( psz, "es" ) )
            {
426
                if( i_es == -1 )
427
                {
428
                    i_es = NumInRange( psz_arg, fmt->i_id) ? 1 : -1;
429 430 431 432 433
                }
            }
            else if( !strcmp( psz, "no-prgm" ) || !strcmp( psz, "noprgm" ) ||
                      !strcmp( psz, "no-program" ) || !strcmp( psz, "noprogram" ) )
            {
434
                if( fmt->i_group >= 0 && i_prgm == -1 )
435
                {
436
                    i_prgm = NumInRange( psz_arg, fmt->i_group ) ? 0 : -1;
437 438
                }
            }
439
            else if( !strcmp( psz, "prgm" ) || !strcmp( psz, "program" ) )
440
            {
441
                if( fmt->i_group >= 0 && i_prgm == -1 )
442
                {
443
                    i_prgm = NumInRange( psz_arg, fmt->i_group ) ? 1 : -1;
444 445 446 447 448 449 450 451 452 453 454 455 456
                }
            }
        }
        else
        {
            fprintf( stderr, "unknown args (%s)\n", psz );
        }
        /* Next */
        psz = p;
    }

    free( psz_dup );

457
    if( i_cat == 1 || i_es == 1 || i_prgm == 1 )
458
    {
459
        return true;
460
    }
461
    return false;
462
}