1 /*****************************************************************************
2 * record.c: record stream output module
3 *****************************************************************************
4 * Copyright (C) 2008 the VideoLAN team
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_block.h>
38 #include <vlc_charset.h>
41 /*****************************************************************************
43 *****************************************************************************/
44 static int Open ( vlc_object_t * );
45 static void Close ( vlc_object_t * );
47 /*****************************************************************************
49 *****************************************************************************/
50 #define DST_PREFIX_TEXT N_("Destination prefix")
51 #define DST_PREFIX_LONGTEXT N_( \
52 "Prefix of the destination file automatically generated" )
54 #define SOUT_CFG_PREFIX "sout-record-"
57 set_description( N_("Record stream output") )
58 set_capability( "sout stream", 0 )
59 add_shortcut( "record" )
60 set_shortname( N_("Record") )
62 set_category( CAT_SOUT )
63 set_subcategory( SUBCAT_SOUT_STREAM )
65 add_string( SOUT_CFG_PREFIX "dst-prefix", "", NULL, DST_PREFIX_TEXT,
66 DST_PREFIX_LONGTEXT, true )
68 set_callbacks( Open, Close )
72 static const char *const ppsz_sout_options[] = {
78 static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * );
79 static int Del ( sout_stream_t *, sout_stream_id_t * );
80 static int Send( sout_stream_t *, sout_stream_id_t *, block_t* );
83 struct sout_stream_id_t
96 struct sout_stream_sys_t
100 sout_stream_t *p_out;
102 mtime_t i_date_start;
111 sout_stream_id_t **id;
115 static void OutputStart( sout_stream_t *p_stream );
116 static void OutputSend( sout_stream_t *p_stream, sout_stream_id_t *id, block_t * );
118 /*****************************************************************************
120 *****************************************************************************/
121 static int Open( vlc_object_t *p_this )
123 sout_stream_t *p_stream = (sout_stream_t*)p_this;
124 sout_stream_sys_t *p_sys;
126 p_stream->pf_add = Add;
127 p_stream->pf_del = Del;
128 p_stream->pf_send = Send;
130 p_stream->p_sys = p_sys = malloc( sizeof(*p_sys) );
134 config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
137 p_sys->psz_prefix = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "dst-prefix" );
138 if( !p_sys->psz_prefix )
140 p_sys->psz_prefix = strdup( "sout-record-" );
141 if( !p_sys->psz_prefix )
148 p_sys->i_date_start = -1;
150 #ifdef OPTIMIZE_MEMORY
151 p_sys->i_max_wait = 5*1000000; /* 5s */
152 p_sys->i_max_size = 1*1000000; /* 1 Mbyte */
154 p_sys->i_max_wait = 30*1000000; /* 30s */
155 p_sys->i_max_size = 20*1000000; /* 20 Mbyte */
157 p_sys->b_drop = false;
158 p_sys->i_dts_start = 0;
159 TAB_INIT( p_sys->i_id, p_sys->id );
164 /*****************************************************************************
166 *****************************************************************************/
167 static void Close( vlc_object_t * p_this )
169 sout_stream_t *p_stream = (sout_stream_t*)p_this;
170 sout_stream_sys_t *p_sys = p_stream->p_sys;
173 sout_StreamDelete( p_sys->p_out );
175 TAB_CLEAN( p_sys->i_id, p_sys->id );
176 free( p_sys->psz_prefix );
180 /*****************************************************************************
182 *****************************************************************************/
183 static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
185 sout_stream_sys_t *p_sys = p_stream->p_sys;
186 sout_stream_id_t *id;
188 id = malloc( sizeof(*id) );
192 es_format_Copy( &id->fmt, p_fmt );
194 id->pp_last = &id->p_first;
196 id->b_wait_key = true;
197 id->b_wait_start = true;
199 TAB_APPEND( p_sys->i_id, p_sys->id, id );
204 static int Del( sout_stream_t *p_stream, sout_stream_id_t *id )
206 sout_stream_sys_t *p_sys = p_stream->p_sys;
209 OutputStart( p_stream );
212 block_ChainRelease( id->p_first );
214 assert( !id->id || p_sys->p_out );
216 sout_StreamIdDel( p_sys->p_out, id->id );
218 es_format_Clean( &id->fmt );
220 TAB_REMOVE( p_sys->i_id, p_sys->id, id );
222 if( p_sys->i_id <= 0 )
225 p_sys->b_drop = false;
233 static int Send( sout_stream_t *p_stream, sout_stream_id_t *id,
236 sout_stream_sys_t *p_sys = p_stream->p_sys;
238 if( p_sys->i_date_start < 0 )
239 p_sys->i_date_start = mdate();
241 ( mdate() - p_sys->i_date_start > p_sys->i_max_wait ||
242 p_sys->i_size > p_sys->i_max_size ) )
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 );
249 OutputSend( p_stream, id, p_buffer );
254 /*****************************************************************************
256 *****************************************************************************/
259 const char *psz_muxer;
260 const char *psz_extension;
262 vlc_fourcc_t codec[128];
263 } muxer_properties_t;
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 ),
276 M( "wav", "wav", 1, VLC_FOURCC('a','r','a','w'), VLC_CODEC_U8, VLC_CODEC_S16L,
277 VLC_CODEC_S24L, VLC_CODEC_S32L, VLC_CODEC_FL32 ),
279 //M( "ffmpeg{mux=flac}", "flac", 1, VLC_CODEC_FLAC ), BROKEN
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 ),
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 ),
287 M( "mp4", "mp4", INT_MAX, VLC_CODEC_MP4A, VLC_CODEC_H264, VLC_CODEC_MP4V,
290 M( "ps", "mpg", 16/* FIXME*/,VLC_CODEC_MPGV, VLC_CODEC_MP1V, VLC_CODEC_MP2V,
291 VLC_CODEC_MPGA, VLC_CODEC_DVD_LPCM, VLC_CODEC_A52,
295 M( "ts", "ts", 8000, VLC_CODEC_MPGV, VLC_CODEC_MP1V, VLC_CODEC_MP2V,
297 VLC_CODEC_MPGA, VLC_CODEC_DVD_LPCM, VLC_CODEC_A52,
298 VLC_CODEC_DTS, VLC_CODEC_MP4A,
299 VLC_CODEC_DVBS, VLC_CODEC_TELETEXT ),
301 M( NULL, NULL, 0, 0 )
305 static int OutputNew( sout_stream_t *p_stream,
306 const char *psz_muxer, const char *psz_prefix, const char *psz_extension )
308 sout_stream_sys_t *p_sys = p_stream->p_sys;
312 if( asprintf( &psz_output, "std{access=file,mux='%s',dst='%s%s%s'}",
313 psz_muxer, psz_prefix, psz_extension ? "." : "", psz_extension ? psz_extension : "" ) < 0 )
316 /* Create the output */
317 msg_Dbg( p_stream, "Using record output `%s'", psz_output );
318 p_sys->p_out = sout_StreamNew( p_stream->p_sout, psz_output );
327 for( int i = 0; i < p_sys->i_id; i++ )
329 sout_stream_id_t *id = p_sys->id[i];
331 id->id = sout_StreamIdAdd( p_sys->p_out, &id->fmt );
339 static void OutputStart( sout_stream_t *p_stream )
341 sout_stream_sys_t *p_sys = p_stream->p_sys;
347 /* From now on drop packet that cannot be handled */
348 p_sys->b_drop = true;
350 /* Detect streams to smart select muxer */
351 const char *psz_muxer = NULL;
352 const char *psz_extension = NULL;
354 /* Look for prefered muxer
355 * TODO we could insert transcode in a few cases like
358 for( int i = 0; p_muxers[i].psz_muxer != NULL; i++ )
361 if( p_sys->i_id > p_muxers[i].i_es_max )
365 for( int j = 0; j < p_sys->i_id; j++ )
367 es_format_t *p_fmt = &p_sys->id[j]->fmt;
370 for( int k = 0; p_muxers[i].codec[k] != 0; k++ )
372 if( p_fmt->i_codec == p_muxers[i].codec[k] )
384 psz_muxer = p_muxers[i].psz_muxer;
385 psz_extension = p_muxers[i].psz_extension;
389 /* If failed, brute force our demuxers and select the one that
390 * keeps most of our stream */
391 if( !psz_muxer || !psz_extension )
393 static const char *ppsz_muxers[][2] = {
394 { "avi", "avi" }, { "mp4", "mp4" }, { "ogg", "ogg" },
395 { "asf", "asf" }, { "ts", "ts" }, { "ps", "mpg" },
397 // XXX ffmpeg sefault really easily if you try an unsupported codec
398 // mov and avi at least segfault
399 { "ffmpeg{mux=avi}", "avi" },
400 { "ffmpeg{mux=mov}", "mov" },
401 { "ffmpeg{mux=mp4}", "mp4" },
402 { "ffmpeg{mux=nsv}", "nsv" },
403 { "ffmpeg{mux=flv}", "flv" },
410 msg_Warn( p_stream, "failed to find an adequate muxer, probing muxers" );
411 for( int i = 0; ppsz_muxers[i][0] != NULL; i++ )
416 psz_file = tempnam( NULL, "vlc" );
420 msg_Dbg( p_stream, "probing muxer %s", ppsz_muxers[i][0] );
421 i_es = OutputNew( p_stream, ppsz_muxers[i][0], psz_file, NULL );
425 utf8_unlink( psz_file );
431 for( int i = 0; i < p_sys->i_id; i++ )
433 sout_stream_id_t *id = p_sys->id[i];
436 sout_StreamIdDel( p_sys->p_out, id->id );
440 sout_StreamDelete( p_sys->p_out );
443 if( i_es > i_best_es )
448 if( i_best_es >= p_sys->i_id )
451 utf8_unlink( psz_file );
456 psz_muxer = ppsz_muxers[i_best][0];
457 psz_extension = ppsz_muxers[i_best][1];
458 msg_Dbg( p_stream, "using muxer %s with extension %s (%d/%d streams accepted)",
459 psz_muxer, psz_extension, i_best_es, p_sys->i_id );
462 /* Create the output */
463 if( OutputNew( p_stream, psz_muxer, p_sys->psz_prefix, psz_extension ) < 0 )
465 msg_Err( p_stream, "failed to open output");
469 /* Compute highest timestamp of first I over all streams */
470 p_sys->i_dts_start = 0;
471 for( int i = 0; i < p_sys->i_id; i++ )
473 sout_stream_id_t *id = p_sys->id[i];
476 if( !id->id || !id->p_first )
479 mtime_t i_dts = id->p_first->i_dts;
480 for( p_block = id->p_first; p_block != NULL; p_block = p_block->p_next )
482 if( p_block->i_flags & BLOCK_FLAG_TYPE_I )
484 i_dts = p_block->i_dts;
489 if( i_dts > p_sys->i_dts_start )
490 p_sys->i_dts_start = i_dts;
493 /* Send buffered data */
494 for( int i = 0; i < p_sys->i_id; i++ )
496 sout_stream_id_t *id = p_sys->id[i];
501 block_t *p_block = id->p_first;
504 block_t *p_next = p_block->p_next;
506 p_block->p_next = NULL;
508 OutputSend( p_stream, id, p_block );
514 id->pp_last = &id->p_first;
518 static void OutputSend( sout_stream_t *p_stream, sout_stream_id_t *id, block_t *p_block )
520 sout_stream_sys_t *p_sys = p_stream->p_sys;
524 /* We wait until the first key frame (if needed) and
525 * to be beyong i_dts_start (for stream without key frame) */
528 if( p_block->i_flags & BLOCK_FLAG_TYPE_I )
530 id->b_wait_key = false;
531 id->b_wait_start = false;
534 if( ( p_block->i_flags & BLOCK_FLAG_TYPE_MASK ) == 0 )
535 id->b_wait_key = false;
537 if( id->b_wait_start )
539 if( p_block->i_dts >=p_sys->i_dts_start )
540 id->b_wait_start = false;
542 if( id->b_wait_key || id->b_wait_start )
543 block_ChainRelease( p_block );
545 sout_StreamIdSend( p_sys->p_out, id->id, p_block );
547 else if( p_sys->b_drop )
549 block_ChainRelease( p_block );
555 block_ChainProperties( p_block, NULL, &i_size, NULL );
556 p_sys->i_size += i_size;
557 block_ChainLastAppend( &id->pp_last, p_block );