1 /*****************************************************************************
2 * record.c: record stream output module
3 *****************************************************************************
4 * Copyright (C) 2008-2009 VLC authors and VideoLAN
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_block.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", "", 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_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
* );
83 struct sout_stream_id_sys_t
90 sout_stream_id_sys_t
*id
;
96 struct sout_stream_sys_t
100 sout_stream_t
*p_out
;
102 mtime_t i_date_start
;
111 sout_stream_id_sys_t
**id
;
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
* );
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*CLOCK_FREQ
; /* 5s */
152 p_sys
->i_max_size
= 1*1024*1024; /* 1 MiB */
154 p_sys
->i_max_wait
= 30*CLOCK_FREQ
; /* 30s */
155 p_sys
->i_max_size
= 20*1024*1024; /* 20 MiB */
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_StreamChainDelete( p_sys
->p_out
, 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_sys_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_sys_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_sys_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_sys_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
[4];
260 const char psz_extension
[4];
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_CODEC_U8
, VLC_CODEC_S16L
,
277 VLC_CODEC_S24L
, VLC_CODEC_S32L
, VLC_CODEC_FL32
),
279 //M( "avformat{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
,
291 VLC_CODEC_MPGA
, VLC_CODEC_DVD_LPCM
, VLC_CODEC_A52
,
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
,
300 M( "ts", "ts", 8000, VLC_CODEC_MPGV
,
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
),
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
),
311 static int OutputNew( sout_stream_t
*p_stream
,
312 const char *psz_muxer
, const char *psz_prefix
, const char *psz_extension
)
314 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
315 char *psz_file
= NULL
, *psz_tmp
= NULL
;
316 char *psz_output
= NULL
;
319 if( asprintf( &psz_tmp
, "%s%s%s",
320 psz_prefix
, psz_extension
? "." : "", psz_extension
? psz_extension
: "" ) < 0 )
325 psz_file
= config_StringEscape( psz_tmp
);
333 if( asprintf( &psz_output
, "std{access=file{no-append,no-format},"
334 "mux='%s',dst='%s'}", psz_muxer
, psz_file
) < 0 )
340 /* Create the output */
341 msg_Dbg( p_stream
, "Using record output `%s'", psz_output
);
343 p_sys
->p_out
= sout_StreamChainNew( p_stream
->p_sout
, psz_output
, NULL
, NULL
);
350 for( int i
= 0; i
< p_sys
->i_id
; i
++ )
352 sout_stream_id_sys_t
*id
= p_sys
->id
[i
];
354 id
->id
= sout_StreamIdAdd( p_sys
->p_out
, &id
->fmt
);
359 if( psz_file
&& psz_extension
)
360 var_SetString( p_stream
->p_libvlc
, "record-file", psz_file
);
375 static void OutputStart( sout_stream_t
*p_stream
)
377 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
383 /* From now on drop packet that cannot be handled */
384 p_sys
->b_drop
= true;
386 /* Detect streams to smart select muxer */
387 const char *psz_muxer
= NULL
;
388 const char *psz_extension
= NULL
;
390 /* Look for preferred muxer
391 * TODO we could insert transcode in a few cases like
394 for( unsigned i
= 0; i
< sizeof(p_muxers
) / sizeof(*p_muxers
); i
++ )
397 if( p_sys
->i_id
> p_muxers
[i
].i_es_max
)
401 for( int j
= 0; j
< p_sys
->i_id
; j
++ )
403 es_format_t
*p_fmt
= &p_sys
->id
[j
]->fmt
;
406 for( int k
= 0; p_muxers
[i
].codec
[k
] != 0; k
++ )
408 if( p_fmt
->i_codec
== p_muxers
[i
].codec
[k
] )
420 psz_muxer
= p_muxers
[i
].psz_muxer
;
421 psz_extension
= p_muxers
[i
].psz_extension
;
425 /* If failed, brute force our demuxers and select the one that
426 * keeps most of our stream */
427 if( !psz_muxer
|| !psz_extension
)
429 static const char ppsz_muxers
[][2][4] = {
430 { "avi", "avi" }, { "mp4", "mp4" }, { "ogg", "ogg" },
431 { "asf", "asf" }, { "ts", "ts" }, { "ps", "mpg" },
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" },
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
++ )
452 psz_file
= tempnam( NULL
, "vlc" );
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
);
461 vlc_unlink( psz_file
);
467 for( int i
= 0; i
< p_sys
->i_id
; i
++ )
469 sout_stream_id_sys_t
*id
= p_sys
->id
[i
];
472 sout_StreamIdDel( p_sys
->p_out
, id
->id
);
476 sout_StreamChainDelete( p_sys
->p_out
, p_sys
->p_out
);
479 if( i_es
> i_best_es
)
484 if( i_best_es
>= p_sys
->i_id
)
487 vlc_unlink( psz_file
);
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
);
498 /* Create the output */
499 if( OutputNew( p_stream
, psz_muxer
, p_sys
->psz_prefix
, psz_extension
) < 0 )
501 msg_Err( p_stream
, "failed to open output");
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
++ )
509 sout_stream_id_sys_t
*id
= p_sys
->id
[i
];
512 if( !id
->id
|| !id
->p_first
)
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
)
518 if( p_block
->i_flags
& BLOCK_FLAG_TYPE_I
)
520 i_dts
= p_block
->i_dts
;
525 if( i_dts
> p_sys
->i_dts_start
)
526 p_sys
->i_dts_start
= i_dts
;
529 /* Send buffered data */
530 for( int i
= 0; i
< p_sys
->i_id
; i
++ )
532 sout_stream_id_sys_t
*id
= p_sys
->id
[i
];
537 block_t
*p_block
= id
->p_first
;
540 block_t
*p_next
= p_block
->p_next
;
542 p_block
->p_next
= NULL
;
544 OutputSend( p_stream
, id
, p_block
);
550 id
->pp_last
= &id
->p_first
;
554 static void OutputSend( sout_stream_t
*p_stream
, sout_stream_id_sys_t
*id
, block_t
*p_block
)
556 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
560 /* We wait until the first key frame (if needed) and
561 * to be beyong i_dts_start (for stream without key frame) */
564 if( p_block
->i_flags
& BLOCK_FLAG_TYPE_I
)
566 id
->b_wait_key
= false;
567 id
->b_wait_start
= false;
570 if( ( p_block
->i_flags
& BLOCK_FLAG_TYPE_MASK
) == 0 )
571 id
->b_wait_key
= false;
573 if( id
->b_wait_start
)
575 if( p_block
->i_dts
>=p_sys
->i_dts_start
)
576 id
->b_wait_start
= false;
578 if( id
->b_wait_key
|| id
->b_wait_start
)
579 block_ChainRelease( p_block
);
581 sout_StreamIdSend( p_sys
->p_out
, id
->id
, p_block
);
583 else if( p_sys
->b_drop
)
585 block_ChainRelease( p_block
);
591 block_ChainProperties( p_block
, NULL
, &i_size
, NULL
);
592 p_sys
->i_size
+= i_size
;
593 block_ChainLastAppend( &id
->pp_last
, p_block
);