]> git.sesse.net Git - vlc/blob - modules/stream_out/record.c
sout: chromecast: ues different wait times
[vlc] / modules / stream_out / record.c
1 /*****************************************************************************
2  * record.c: record stream output module
3  *****************************************************************************
4  * Copyright (C) 2008-2009 VLC authors and VideoLAN
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 it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * 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_fs.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     set_shortname( N_("Record") )
61
62     set_category( CAT_SOUT )
63     set_subcategory( SUBCAT_SOUT_STREAM )
64
65     add_string( SOUT_CFG_PREFIX "dst-prefix", "", DST_PREFIX_TEXT,
66                 DST_PREFIX_LONGTEXT, true )
67
68     set_callbacks( Open, Close )
69 vlc_module_end ()
70
71 /* */
72 static const char *const ppsz_sout_options[] = {
73     "dst-prefix",
74     NULL
75 };
76
77 /* */
78 static sout_stream_id_sys_t *Add ( sout_stream_t *, es_format_t * );
79 static int               Del ( sout_stream_t *, sout_stream_id_sys_t * );
80 static int               Send( sout_stream_t *, sout_stream_id_sys_t *, block_t* );
81
82 /* */
83 struct sout_stream_id_sys_t
84 {
85     es_format_t fmt;
86
87     block_t *p_first;
88     block_t **pp_last;
89
90     sout_stream_id_sys_t *id;
91
92     bool b_wait_key;
93     bool b_wait_start;
94 };
95
96 struct sout_stream_sys_t
97 {
98     char *psz_prefix;
99
100     sout_stream_t *p_out;
101
102     mtime_t     i_date_start;
103     size_t      i_size;
104
105     mtime_t     i_max_wait;
106     size_t      i_max_size;
107
108     bool        b_drop;
109
110     int              i_id;
111     sout_stream_id_sys_t **id;
112     mtime_t     i_dts_start;
113 };
114
115 static void OutputStart( sout_stream_t *p_stream );
116 static void OutputSend( sout_stream_t *p_stream, sout_stream_id_sys_t *id, block_t * );
117
118 /*****************************************************************************
119  * Open:
120  *****************************************************************************/
121 static int Open( vlc_object_t *p_this )
122 {
123     sout_stream_t *p_stream = (sout_stream_t*)p_this;
124     sout_stream_sys_t *p_sys;
125
126     p_stream->pf_add    = Add;
127     p_stream->pf_del    = Del;
128     p_stream->pf_send   = Send;
129
130     p_stream->p_sys = p_sys = malloc( sizeof(*p_sys) );
131     if( !p_sys )
132         return VLC_ENOMEM;
133
134     config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
135
136     p_sys->p_out = NULL;
137     p_sys->psz_prefix = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "dst-prefix" );
138     if( !p_sys->psz_prefix  )
139     {
140         p_sys->psz_prefix = strdup( "sout-record-" );
141         if( !p_sys->psz_prefix )
142         {
143             free( p_sys );
144             return VLC_ENOMEM;
145         }
146     }
147
148     p_sys->i_date_start = -1;
149     p_sys->i_size = 0;
150 #ifdef OPTIMIZE_MEMORY
151     p_sys->i_max_wait = 5*CLOCK_FREQ; /* 5s */
152     p_sys->i_max_size = 1*1024*1024; /* 1 MiB */
153 #else
154     p_sys->i_max_wait = 30*CLOCK_FREQ; /* 30s */
155     p_sys->i_max_size = 20*1024*1024; /* 20 MiB */
156 #endif
157     p_sys->b_drop = false;
158     p_sys->i_dts_start = 0;
159     TAB_INIT( p_sys->i_id, p_sys->id );
160
161     return VLC_SUCCESS;
162 }
163
164 /*****************************************************************************
165  * Close:
166  *****************************************************************************/
167 static void Close( vlc_object_t * p_this )
168 {
169     sout_stream_t *p_stream = (sout_stream_t*)p_this;
170     sout_stream_sys_t *p_sys = p_stream->p_sys;
171
172     if( p_sys->p_out )
173         sout_StreamChainDelete( p_sys->p_out, p_sys->p_out );
174
175     TAB_CLEAN( p_sys->i_id, p_sys->id );
176     free( p_sys->psz_prefix );
177     free( p_sys );
178 }
179
180 /*****************************************************************************
181  *
182  *****************************************************************************/
183 static sout_stream_id_sys_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
184 {
185     sout_stream_sys_t *p_sys = p_stream->p_sys;
186     sout_stream_id_sys_t *id;
187
188     id = malloc( sizeof(*id) );
189     if( !id )
190         return NULL;
191
192     es_format_Copy( &id->fmt, p_fmt );
193     id->p_first = NULL;
194     id->pp_last = &id->p_first;
195     id->id = NULL;
196     id->b_wait_key = true;
197     id->b_wait_start = true;
198
199     TAB_APPEND( p_sys->i_id, p_sys->id, id );
200
201     return id;
202 }
203
204 static int Del( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
205 {
206     sout_stream_sys_t *p_sys = p_stream->p_sys;
207
208     if( !p_sys->p_out )
209         OutputStart( p_stream );
210
211     if( id->p_first )
212         block_ChainRelease( id->p_first );
213
214     assert( !id->id || p_sys->p_out );
215     if( id->id )
216         sout_StreamIdDel( p_sys->p_out, id->id );
217
218     es_format_Clean( &id->fmt );
219
220     TAB_REMOVE( p_sys->i_id, p_sys->id, id );
221
222     if( p_sys->i_id <= 0 )
223     {
224         if( !p_sys->p_out )
225             p_sys->b_drop = false;
226     }
227
228     free( id );
229
230     return VLC_SUCCESS;
231 }
232
233 static int Send( sout_stream_t *p_stream, sout_stream_id_sys_t *id,
234                  block_t *p_buffer )
235 {
236     sout_stream_sys_t *p_sys = p_stream->p_sys;
237
238     if( p_sys->i_date_start < 0 )
239         p_sys->i_date_start = mdate();
240     if( !p_sys->p_out &&
241         ( mdate() - p_sys->i_date_start > p_sys->i_max_wait ||
242           p_sys->i_size > p_sys->i_max_size ) )
243     {
244         msg_Dbg( p_stream, "Starting recording, waited %ds and %dbyte",
245                  (int)((mdate() - p_sys->i_date_start)/1000000), (int)p_sys->i_size );
246         OutputStart( p_stream );
247     }
248
249     OutputSend( p_stream, id, p_buffer );
250
251     return VLC_SUCCESS;
252 }
253
254 /*****************************************************************************
255  *
256  *****************************************************************************/
257 typedef struct
258 {
259     const char  psz_muxer[4];
260     const char  psz_extension[4];
261     int         i_es_max;
262     vlc_fourcc_t codec[128];
263 } muxer_properties_t;
264
265 #define M(muxer, ext, count, ... ) { .psz_muxer = muxer, .psz_extension = ext, .i_es_max = count, .codec = { __VA_ARGS__, 0 } }
266 /* Table of native codec support,
267  * Do not do non native and non standard association !
268  * Muxer will be probe if no entry found */
269 static const muxer_properties_t p_muxers[] = {
270     M( "raw", "mp3", 1,         VLC_CODEC_MPGA ),
271     M( "raw", "a52", 1,         VLC_CODEC_A52 ),
272     M( "raw", "dts", 1,         VLC_CODEC_DTS ),
273     M( "raw", "mpc", 1,         VLC_CODEC_MUSEPACK7, VLC_CODEC_MUSEPACK8 ),
274     M( "raw", "ape", 1,         VLC_CODEC_APE ),
275
276     M( "wav", "wav", 1,         VLC_CODEC_U8,   VLC_CODEC_S16L,
277                                 VLC_CODEC_S24L, VLC_CODEC_S32L, VLC_CODEC_FL32 ),
278
279     //M( "avformat{mux=flac}", "flac", 1, VLC_CODEC_FLAC ), BROKEN
280
281     M( "ogg", "ogg", INT_MAX,   VLC_CODEC_VORBIS, VLC_CODEC_SPEEX,  VLC_CODEC_FLAC,
282                                 VLC_CODEC_SUBT,   VLC_CODEC_THEORA, VLC_CODEC_DIRAC  ),
283
284     M( "asf", "asf", 127,       VLC_CODEC_WMA1, VLC_CODEC_WMA2, VLC_CODEC_WMAP, VLC_CODEC_WMAL, VLC_CODEC_WMAS,
285                                 VLC_CODEC_WMV1, VLC_CODEC_WMV2, VLC_CODEC_WMV3, VLC_CODEC_VC1 ),
286
287     M( "mp4", "mp4", INT_MAX,   VLC_CODEC_MP4A, VLC_CODEC_H264, VLC_CODEC_MP4V,
288                                 VLC_CODEC_SUBT ),
289
290     M( "ps", "mpg", 16/* FIXME*/,VLC_CODEC_MPGV,
291                                 VLC_CODEC_MPGA, VLC_CODEC_DVD_LPCM, VLC_CODEC_A52,
292                                 VLC_CODEC_DTS,
293                                 VLC_CODEC_SPU ),
294
295     M( "avi", "avi", 100,       VLC_CODEC_A52, VLC_CODEC_MPGA,
296                                 VLC_CODEC_WMA1, VLC_CODEC_WMA2, VLC_CODEC_WMAP, VLC_CODEC_WMAL,
297                                 VLC_CODEC_U8, VLC_CODEC_S16L, VLC_CODEC_S24L,
298                                 VLC_CODEC_MP4V ),
299
300     M( "ts", "ts", 8000,        VLC_CODEC_MPGV,
301                                 VLC_CODEC_H264,
302                                 VLC_CODEC_MPGA, VLC_CODEC_DVD_LPCM, VLC_CODEC_A52,
303                                 VLC_CODEC_DTS,  VLC_CODEC_MP4A,
304                                 VLC_CODEC_DVBS, VLC_CODEC_TELETEXT ),
305
306     M( "mkv", "mkv", 32,        VLC_CODEC_H264, VLC_CODEC_VP8, VLC_CODEC_MP4V,
307                                 VLC_CODEC_A52,  VLC_CODEC_MP4A, VLC_CODEC_VORBIS, VLC_CODEC_FLAC ),
308 };
309 #undef M
310
311 static int OutputNew( sout_stream_t *p_stream,
312                       const char *psz_muxer, const char *psz_prefix, const char *psz_extension  )
313 {
314     sout_stream_sys_t *p_sys = p_stream->p_sys;
315     char *psz_file = NULL, *psz_tmp = NULL;
316     char *psz_output = NULL;
317     int i_count;
318
319     if( asprintf( &psz_tmp, "%s%s%s",
320                   psz_prefix, psz_extension ? "." : "", psz_extension ? psz_extension : "" ) < 0 )
321     {
322         goto error;
323     }
324
325     psz_file = config_StringEscape( psz_tmp );
326     if( !psz_file )
327     {
328         free( psz_tmp );
329         goto error;
330     }
331     free( psz_tmp );
332
333     if( asprintf( &psz_output, "std{access=file{no-append,no-format},"
334                   "mux='%s',dst='%s'}", psz_muxer, psz_file ) < 0 )
335     {
336         psz_output = NULL;
337         goto error;
338     }
339
340     /* Create the output */
341     msg_Dbg( p_stream, "Using record output `%s'", psz_output );
342
343     p_sys->p_out = sout_StreamChainNew( p_stream->p_sout, psz_output, NULL, NULL );
344
345     if( !p_sys->p_out )
346         goto error;
347
348     /* Add es */
349     i_count = 0;
350     for( int i = 0; i < p_sys->i_id; i++ )
351     {
352         sout_stream_id_sys_t *id = p_sys->id[i];
353
354         id->id = sout_StreamIdAdd( p_sys->p_out, &id->fmt );
355         if( id->id )
356             i_count++;
357     }
358
359     if( psz_file && psz_extension )
360         var_SetString( p_stream->p_libvlc, "record-file", psz_file );
361
362     free( psz_file );
363     free( psz_output );
364
365     return i_count;
366
367 error:
368
369     free( psz_file );
370     free( psz_output );
371     return -1;
372
373 }
374
375 static void OutputStart( sout_stream_t *p_stream )
376 {
377     sout_stream_sys_t *p_sys = p_stream->p_sys;
378
379     /* */
380     if( p_sys->b_drop )
381         return;
382
383     /* From now on drop packet that cannot be handled */
384     p_sys->b_drop = true;
385
386     /* Detect streams to smart select muxer */
387     const char *psz_muxer = NULL;
388     const char *psz_extension = NULL;
389
390     /* Look for preferred muxer
391      * TODO we could insert transcode in a few cases like
392      * s16l <-> s16b
393      */
394     for( unsigned i = 0; i < sizeof(p_muxers) / sizeof(*p_muxers); i++ )
395     {
396         bool b_ok;
397         if( p_sys->i_id > p_muxers[i].i_es_max )
398             continue;
399
400         b_ok = true;
401         for( int j = 0; j < p_sys->i_id; j++ )
402         {
403             es_format_t *p_fmt = &p_sys->id[j]->fmt;
404
405             b_ok = false;
406             for( int k = 0; p_muxers[i].codec[k] != 0; k++ )
407             {
408                 if( p_fmt->i_codec == p_muxers[i].codec[k] )
409                 {
410                     b_ok = true;
411                     break;
412                 }
413             }
414             if( !b_ok )
415                 break;
416         }
417         if( !b_ok )
418             continue;
419
420         psz_muxer = p_muxers[i].psz_muxer;
421         psz_extension = p_muxers[i].psz_extension;
422         break;
423     }
424
425     /* If failed, brute force our demuxers and select the one that
426      * keeps most of our stream */
427     if( !psz_muxer || !psz_extension )
428     {
429         static const char ppsz_muxers[][2][4] = {
430             { "avi", "avi" }, { "mp4", "mp4" }, { "ogg", "ogg" },
431             { "asf", "asf" }, {  "ts",  "ts" }, {  "ps", "mpg" },
432             { "mkv", "mkv" },
433 #if 0
434             // XXX ffmpeg sefault really easily if you try an unsupported codec
435             // mov and avi at least segfault
436             { "avformat{mux=avi}", "avi" },
437             { "avformat{mux=mov}", "mov" },
438             { "avformat{mux=mp4}", "mp4" },
439             { "avformat{mux=nsv}", "nsv" },
440             { "avformat{mux=flv}", "flv" },
441 #endif
442         };
443         int i_best = 0;
444         int i_best_es = 0;
445
446         msg_Warn( p_stream, "failed to find an adequate muxer, probing muxers" );
447         for( unsigned i = 0; i < sizeof(ppsz_muxers) / sizeof(*ppsz_muxers); i++ )
448         {
449             char *psz_file;
450             int i_es;
451
452             psz_file = tempnam( NULL, "vlc" );
453             if( !psz_file )
454                 continue;
455
456             msg_Dbg( p_stream, "probing muxer %s", ppsz_muxers[i][0] );
457             i_es = OutputNew( p_stream, ppsz_muxers[i][0], psz_file, NULL );
458
459             if( i_es < 0 )
460             {
461                 vlc_unlink( psz_file );
462                 free( psz_file );
463                 continue;
464             }
465
466             /* */
467             for( int i = 0; i < p_sys->i_id; i++ )
468             {
469                 sout_stream_id_sys_t *id = p_sys->id[i];
470
471                 if( id->id )
472                     sout_StreamIdDel( p_sys->p_out, id->id );
473                 id->id = NULL;
474             }
475             if( p_sys->p_out )
476                 sout_StreamChainDelete( p_sys->p_out, p_sys->p_out );
477             p_sys->p_out = NULL;
478
479             if( i_es > i_best_es )
480             {
481                 i_best_es = i_es;
482                 i_best = i;
483
484                 if( i_best_es >= p_sys->i_id )
485                     break;
486             }
487             vlc_unlink( psz_file );
488             free( psz_file );
489         }
490
491         /* */
492         psz_muxer = ppsz_muxers[i_best][0];
493         psz_extension = ppsz_muxers[i_best][1];
494         msg_Dbg( p_stream, "using muxer %s with extension %s (%d/%d streams accepted)",
495                  psz_muxer, psz_extension, i_best_es, p_sys->i_id );
496     }
497
498     /* Create the output */
499     if( OutputNew( p_stream, psz_muxer, p_sys->psz_prefix, psz_extension ) < 0 )
500     {
501         msg_Err( p_stream, "failed to open output");
502         return;
503     }
504
505     /* Compute highest timestamp of first I over all streams */
506     p_sys->i_dts_start = 0;
507     for( int i = 0; i < p_sys->i_id; i++ )
508     {
509         sout_stream_id_sys_t *id = p_sys->id[i];
510         block_t *p_block;
511
512         if( !id->id || !id->p_first )
513             continue;
514
515         mtime_t i_dts = id->p_first->i_dts;
516         for( p_block = id->p_first; p_block != NULL; p_block = p_block->p_next )
517         {
518             if( p_block->i_flags & BLOCK_FLAG_TYPE_I )
519             {
520                 i_dts = p_block->i_dts;
521                 break;
522             }
523         }
524
525         if( i_dts > p_sys->i_dts_start )
526             p_sys->i_dts_start = i_dts;
527     }
528
529     /* Send buffered data */
530     for( int i = 0; i < p_sys->i_id; i++ )
531     {
532         sout_stream_id_sys_t *id = p_sys->id[i];
533
534         if( !id->id )
535             continue;
536
537         block_t *p_block = id->p_first;
538         while( p_block )
539         {
540             block_t *p_next = p_block->p_next;
541
542             p_block->p_next = NULL;
543
544             OutputSend( p_stream, id, p_block );
545
546             p_block = p_next;
547         }
548
549         id->p_first = NULL;
550         id->pp_last = &id->p_first;
551     }
552 }
553
554 static void OutputSend( sout_stream_t *p_stream, sout_stream_id_sys_t *id, block_t *p_block )
555 {
556     sout_stream_sys_t *p_sys = p_stream->p_sys;
557
558     if( id->id )
559     {
560         /* We wait until the first key frame (if needed) and
561          * to be beyong i_dts_start (for stream without key frame) */
562         if( unlikely( id->b_wait_key ) )
563         {
564             if( p_block->i_flags & BLOCK_FLAG_TYPE_I )
565             {
566                 id->b_wait_key = false;
567                 id->b_wait_start = false;
568             }
569
570             if( ( p_block->i_flags & BLOCK_FLAG_TYPE_MASK ) == 0 )
571                 id->b_wait_key = false;
572         }
573         if( unlikely( id->b_wait_start ) )
574         {
575             if( p_block->i_dts >=p_sys->i_dts_start )
576                 id->b_wait_start = false;
577         }
578         if( unlikely( id->b_wait_key || id->b_wait_start ) )
579             block_ChainRelease( p_block );
580         else
581             sout_StreamIdSend( p_sys->p_out, id->id, p_block );
582     }
583     else if( p_sys->b_drop )
584     {
585         block_ChainRelease( p_block );
586     }
587     else
588     {
589         size_t i_size;
590
591         block_ChainProperties( p_block, NULL, &i_size, NULL );
592         p_sys->i_size += i_size;
593         block_ChainLastAppend( &id->pp_last, p_block );
594     }
595 }
596