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