21302f5e4634d2d7cddb23f34f8875fa5960a2ac
[vlc.git] / modules / stream_out / duplicate.c
1 /*****************************************************************************
2  * duplicate.c: duplicate stream output module
3  *****************************************************************************
4  * Copyright (C) 2003-2004 the VideoLAN team
5  *
6  * Author: Laurent Aimar <fenrir@via.ecp.fr>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 /*****************************************************************************
24  * Preamble
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_sout.h>
34 #include <vlc_block.h>
35
36 /*****************************************************************************
37  * Module descriptor
38  *****************************************************************************/
39 static int      Open    ( vlc_object_t * );
40 static void     Close   ( vlc_object_t * );
41
42 vlc_module_begin ()
43     set_description( N_("Duplicate stream output") )
44     set_capability( "sout output", 50 )
45     add_shortcut( "duplicate", "dup" )
46     set_category( CAT_SOUT )
47     set_subcategory( SUBCAT_SOUT_STREAM )
48     set_callbacks( Open, Close )
49     add_submodule()
50     set_capability("sout filter", 0)
51     add_shortcut("duplicate", "dup")
52     set_callbacks(Open, Close)
53 vlc_module_end ()
54
55
56 /*****************************************************************************
57  * Exported prototypes
58  *****************************************************************************/
59 static void *Add( sout_stream_t *, const es_format_t * );
60 static void  Del( sout_stream_t *, void * );
61 static int   Send( sout_stream_t *, void *, block_t * );
62
63 typedef struct
64 {
65     int             i_nb_streams;
66     sout_stream_t   **pp_streams;
67
68     int             i_nb_select;
69     char            **ppsz_select;
70 } sout_stream_sys_t;
71
72 typedef struct
73 {
74     int                 i_nb_ids;
75     void                **pp_ids;
76 } sout_stream_id_sys_t;
77
78 static bool ESSelected( struct vlc_logger *, const es_format_t *fmt,
79                         char *psz_select );
80
81 /*****************************************************************************
82  * Control
83  *****************************************************************************/
84 static int Control( sout_stream_t *p_stream, int i_query, va_list args )
85 {
86     sout_stream_sys_t *p_sys = p_stream->p_sys;
87
88     /* Fanout controls */
89     switch( i_query )
90     {
91         case SOUT_STREAM_ID_SPU_HIGHLIGHT:
92         {
93             sout_stream_id_sys_t *id = va_arg(args, void *);
94             void *spu_hl = va_arg(args, void *);
95             for( int i = 0; i < id->i_nb_ids; i++ )
96             {
97                 if( id->pp_ids[i] )
98                     sout_StreamControl( p_sys->pp_streams[i], i_query,
99                                         id->pp_ids[i], spu_hl );
100             }
101             return VLC_SUCCESS;
102         }
103     }
104
105     return VLC_EGENERIC;
106 }
107
108 static const struct sout_stream_operations ops = {
109     Add, Del, Send, Control, NULL,
110 };
111
112 /*****************************************************************************
113  * Open:
114  *****************************************************************************/
115 static int Open( vlc_object_t *p_this )
116 {
117     sout_stream_t     *p_stream = (sout_stream_t*)p_this;
118     sout_stream_sys_t *p_sys;
119     config_chain_t        *p_cfg;
120
121     msg_Dbg( p_stream, "creating 'duplicate'" );
122
123     p_sys = malloc( sizeof( sout_stream_sys_t ) );
124     if( !p_sys )
125         return VLC_ENOMEM;
126
127     TAB_INIT( p_sys->i_nb_streams, p_sys->pp_streams );
128     TAB_INIT( p_sys->i_nb_select, p_sys->ppsz_select );
129
130     char **ppsz_select = NULL;
131
132     for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
133     {
134         if( !strncmp( p_cfg->psz_name, "dst", strlen( "dst" ) ) )
135         {
136             sout_stream_t *s;
137
138             msg_Dbg( p_stream, " * adding `%s'", p_cfg->psz_value );
139             s = sout_StreamChainNew( VLC_OBJECT(p_stream), p_cfg->psz_value,
140                 p_stream->p_next );
141
142             if( s )
143             {
144                 TAB_APPEND( p_sys->i_nb_streams, p_sys->pp_streams, s );
145                 TAB_APPEND( p_sys->i_nb_select,  p_sys->ppsz_select, NULL );
146                 ppsz_select = &p_sys->ppsz_select[p_sys->i_nb_select - 1];
147             }
148         }
149         else if( !strncmp( p_cfg->psz_name, "select", strlen( "select" ) ) )
150         {
151             char *psz = p_cfg->psz_value;
152
153             if( psz && *psz )
154             {
155                 if( ppsz_select == NULL )
156                 {
157                     msg_Err( p_stream, " * ignore selection `%s'", psz );
158                 }
159                 else
160                 {
161                     msg_Dbg( p_stream, " * apply selection `%s'", psz );
162                     *ppsz_select = strdup( psz );
163                     ppsz_select = NULL;
164                 }
165             }
166         }
167         else
168         {
169             msg_Err( p_stream, " * ignore unknown option `%s'", p_cfg->psz_name );
170         }
171     }
172
173     if( p_sys->i_nb_streams == 0 )
174     {
175         msg_Err( p_stream, "no destination given" );
176         free( p_sys );
177
178         return VLC_EGENERIC;
179     }
180
181     p_stream->p_sys = p_sys;
182     p_stream->ops = &ops;
183     return VLC_SUCCESS;
184 }
185
186 /*****************************************************************************
187  * Close:
188  *****************************************************************************/
189 static void Close( vlc_object_t * p_this )
190 {
191     sout_stream_t     *p_stream = (sout_stream_t*)p_this;
192     sout_stream_sys_t *p_sys = p_stream->p_sys;
193
194     msg_Dbg( p_stream, "closing a duplication" );
195     for( int i = 0; i < p_sys->i_nb_streams; i++ )
196     {
197         sout_StreamChainDelete(p_sys->pp_streams[i], p_stream->p_next);
198         free( p_sys->ppsz_select[i] );
199     }
200     free( p_sys->pp_streams );
201     free( p_sys->ppsz_select );
202
203     free( p_sys );
204 }
205
206 /*****************************************************************************
207  * Add:
208  *****************************************************************************/
209 static void *Add( sout_stream_t *p_stream, const es_format_t *p_fmt )
210 {
211     sout_stream_sys_t *p_sys = p_stream->p_sys;
212     sout_stream_id_sys_t  *id;
213     int i_stream, i_valid_streams = 0;
214
215     id = malloc( sizeof( sout_stream_id_sys_t ) );
216     if( !id )
217         return NULL;
218
219     TAB_INIT( id->i_nb_ids, id->pp_ids );
220
221     msg_Dbg( p_stream, "duplicated a new stream codec=%4.4s (es=%d group=%d)",
222              (char*)&p_fmt->i_codec, p_fmt->i_id, p_fmt->i_group );
223
224     for( i_stream = 0; i_stream < p_sys->i_nb_streams; i_stream++ )
225     {
226         void *id_new = NULL;
227
228         if( ESSelected( p_stream->obj.logger, p_fmt,
229                         p_sys->ppsz_select[i_stream] ) )
230         {
231             sout_stream_t *out = p_sys->pp_streams[i_stream];
232
233             id_new = (void*)sout_StreamIdAdd( out, p_fmt );
234             if( id_new )
235             {
236                 msg_Dbg( p_stream, "    - added for output %d", i_stream );
237                 i_valid_streams++;
238             }
239             else
240             {
241                 msg_Dbg( p_stream, "    - failed for output %d", i_stream );
242             }
243         }
244         else
245         {
246             msg_Dbg( p_stream, "    - ignored for output %d", i_stream );
247         }
248
249         /* Append failed attempts as well to keep track of which pp_id
250          * belongs to which duplicated stream */
251         TAB_APPEND( id->i_nb_ids, id->pp_ids, id_new );
252     }
253
254     if( i_valid_streams <= 0 )
255     {
256         Del( p_stream, id );
257         return NULL;
258     }
259
260     return id;
261 }
262
263 /*****************************************************************************
264  * Del:
265  *****************************************************************************/
266 static void Del( sout_stream_t *p_stream, void *_id )
267 {
268     sout_stream_sys_t *p_sys = p_stream->p_sys;
269     sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)_id;
270     int               i_stream;
271
272     for( i_stream = 0; i_stream < p_sys->i_nb_streams; i_stream++ )
273     {
274         if( id->pp_ids[i_stream] )
275         {
276             sout_stream_t *out = p_sys->pp_streams[i_stream];
277             sout_StreamIdDel( out, id->pp_ids[i_stream] );
278         }
279     }
280
281     free( id->pp_ids );
282     free( id );
283 }
284
285 /*****************************************************************************
286  * Send:
287  *****************************************************************************/
288 static int Send( sout_stream_t *p_stream, void *_id, block_t *p_buffer )
289 {
290     sout_stream_sys_t *p_sys = p_stream->p_sys;
291     sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)_id;
292     sout_stream_t     *p_dup_stream;
293     int               i_stream;
294
295     /* Loop through the linked list of buffers */
296     while( p_buffer )
297     {
298         block_t *p_next = p_buffer->p_next;
299
300         p_buffer->p_next = NULL;
301
302         for( i_stream = 0; i_stream < p_sys->i_nb_streams - 1; i_stream++ )
303         {
304             p_dup_stream = p_sys->pp_streams[i_stream];
305
306             if( id->pp_ids[i_stream] )
307             {
308                 block_t *p_dup = block_Duplicate( p_buffer );
309
310                 if( p_dup )
311                     sout_StreamIdSend( p_dup_stream, id->pp_ids[i_stream], p_dup );
312             }
313         }
314
315         if( i_stream < p_sys->i_nb_streams && id->pp_ids[i_stream] )
316         {
317             p_dup_stream = p_sys->pp_streams[i_stream];
318             sout_StreamIdSend( p_dup_stream, id->pp_ids[i_stream], p_buffer );
319         }
320         else
321         {
322             block_Release( p_buffer );
323         }
324
325         p_buffer = p_next;
326     }
327     return VLC_SUCCESS;
328 }
329
330 /*****************************************************************************
331  * Divers
332  *****************************************************************************/
333 static bool NumInRange( const char *psz_range, int i_num )
334 {
335     int beginRange, endRange;
336     int res = sscanf(psz_range, "%d-%d", &beginRange, &endRange);
337     if (res == 0)
338         return false;
339     else if (res == 1)
340         return beginRange == i_num;
341     return (i_num >= beginRange && i_num <= endRange)
342         || (beginRange > endRange && (i_num <= beginRange && i_num >= endRange));
343 }
344
345 static bool ESSelected( struct vlc_logger *logger, const es_format_t *fmt,
346                         char *psz_select )
347 {
348     char  *psz_dup;
349     char  *psz;
350
351     /* We have tri-state variable : no tested (-1), failed(0), succeed(1) */
352     int i_cat = -1;
353     int i_es  = -1;
354     int i_prgm= -1;
355
356     /* If empty all es are selected */
357     if( psz_select == NULL || *psz_select == '\0' )
358     {
359         return true;
360     }
361     psz_dup = strdup( psz_select );
362     if( !psz_dup )
363         return false;
364     psz = psz_dup;
365
366     /* If non empty, parse the selection:
367      * We have selection[,selection[,..]] where following selection are recognized:
368      *      (no(-))audio
369      *      (no(-))spu
370      *      (no(-))video
371      *      (no(-))es=[start]-[end] or es=num
372      *      (no(-))prgm=[start]-[end] or prgm=num (program works too)
373      *      if a negative test failed we exit directly
374      */
375     while( psz && *psz )
376     {
377         char *p;
378
379         /* Skip space */
380         while( *psz == ' ' || *psz == '\t' ) psz++;
381
382         /* Search end */
383         p = strchr( psz, ',' );
384         if( p == psz )
385         {
386             /* Empty */
387             psz = p + 1;
388             continue;
389         }
390         if( p )
391         {
392             *p++ = '\0';
393         }
394
395         if( !strncmp( psz, "no-audio", strlen( "no-audio" ) ) ||
396             !strncmp( psz, "noaudio", strlen( "noaudio" ) ) )
397         {
398             if( i_cat == -1 )
399             {
400                 i_cat = fmt->i_cat != AUDIO_ES ? 1 : 0;
401             }
402         }
403         else if( !strncmp( psz, "no-video", strlen( "no-video" ) ) ||
404                  !strncmp( psz, "novideo", strlen( "novideo" ) ) )
405         {
406             if( i_cat == -1 )
407             {
408                 i_cat = fmt->i_cat != VIDEO_ES ? 1 : 0;
409             }
410         }
411         else if( !strncmp( psz, "no-spu", strlen( "no-spu" ) ) ||
412                  !strncmp( psz, "nospu", strlen( "nospu" ) ) )
413         {
414             if( i_cat == -1 )
415             {
416                 i_cat = fmt->i_cat != SPU_ES ? 1 : 0;
417             }
418         }
419         else if( !strncmp( psz, "audio", strlen( "audio" ) ) )
420         {
421             if( i_cat == -1 )
422             {
423                 i_cat = fmt->i_cat == AUDIO_ES ? 1 : 0;
424             }
425         }
426         else if( !strncmp( psz, "video", strlen( "video" ) ) )
427         {
428             if( i_cat == -1 )
429             {
430                 i_cat = fmt->i_cat == VIDEO_ES ? 1 : 0;
431             }
432         }
433         else if( !strncmp( psz, "spu", strlen( "spu" ) ) )
434         {
435             if( i_cat == -1 )
436             {
437                 i_cat = fmt->i_cat == SPU_ES ? 1 : 0;
438             }
439         }
440         else if( strchr( psz, '=' ) != NULL )
441         {
442             char *psz_arg = strchr( psz, '=' );
443             *psz_arg++ = '\0';
444
445             if( !strcmp( psz, "no-es" ) || !strcmp( psz, "noes" ) )
446             {
447                 if( i_es == -1 )
448                 {
449                     i_es = NumInRange( psz_arg, fmt->i_id ) ? 0 : -1;
450                 }
451             }
452             else if( !strcmp( psz, "es" ) )
453             {
454                 if( i_es == -1 )
455                 {
456                     i_es = NumInRange( psz_arg, fmt->i_id) ? 1 : -1;
457                 }
458             }
459             else if( !strcmp( psz, "no-prgm" ) || !strcmp( psz, "noprgm" ) ||
460                       !strcmp( psz, "no-program" ) || !strcmp( psz, "noprogram" ) )
461             {
462                 if( fmt->i_group >= 0 && i_prgm == -1 )
463                 {
464                     i_prgm = NumInRange( psz_arg, fmt->i_group ) ? 0 : -1;
465                 }
466             }
467             else if( !strcmp( psz, "prgm" ) || !strcmp( psz, "program" ) )
468             {
469                 if( fmt->i_group >= 0 && i_prgm == -1 )
470                 {
471                     i_prgm = NumInRange( psz_arg, fmt->i_group ) ? 1 : -1;
472                 }
473             }
474         }
475         else
476         {
477             vlc_error( logger, "unknown args (%s)", psz );
478         }
479         /* Next */
480         psz = p;
481     }
482
483     free( psz_dup );
484
485     if( i_cat == 1 || i_es == 1 || i_prgm == 1 )
486     {
487         return true;
488     }
489     return false;
490 }