]> git.sesse.net Git - vlc/blob - modules/stream_out/record.c
Qt4: find skins interface by name
[vlc] / modules / stream_out / record.c
1 /*****************************************************************************
2  * record.c: record stream output module
3  *****************************************************************************
4  * Copyright (C) 2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: 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
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <limits.h>
33
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_block.h>
37 #include <vlc_sout.h>
38 #include <vlc_charset.h>
39 #include <assert.h>
40
41 /*****************************************************************************
42  * Exported prototypes
43  *****************************************************************************/
44 static int      Open    ( vlc_object_t * );
45 static void     Close   ( vlc_object_t * );
46
47 /*****************************************************************************
48  * Module descriptor
49  *****************************************************************************/
50 #define DST_PREFIX_TEXT N_("Destination prefix")
51 #define DST_PREFIX_LONGTEXT N_( \
52     "Prefix of the destination file automatically generated" )
53
54 #define SOUT_CFG_PREFIX "sout-record-"
55
56 vlc_module_begin ()
57     set_description( N_("Record stream output") )
58     set_capability( "sout stream", 0 )
59     add_shortcut( "record" )
60
61     set_category( CAT_SOUT )
62     set_subcategory( SUBCAT_SOUT_STREAM )
63
64     add_string( SOUT_CFG_PREFIX "dst-prefix", "", NULL, DST_PREFIX_TEXT,
65                 DST_PREFIX_LONGTEXT, true );
66
67     set_callbacks( Open, Close )
68 vlc_module_end ()
69
70 /* */
71 static const char *const ppsz_sout_options[] = {
72     "dst-prefix",
73     NULL
74 };
75
76 /* */
77 static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * );
78 static int               Del ( sout_stream_t *, sout_stream_id_t * );
79 static int               Send( sout_stream_t *, sout_stream_id_t *, block_t* );
80
81 /* */
82 struct sout_stream_id_t
83 {
84     es_format_t fmt;
85
86     block_t *p_first;
87     block_t **pp_last;
88
89     sout_stream_id_t *id;
90
91     bool b_wait_key;
92     bool b_wait_start;
93 };
94
95 struct sout_stream_sys_t
96 {
97     char *psz_prefix;
98
99     sout_stream_t *p_out;
100
101     mtime_t     i_date_start;
102     size_t      i_size;
103
104     mtime_t     i_max_wait;
105     size_t      i_max_size;
106
107     bool        b_drop;
108
109     int              i_id;
110     sout_stream_id_t **id;
111     mtime_t     i_dts_start;
112 };
113
114 static void OutputStart( sout_stream_t *p_stream );
115 static void OutputSend( sout_stream_t *p_stream, sout_stream_id_t *id, block_t * );
116
117 /*****************************************************************************
118  * Open:
119  *****************************************************************************/
120 static int Open( vlc_object_t *p_this )
121 {
122     sout_stream_t *p_stream = (sout_stream_t*)p_this;
123     sout_stream_sys_t *p_sys;
124
125     p_stream->pf_add    = Add;
126     p_stream->pf_del    = Del;
127     p_stream->pf_send   = Send;
128
129     p_stream->p_sys = p_sys = malloc( sizeof(*p_sys) );
130     if( !p_sys )
131         return VLC_ENOMEM;
132
133     config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
134
135     p_sys->p_out = NULL;
136     p_sys->psz_prefix = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "dst-prefix" );
137     if( !p_sys->psz_prefix  )
138     {
139         p_sys->psz_prefix = strdup( "sout-record-" );
140         if( !p_sys->psz_prefix )
141         {
142             free( p_sys );
143             return VLC_ENOMEM;
144         }
145     }
146
147     p_sys->i_date_start = -1;
148     p_sys->i_size = 0;
149 #ifdef OPTIMIZE_MEMORY
150     p_sys->i_max_wait = 5*1000000; /* 5s */
151     p_sys->i_max_size = 1*1000000; /* 1 Mbyte */
152 #else
153     p_sys->i_max_wait = 30*1000000; /* 30s */
154     p_sys->i_max_size = 20*1000000; /* 20 Mbyte */
155 #endif
156     p_sys->b_drop = false;
157     p_sys->i_dts_start = 0;
158     TAB_INIT( p_sys->i_id, p_sys->id );
159
160     return VLC_SUCCESS;
161 }
162
163 /*****************************************************************************
164  * Close:
165  *****************************************************************************/
166 static void Close( vlc_object_t * p_this )
167 {
168     sout_stream_t *p_stream = (sout_stream_t*)p_this;
169     sout_stream_sys_t *p_sys = p_stream->p_sys;
170
171     if( p_sys->p_out )
172         sout_StreamDelete( p_sys->p_out );
173
174     TAB_CLEAN( p_sys->i_id, p_sys->id );
175     free( p_sys->psz_prefix );
176     free( p_sys );
177 }
178
179 /*****************************************************************************
180  *
181  *****************************************************************************/
182 static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
183 {
184     sout_stream_sys_t *p_sys = p_stream->p_sys;
185     sout_stream_id_t *id;
186
187     id = malloc( sizeof(*id) );
188     if( !id )
189         return NULL;
190
191     es_format_Copy( &id->fmt, p_fmt );
192     id->p_first = NULL;
193     id->pp_last = &id->p_first;
194     id->id = NULL;
195     id->b_wait_key = true;
196     id->b_wait_start = true;
197
198     TAB_APPEND( p_sys->i_id, p_sys->id, id );
199
200     return id;
201 }
202
203 static int Del( sout_stream_t *p_stream, sout_stream_id_t *id )
204 {
205     sout_stream_sys_t *p_sys = p_stream->p_sys;
206
207     if( !p_sys->p_out )
208         OutputStart( p_stream );
209
210     if( id->p_first )
211         block_ChainRelease( id->p_first );
212     es_format_Clean( &id->fmt );
213
214     assert( !id->id || p_sys->p_out );
215     if( id->id )
216         sout_StreamIdDel( p_sys->p_out, id->id );
217
218     TAB_REMOVE( p_sys->i_id, p_sys->id, id );
219
220     if( p_sys->i_id <= 0 )
221     {
222         if( !p_sys->p_out )
223             p_sys->b_drop = false;
224     }
225
226     free( id );
227
228     return VLC_SUCCESS;
229 }
230
231 static int Send( sout_stream_t *p_stream, sout_stream_id_t *id,
232                  block_t *p_buffer )
233 {
234     sout_stream_sys_t *p_sys = p_stream->p_sys;
235
236     if( p_sys->i_date_start < 0 )
237         p_sys->i_date_start = mdate();
238     if( !p_sys->p_out &&
239         ( mdate() - p_sys->i_date_start > p_sys->i_max_wait ||
240           p_sys->i_size > p_sys->i_max_size ) )
241     {
242         msg_Dbg( p_stream, "Staring recording, waited %ds and %dbyte",
243                  (int)((mdate() - p_sys->i_date_start)/1000000), (int)p_sys->i_size );
244         OutputStart( p_stream );
245     }
246
247     OutputSend( p_stream, id, p_buffer );
248
249     return VLC_SUCCESS;
250 }
251
252 /*****************************************************************************
253  *
254  *****************************************************************************/
255 typedef struct
256 {
257     const char  *psz_muxer;
258     const char  *psz_extension;
259     int         i_es_max;
260     vlc_fourcc_t codec[128];
261 } muxer_properties_t;
262
263 #define M(muxer, ext, count, ... ) { .psz_muxer = muxer, .psz_extension = ext, .i_es_max = count, .codec = { __VA_ARGS__, 0 } }
264 /* Table of native codec support,
265  * Do not do non native and non standard association !
266  * Muxer will be probe if no entry found */
267 static const muxer_properties_t p_muxers[] = {
268     M( "raw", "mp3", 1,         VLC_FOURCC('m','p','g','a') ),
269     M( "raw", "a52", 1,         VLC_FOURCC('a','5','2',' ') ),
270     M( "raw", "dts", 1,         VLC_FOURCC('d','t','s',' ') ),
271     M( "raw", "mpc", 1,         VLC_FOURCC('m','p','c',' ') ),
272     M( "raw", "ape", 1,         VLC_FOURCC('A','P','E',' ') ),
273
274     M( "wav", "wav", 1,         VLC_FOURCC('a','r','a','w'), VLC_FOURCC('u','8',' ',' '), VLC_FOURCC('s','1','6','l'),
275                                 VLC_FOURCC('s','2','4','l'), VLC_FOURCC('s','3','2','l'), VLC_FOURCC('f','l','3','2') ),
276
277     //M( "ffmpeg{mux=flac}", "flac", 1, VLC_FOURCC('f','l','a','c') ), BROKEN
278
279     M( "ogg", "ogg", INT_MAX,   VLC_FOURCC('v','o','r','b'), VLC_FOURCC('s','p','x',' '), VLC_FOURCC('f','l','a','c'),
280                                 VLC_FOURCC('s','u','b','t'), VLC_FOURCC('t','h','e','o'), VLC_FOURCC('d','r','a','c')  ),
281
282     M( "asf", "asf", 127,       VLC_FOURCC('w','m','a','1'), VLC_FOURCC('w','m','a','2'), VLC_FOURCC('w','m','a',' '),
283                                 VLC_FOURCC('w','m','a','p'), VLC_FOURCC('w','m','a','l'),
284                                 VLC_FOURCC('W','M','V','1'), VLC_FOURCC('W','M','V','2'), VLC_FOURCC('W','M','V','3') ),
285
286     M( "mp4", "mp4", INT_MAX,   VLC_FOURCC('m','p','4','a'), VLC_FOURCC('h','2','6','4'), VLC_FOURCC('m','p','4','v'),
287                                 VLC_FOURCC('s','u','b','t') ),
288
289     M( "ps", "ps", 16/* FIXME*/,VLC_FOURCC('m','p','g','v'), VLC_FOURCC('m','p','1','v'), VLC_FOURCC('m','p','2','v'),
290                                 VLC_FOURCC('m','p','g','a'), VLC_FOURCC('l','p','c','m'), VLC_FOURCC('a','5','2',' '),
291                                 VLC_FOURCC('d','t','s',' '),
292                                 VLC_FOURCC('s','p','u',' ') ),
293
294     M( "ts", "ts", 8000,        VLC_FOURCC('m','p','g','v'), VLC_FOURCC('m','p','1','v'), VLC_FOURCC('m','p','2','v'),
295                                 VLC_FOURCC('h','2','6','4'),
296                                 VLC_FOURCC('m','p','g','a'), VLC_FOURCC('l','p','c','m'), VLC_FOURCC('a','5','2',' '),
297                                 VLC_FOURCC('d','t','s',' '), VLC_FOURCC('m','p','4','a'),
298                                 VLC_FOURCC('d','v','b','s'), VLC_FOURCC('t','e','l','x') ),
299
300     M( NULL, NULL, 0, 0 )
301 };
302 #undef M
303
304 static int OutputNew( sout_stream_t *p_stream,
305                       const char *psz_muxer, const char *psz_prefix, const char *psz_extension  )
306 {
307     sout_stream_sys_t *p_sys = p_stream->p_sys;
308     char *psz_output;
309     int i_count;
310
311     if( asprintf( &psz_output, "std{access=file,mux='%s',dst='%s%s%s'}",
312                   psz_muxer, psz_prefix, psz_extension ? "." : "", psz_extension ? psz_extension : "" ) < 0 )
313         return -1;
314
315     /* Create the output */
316     msg_Dbg( p_stream, "Using record output `%s'", psz_output );
317     p_sys->p_out = sout_StreamNew( p_stream->p_sout, psz_output );
318
319     free( psz_output );
320
321     if( !p_sys->p_out )
322         return -1;
323
324     /* Add es */
325     i_count = 0;
326     for( int i = 0; i < p_sys->i_id; i++ )
327     {
328         sout_stream_id_t *id = p_sys->id[i];
329
330         id->id = sout_StreamIdAdd( p_sys->p_out, &id->fmt );
331         if( id->id )
332             i_count++;
333     }
334
335     return i_count;
336 }
337
338 static void OutputStart( sout_stream_t *p_stream )
339 {
340     sout_stream_sys_t *p_sys = p_stream->p_sys;
341
342     /* */
343     if( p_sys->b_drop )
344         return;
345
346     /* From now on drop packet that cannot be handled */
347     p_sys->b_drop = true;
348
349     /* Detect streams to smart select muxer */
350     const char *psz_muxer = NULL;
351     const char *psz_extension = NULL;
352
353     /* Look for prefered muxer
354      * TODO we could insert transcode in a few cases like
355      * s16l <-> s16b
356      */
357     for( int i = 0; p_muxers[i].psz_muxer != NULL; i++ )
358     {
359         bool b_ok;
360         if( p_sys->i_id > p_muxers[i].i_es_max )
361             continue;
362
363         b_ok = true;
364         for( int j = 0; j < p_sys->i_id; j++ )
365         {
366             es_format_t *p_fmt = &p_sys->id[j]->fmt;
367
368             b_ok = false;
369             for( int k = 0; p_muxers[i].codec[k] != 0; k++ )
370             {
371                 if( p_fmt->i_codec == p_muxers[i].codec[k] )
372                 {
373                     b_ok = true;
374                     break;
375                 }
376             }
377             if( !b_ok )
378                 break;
379         }
380         if( !b_ok )
381             continue;
382
383         psz_muxer = p_muxers[i].psz_muxer;
384         psz_extension = p_muxers[i].psz_extension;
385         break;
386     }
387
388     /* If failed, brute force our demuxers and select the one that
389      * keeps most of our stream */
390     if( !psz_muxer || !psz_extension )
391     {
392         static const char *ppsz_muxers[][2] = {
393             { "avi", "avi" }, { "mp4", "mp4" }, { "ogg", "ogg" },
394             { "asf", "asf" }, {  "ts",  "ts" }, {  "ps",  "ps" },
395 #if 0
396             // XXX ffmpeg sefault really easily if you try an unsupported codec
397             // mov and avi at least segfault
398             { "ffmpeg{mux=avi}", "avi" },
399             { "ffmpeg{mux=mov}", "mov" },
400             { "ffmpeg{mux=mp4}", "mp4" },
401             { "ffmpeg{mux=nsv}", "nsv" },
402             { "ffmpeg{mux=flv}", "flv" },
403 #endif
404             { NULL, NULL }
405         };
406         int i_best = 0;
407         int i_best_es = 0;
408
409         msg_Warn( p_stream, "failed to find an adequate muxer, probing muxers" );
410         for( int i = 0; ppsz_muxers[i][0] != NULL; i++ )
411         {
412             char *psz_file;
413             int i_es;
414
415             psz_file = tempnam( NULL, "vlc" );
416             if( !psz_file )
417                 continue;
418
419             msg_Dbg( p_stream, "probing muxer %s", ppsz_muxers[i][0] );
420             i_es = OutputNew( p_stream, ppsz_muxers[i][0], psz_file, NULL );
421
422             if( i_es < 0 )
423             {
424                 utf8_unlink( psz_file );
425                 free( psz_file );
426                 continue;
427             }
428
429             /* */
430             for( int i = 0; i < p_sys->i_id; i++ )
431             {
432                 sout_stream_id_t *id = p_sys->id[i];
433
434                 if( id->id )
435                     sout_StreamIdDel( p_sys->p_out, id->id );
436                 id->id = NULL;
437             }
438             if( p_sys->p_out )
439                 sout_StreamDelete( p_sys->p_out );
440             p_sys->p_out = NULL;
441
442             if( i_es > i_best_es )
443             {
444                 i_best_es = i_es;
445                 i_best = i;
446
447                 if( i_best_es >= p_sys->i_id )
448                     break;
449             }
450             utf8_unlink( psz_file );
451             free( psz_file );
452         }
453
454         /* */
455         psz_muxer = ppsz_muxers[i_best][0];
456         psz_extension = ppsz_muxers[i_best][1];
457         msg_Dbg( p_stream, "using muxer %s with extension %s (%d/%d streams accepted)",
458                  psz_muxer, psz_extension, i_best_es, p_sys->i_id );
459     }
460
461     /* Create the output */
462     if( OutputNew( p_stream, psz_muxer, p_sys->psz_prefix, psz_extension ) < 0 )
463     {
464         msg_Err( p_stream, "failed to open output");
465         return;
466     }
467
468     /* Compute highest timestamp of first I over all streams */
469     p_sys->i_dts_start = 0;
470     for( int i = 0; i < p_sys->i_id; i++ )
471     {
472         sout_stream_id_t *id = p_sys->id[i];
473         block_t *p_block;
474
475         if( !id->id || !id->p_first )
476             continue;
477
478         mtime_t i_dts = id->p_first->i_dts;
479         for( p_block = id->p_first; p_block != NULL; p_block = p_block->p_next )
480         {
481             if( p_block->i_flags & BLOCK_FLAG_TYPE_I )
482             {
483                 i_dts = p_block->i_dts;
484                 break;
485             }
486         }
487
488         if( i_dts > p_sys->i_dts_start )
489             p_sys->i_dts_start = i_dts;
490     }
491
492     /* Send buffered data */
493     for( int i = 0; i < p_sys->i_id; i++ )
494     {
495         sout_stream_id_t *id = p_sys->id[i];
496
497         if( !id->id )
498             continue;
499
500         block_t *p_block = id->p_first;
501         while( p_block )
502         {
503             block_t *p_next = p_block->p_next;
504
505             p_block->p_next = NULL;
506
507             OutputSend( p_stream, id, p_block );
508
509             p_block = p_next;
510         }
511
512         id->p_first = NULL;
513         id->pp_last = &id->p_first;
514     }
515 }
516
517 static void OutputSend( sout_stream_t *p_stream, sout_stream_id_t *id, block_t *p_block )
518 {
519     sout_stream_sys_t *p_sys = p_stream->p_sys;
520
521     if( id->id )
522     {
523         /* We wait until the first key frame (if needed) and
524          * to be beyong i_dts_start (for stream without key frame) */
525         if( id->b_wait_key )
526         {
527             if( p_block->i_flags & BLOCK_FLAG_TYPE_I )
528             {
529                 id->b_wait_key = false;
530                 id->b_wait_start = false;
531             }
532
533             if( ( p_block->i_flags & BLOCK_FLAG_TYPE_MASK ) == 0 )
534                 id->b_wait_key = false;
535         }
536         if( id->b_wait_start )
537         {
538             if( p_block->i_dts >=p_sys->i_dts_start )
539                 id->b_wait_start = false;
540         }
541         if( id->b_wait_key || id->b_wait_start )
542             block_ChainRelease( p_block );
543         else
544             sout_StreamIdSend( p_sys->p_out, id->id, p_block );
545     }
546     else if( p_sys->b_drop )
547     {
548         block_ChainRelease( p_block );
549     }
550     else
551     {
552         size_t i_size;
553
554         block_ChainProperties( p_block, NULL, &i_size, NULL );
555         p_sys->i_size += i_size;
556         block_ChainLastAppend( &id->pp_last, p_block );
557     }
558 }
559