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