From aa407ea9ca0beca090021e1bc26c4d9f84f003df Mon Sep 17 00:00:00 2001 From: Laurent Aimar Date: Tue, 26 Aug 2008 12:54:18 +0200 Subject: [PATCH] Added record support at the stream_t level in core. The record access_filter will become useless as soon as all demuxers that can support it will be modified. The record support is half done. I will also add es_out_t record functionnalities, and a way to selected one of them if both are supported. --- include/vlc_demux.h | 8 +- include/vlc_input.h | 6 +- include/vlc_stream.h | 6 +- modules/control/hotkeys.c | 13 ++ modules/demux/ts.c | 25 +++ .../gui/qt4/components/interface_widgets.cpp | 11 +- src/input/control.c | 10 ++ src/input/decoder.c | 68 ++++---- src/input/demux.c | 3 + src/input/input.c | 53 +++++- src/input/input_internal.h | 11 +- src/input/stream.c | 159 +++++++++++++++++- src/input/var.c | 38 ++++- src/libvlc-module.c | 7 + 14 files changed, 358 insertions(+), 60 deletions(-) diff --git a/include/vlc_demux.h b/include/vlc_demux.h index 5b5c5829c2..2d8e1c056b 100644 --- a/include/vlc_demux.h +++ b/include/vlc_demux.h @@ -124,8 +124,14 @@ enum demux_query_e /* Attachments */ DEMUX_GET_ATTACHMENTS, /* arg1=input_attachment_t***, int* res=can fail */ + /* RECORD you should accept it only if the stream can be recorded without + * any modification or header addition. */ + DEMUX_CAN_RECORD, /* arg1=bool* res=can fail(assume false) */ + DEMUX_SET_RECORD_STATE, /* arg1=bool res=can fail */ + + /* II. Specific access_demux queries */ - DEMUX_CAN_PAUSE, /* arg1= bool* can fail (assume false)*/ + DEMUX_CAN_PAUSE = 0x1000, /* arg1= bool* can fail (assume false)*/ DEMUX_SET_PAUSE_STATE, /* arg1= bool can fail */ DEMUX_GET_PTS_DELAY, /* arg1= int64_t* cannot fail */ diff --git a/include/vlc_input.h b/include/vlc_input.h index f97d196061..70f56c6271 100644 --- a/include/vlc_input.h +++ b/include/vlc_input.h @@ -524,7 +524,11 @@ enum input_query_e INPUT_GET_ATTACHMENT, /* arg1=input_attachment_t**, arg2=char* res=can fail */ /* On the fly input slave */ - INPUT_ADD_SLAVE /* arg1= char * */ + INPUT_ADD_SLAVE, /* arg1= char * */ + + /* On the fly record while playing */ + INPUT_SET_RECORD_STATE, /* arg1=bool res=can fail */ + INPUT_GET_RECORD_STATE, /* arg1=bool* res=can fail */ }; VLC_EXPORT( int, input_vaControl,( input_thread_t *, int i_query, va_list ) ); diff --git a/include/vlc_stream.h b/include/vlc_stream.h index eba91cf7e9..92aee352ee 100644 --- a/include/vlc_stream.h +++ b/include/vlc_stream.h @@ -64,7 +64,11 @@ enum stream_query_e STREAM_CONTROL_ACCESS, /* arg1= int i_access_query, args res: can fail if access unreachable or access control answer */ - STREAM_GET_CONTENT_TYPE, /**< arg1= char ** res=can file */ + STREAM_GET_CONTENT_TYPE, /**< arg1= char ** res=can fail */ + + /* SET_RECORD: + * XXX only data read through stream_Read/Block will be recorded */ + STREAM_SET_RECORD_STATE, /**< arg1=bool, arg2=const char *psz_ext (if arg1 is true) res=can fail */ }; VLC_EXPORT( int, stream_Read, ( stream_t *s, void *p_read, int i_read ) ); diff --git a/modules/control/hotkeys.c b/modules/control/hotkeys.c index 22a08870ff..c5fbc19eee 100644 --- a/modules/control/hotkeys.c +++ b/modules/control/hotkeys.c @@ -810,6 +810,19 @@ static void Run( intf_thread_t *p_intf ) { osd_MenuActivate( VLC_OBJECT(p_intf) ); } + else if( i_action == ACTIONID_RECORD ) + { + if( var_GetBool( p_input, "can-record" ) ) + { + const bool b_record = !var_GetBool( p_input, "record" ); + + if( b_record ) + vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording") ); + else + vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording done") ); + var_SetBool( p_input, "record", b_record ); + } + } } if( p_vout ) vlc_object_release( p_vout ); diff --git a/modules/demux/ts.c b/modules/demux/ts.c index a49ba41840..6b62688980 100644 --- a/modules/demux/ts.c +++ b/modules/demux/ts.c @@ -370,6 +370,9 @@ struct demux_sys_t /* */ bool b_meta; + + /* */ + bool b_start_record; }; static int Demux ( demux_t *p_demux ); @@ -642,6 +645,7 @@ static int Open( vlc_object_t *p_this ) p_sys->b_udp_out = false; p_sys->i_ts_read = 50; p_sys->csa = NULL; + p_sys->b_start_record = false; /* Init PAT handler */ pat = &p_sys->pid[0]; @@ -1091,6 +1095,13 @@ static int Demux( demux_t *p_demux ) } } + if( p_sys->b_start_record ) + { + /* Enable recording once synchronized */ + stream_Control( p_demux->s, STREAM_SET_RECORD_STATE, true, "ts" ); + p_sys->b_start_record = false; + } + if( p_sys->b_udp_out ) { memcpy( &p_sys->buffer[i_pkt * p_sys->i_packet_size], @@ -1190,6 +1201,7 @@ static int Control( demux_t *p_demux, int i_query, va_list args ) { demux_sys_t *p_sys = p_demux->p_sys; double f, *pf; + bool b_bool, *pb_bool; int64_t i64; int64_t *pi64; int i_int; @@ -1386,6 +1398,19 @@ static int Control( demux_t *p_demux, int i_query, va_list args ) return VLC_SUCCESS; } + case DEMUX_CAN_RECORD: + pb_bool = (bool*)va_arg( args, bool * ); + *pb_bool = true; + return VLC_SUCCESS; + + case DEMUX_SET_RECORD_STATE: + b_bool = (bool)va_arg( args, int ); + + if( !b_bool ) + stream_Control( p_demux->s, STREAM_SET_RECORD_STATE, false ); + p_sys->b_start_record = b_bool; + return VLC_SUCCESS; + case DEMUX_GET_FPS: case DEMUX_SET_TIME: default: diff --git a/modules/gui/qt4/components/interface_widgets.cpp b/modules/gui/qt4/components/interface_widgets.cpp index e10a0ba9ef..305eed3cf1 100644 --- a/modules/gui/qt4/components/interface_widgets.cpp +++ b/modules/gui/qt4/components/interface_widgets.cpp @@ -372,13 +372,12 @@ void AdvControlsWidget::enableInput( bool enable ) input_item_t *p_item = input_GetItem( THEMIM->getInput() ); i_input_id = p_item->i_id; - if( var_Type( THEMIM->getInput(), "record-toggle" ) == VLC_VAR_VOID ) - recordButton->setVisible( true ); - else - recordButton->setVisible( false ); + recordButton->setVisible( var_GetBool( THEMIM->getInput(), "can-record" ) ); } else + { recordButton->setVisible( false ); + } ABButton->setEnabled( enable ); recordButton->setEnabled( enable ); @@ -464,8 +463,8 @@ void AdvControlsWidget::record() if( p_input ) { /* This method won't work fine if the stream can't be cut anywhere */ - if( var_Type( p_input, "record-toggle" ) == VLC_VAR_VOID ) - var_TriggerCallback( p_input, "record-toggle" ); + const bool b_recording = var_GetBool( p_input, "record" ); + var_SetBool( p_input, "record", !b_recording ); #if 0 else { diff --git a/src/input/control.c b/src/input/control.c index 123c9f908f..1e286f8b41 100644 --- a/src/input/control.c +++ b/src/input/control.c @@ -63,6 +63,7 @@ int input_vaControl( input_thread_t *p_input, int i_query, va_list args ) int *pi_bkmk; int i_int, *pi_int; + bool b_bool, *pb_bool; double f, *pf; int64_t i_64, *pi_64; @@ -598,6 +599,15 @@ int input_vaControl( input_thread_t *p_input, int i_query, va_list args ) return VLC_EGENERIC; } + case INPUT_SET_RECORD_STATE: + b_bool = (bool)va_arg( args, int ); + var_SetBool( p_input, "record", b_bool ); + return VLC_SUCCESS; + + case INPUT_GET_RECORD_STATE: + pb_bool = (bool*)va_arg( args, bool* ); + *pb_bool = var_GetBool( p_input, "record" ); + return VLC_SUCCESS; default: msg_Err( p_input, "unknown query in input_vaControl" ); diff --git a/src/input/decoder.c b/src/input/decoder.c index dace0f6d28..ff75d76d37 100644 --- a/src/input/decoder.c +++ b/src/input/decoder.c @@ -734,33 +734,34 @@ static void VoutFlushPicture( vout_thread_t *p_vout ) vlc_mutex_unlock( &p_vout->picture_lock ); } - -static void optimize_video_pts( decoder_t *p_dec ) +static void DecoderOptimizePtsDelay( decoder_t *p_dec ) { - picture_t * oldest_pict = NULL; - picture_t * youngest_pict = NULL; - int i; + input_thread_t *p_input = p_dec->p_owner->p_input; + vout_thread_t *p_vout = p_dec->p_owner->p_vout; + input_thread_private_t *p_priv = p_input->p; - input_thread_t * p_input = p_dec->p_owner->p_input; - vout_thread_t * p_vout = p_dec->p_owner->p_vout; - input_thread_private_t * p_priv = p_input->p; + picture_t *p_old = NULL; + picture_t *p_young = NULL; + int i; /* Enable with --auto-adjust-pts-delay */ - if( !p_priv->pts_adjust.auto_adjust ) return; + if( !p_priv->pts_adjust.b_auto_adjust ) + return; for( i = 0; i < I_RENDERPICTURES; i++ ) { - picture_t * pic = PP_RENDERPICTURE[i]; - if( pic->i_status != READY_PICTURE ) + picture_t *p_pic = PP_RENDERPICTURE[i]; + + if( p_pic->i_status != READY_PICTURE ) continue; - if( !oldest_pict || pic->date < oldest_pict->date ) - oldest_pict = pic; - if( !youngest_pict || pic->date > youngest_pict->date ) - youngest_pict = pic; + if( !p_old || p_pic->date < p_old->date ) + p_old = p_pic; + if( !p_young || p_pic->date > p_young->date ) + p_young = p_pic; } - if( !youngest_pict || !oldest_pict ) + if( !p_young || !p_old ) return; /* Try to find if we can reduce the pts @@ -775,59 +776,58 @@ static void optimize_video_pts( decoder_t *p_dec ) * pts<->dts delay in the muxed stream. That is * why we may end up in having a negative pts_delay, * to compensate that artificial delay. */ - mtime_t buffer_size = youngest_pict->date - oldest_pict->date; - int64_t pts_slide = 0; - if( buffer_size < 10000 ) + const mtime_t i_buffer_length = p_young->date - p_old->date; + int64_t i_pts_slide = 0; + if( i_buffer_length < 10000 ) { if( p_priv->pts_adjust.i_num_faulty > 10 ) { - pts_slide = __MAX(p_input->i_pts_delay *3 / 2, 10000); + i_pts_slide = __MAX(p_input->i_pts_delay *3 / 2, 10000); p_priv->pts_adjust.i_num_faulty = 0; } - if( p_priv->pts_adjust.to_high ) + if( p_priv->pts_adjust.b_to_high ) { - p_priv->pts_adjust.to_high = !p_priv->pts_adjust.to_high; + p_priv->pts_adjust.b_to_high = !p_priv->pts_adjust.b_to_high; p_priv->pts_adjust.i_num_faulty = 0; } p_priv->pts_adjust.i_num_faulty++; } - else if( buffer_size > 100000 ) + else if( i_buffer_length > 100000 ) { if( p_priv->pts_adjust.i_num_faulty > 25 ) { - pts_slide = -buffer_size/2; + i_pts_slide = -i_buffer_length/2; p_priv->pts_adjust.i_num_faulty = 0; } - if( p_priv->pts_adjust.to_high ) + if( p_priv->pts_adjust.b_to_high ) { - p_priv->pts_adjust.to_high = !p_priv->pts_adjust.to_high; + p_priv->pts_adjust.b_to_high = !p_priv->pts_adjust.b_to_high; p_priv->pts_adjust.i_num_faulty = 0; } p_priv->pts_adjust.i_num_faulty++; } - if( pts_slide ) + if( i_pts_slide != 0 ) { - mtime_t origi_delay = p_input->i_pts_delay; + const mtime_t i_pts_delay_org = p_input->i_pts_delay; - p_input->i_pts_delay += pts_slide; + p_input->i_pts_delay += i_pts_slide; /* Don't play with the pts delay for more than -2<->3sec */ if( p_input->i_pts_delay < -2000000 ) p_input->i_pts_delay = -2000000; else if( p_input->i_pts_delay > 3000000 ) p_input->i_pts_delay = 3000000; - pts_slide = p_input->i_pts_delay - origi_delay; + i_pts_slide = p_input->i_pts_delay - i_pts_delay_org; msg_Dbg( p_input, "Sliding the pts by %dms pts delay at %dms picture buffer was %dms", - (int)pts_slide/1000, (int)p_input->i_pts_delay/1000, (int)buffer_size/1000); + (int)i_pts_slide/1000, (int)p_input->i_pts_delay/1000, (int)i_buffer_length/1000); vlc_mutex_lock( &p_vout->picture_lock ); /* Slide all the picture */ for( i = 0; i < I_RENDERPICTURES; i++ ) - PP_RENDERPICTURE[i]->date += pts_slide; + PP_RENDERPICTURE[i]->date += i_pts_slide; /* FIXME: slide aout/spu */ vlc_mutex_unlock( &p_vout->picture_lock ); - } } @@ -872,7 +872,7 @@ static void DecoderDecodeVideo( decoder_t *p_dec, block_t *p_block ) vout_DatePicture( p_vout, p_pic, p_pic->date ); - optimize_video_pts( p_dec ); + DecoderOptimizePtsDelay( p_dec ); vout_DisplayPicture( p_vout, p_pic ); } diff --git a/src/input/demux.c b/src/input/demux.c index b924e65d01..aa9c89fd2e 100644 --- a/src/input/demux.c +++ b/src/input/demux.c @@ -280,6 +280,8 @@ int demux_vaControlHelper( stream_t *s, case DEMUX_GET_TITLE_INFO: case DEMUX_SET_GROUP: case DEMUX_GET_ATTACHMENTS: + case DEMUX_CAN_RECORD: + case DEMUX_SET_RECORD_STATE: return VLC_EGENERIC; default: @@ -526,6 +528,7 @@ static int DStreamControl( stream_t *s, int i_query, va_list args ) case STREAM_CONTROL_ACCESS: case STREAM_GET_CONTENT_TYPE: + case STREAM_SET_RECORD_STATE: return VLC_EGENERIC; default: diff --git a/src/input/input.c b/src/input/input.c index fdd907e69c..1fad35ab8d 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -112,6 +112,7 @@ static void AppendAttachment( int *pi_attachment, input_attachment_t ***ppp_atta * - seekable (if you can seek, it doesn't say if 'bar display' has be shown * or not, for that check position != 0.0) * - can-pause + * - can-record (if a stream can be recorded while playing) * - teletext-es to get the index of spu track that is teletext --1 if no teletext) * * For intf callback upon changes * - intf-change @@ -184,6 +185,7 @@ static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item, p_input->p->i_title_offset = p_input->p->i_seekpoint_offset = 0; p_input->i_state = INIT_S; p_input->p->i_rate = INPUT_RATE_DEFAULT; + p_input->p->b_recording = false; TAB_INIT( p_input->p->i_bookmark, p_input->p->bookmark ); TAB_INIT( p_input->p->i_attachment, p_input->p->attachment ); p_input->p->p_es_out = NULL; @@ -235,6 +237,8 @@ static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item, /* Create Objects variables for public Get and Set */ input_ControlVarInit( p_input ); + /* */ + p_input->p->pts_adjust.b_auto_adjust = var_GetBool( p_input, "auto-adjust-pts-delay" ); p_input->p->input.i_cr_average = var_GetInteger( p_input, "cr-average" ); if( !p_input->b_preparsing ) @@ -1452,6 +1456,12 @@ static bool Control( input_thread_t *p_input, int i_type, case INPUT_CONTROL_SET_POSITION_OFFSET: { double f_pos; + + if( p_input->p->b_recording ) + { + msg_Err( p_input, "INPUT_CONTROL_SET_POSITION(_OFFSET) ignored while recording" ); + break; + } if( i_type == INPUT_CONTROL_SET_POSITION ) { f_pos = val.f_float; @@ -1489,6 +1499,12 @@ static bool Control( input_thread_t *p_input, int i_type, int64_t i_time; int i_ret; + if( p_input->p->b_recording ) + { + msg_Err( p_input, "INPUT_CONTROL_SET_TIME(_OFFSET) ignored while recording" ); + break; + } + if( i_type == INPUT_CONTROL_SET_TIME ) { i_time = val.i_time; @@ -1744,6 +1760,11 @@ static bool Control( input_thread_t *p_input, int i_type, case INPUT_CONTROL_SET_TITLE: case INPUT_CONTROL_SET_TITLE_NEXT: case INPUT_CONTROL_SET_TITLE_PREV: + if( p_input->p->b_recording ) + { + msg_Err( p_input, "INPUT_CONTROL_SET_TITLE(*) ignored while recording" ); + break; + } if( p_input->p->input.b_title_demux && p_input->p->input.i_title > 0 ) { @@ -1791,6 +1812,12 @@ static bool Control( input_thread_t *p_input, int i_type, case INPUT_CONTROL_SET_SEEKPOINT: case INPUT_CONTROL_SET_SEEKPOINT_NEXT: case INPUT_CONTROL_SET_SEEKPOINT_PREV: + if( p_input->p->b_recording ) + { + msg_Err( p_input, "INPUT_CONTROL_SET_SEEKPOINT(*) ignored while recording" ); + break; + } + if( p_input->p->input.b_title_demux && p_input->p->input.i_title > 0 ) { @@ -1918,6 +1945,24 @@ static bool Control( input_thread_t *p_input, int i_type, } break; + case INPUT_CONTROL_SET_RECORD_STATE: + if( p_input->p->input.b_can_record ) + { + if( !!p_input->p->b_recording != !!val.b_bool ) + { + if( demux_Control( p_input->p->input.p_demux, + DEMUX_SET_RECORD_STATE, val.b_bool ) ) + val.b_bool = false; + + p_input->p->b_recording = val.b_bool; + } + + var_Change( p_input, "record", VLC_VAR_SETVALUE, &val, NULL ); + + b_force_update = true; + } + break; + case INPUT_CONTROL_SET_BOOKMARK: default: msg_Err( p_input, "not yet implemented" ); @@ -2056,8 +2101,8 @@ static void UpdateItemLength( input_thread_t *p_input, int64_t i_length ) *****************************************************************************/ static input_source_t *InputSourceNew( input_thread_t *p_input ) { - (void)p_input; - input_source_t *in = (input_source_t*) malloc( sizeof( input_source_t ) ); + VLC_UNUSED(p_input); + input_source_t *in = malloc( sizeof( input_source_t ) ); if( in ) memset( in, 0, sizeof( input_source_t ) ); return in; @@ -2320,6 +2365,10 @@ static int InputSourceInit( input_thread_t *p_input, goto error; } + if( demux_Control( in->p_demux, DEMUX_CAN_RECORD, &in->b_can_record ) ) + in->b_can_record = false; + var_SetBool( p_input, "can-record", in->b_can_record ); + /* Get title from demux */ if( !p_input->b_preparsing && in->i_title <= 0 ) { diff --git a/src/input/input_internal.h b/src/input/input_internal.h index c8e064147d..b7dfd019ea 100644 --- a/src/input/input_internal.h +++ b/src/input/input_internal.h @@ -63,6 +63,7 @@ typedef struct bool b_can_pause; bool b_can_pace_control; bool b_can_rate_control; + bool b_can_record; bool b_rescale_ts; bool b_eof; /* eof of demuxer */ @@ -84,6 +85,7 @@ struct input_thread_private_t bool b_can_rate_control; int i_rate; + bool b_recording; /* */ int64_t i_start; /* :start-time,0 by default */ int64_t i_stop; /* :stop-time, 0 if none */ @@ -116,10 +118,11 @@ struct input_thread_private_t input_source_t **slave; /* pts delay fixup */ - struct { + struct + { int i_num_faulty; - bool to_high; - bool auto_adjust; + bool b_to_high; + bool b_auto_adjust; } pts_adjust; /* Stats counters */ @@ -191,6 +194,8 @@ enum input_control_e INPUT_CONTROL_SET_SPU_DELAY, INPUT_CONTROL_ADD_SLAVE, + + INPUT_CONTROL_SET_RECORD_STATE, }; /* Internal helpers */ diff --git a/src/input/stream.c b/src/input/stream.c index 7e35d37bcf..db14a44cd0 100644 --- a/src/input/stream.c +++ b/src/input/stream.c @@ -25,7 +25,12 @@ # include "config.h" #endif +#include + #include +#include +#include +#include #include @@ -186,6 +191,14 @@ struct stream_sys_t /* Preparse mode ? */ bool b_quick; + + /* */ + struct + { + bool b_active; + + FILE *f; /* TODO it could be replaced by access_output_t one day */ + } record; }; /* Method 1: */ @@ -212,6 +225,8 @@ static int AStreamControl( stream_t *s, int i_query, va_list ); static void AStreamDestroy( stream_t *s ); static void UStreamDestroy( stream_t *s ); static int ASeek( stream_t *s, int64_t i_pos ); +static int ARecordSetState( stream_t *s, bool b_record, const char *psz_extension ); +static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer ); /**************************************************************************** * Method 3 helpers: @@ -314,6 +329,8 @@ stream_t *stream_AccessNew( access_t *p_access, bool b_quick ) else p_sys->method = Stream; + p_sys->record.b_active = false; + p_sys->i_pos = p_access->info.i_pos; /* Stats */ @@ -512,9 +529,15 @@ static void AStreamDestroy( stream_t *s ) vlc_object_detach( s ); - if( p_sys->method == Block ) block_ChainRelease( p_sys->block.p_first ); - else if ( p_sys->method == Immediate ) free( p_sys->immediate.p_buffer ); - else free( p_sys->stream.p_buffer ); + if( p_sys->record.b_active ) + ARecordSetState( s, false, NULL ); + + if( p_sys->method == Block ) + block_ChainRelease( p_sys->block.p_first ); + else if ( p_sys->method == Immediate ) + free( p_sys->immediate.p_buffer ); + else + free( p_sys->stream.p_buffer ); free( p_sys->p_peek ); @@ -617,6 +640,8 @@ static int AStreamControl( stream_t *s, int i_query, va_list args ) access_t *p_access = p_sys->p_access; bool *p_bool; + bool b_bool; + const char *psz_string; int64_t *pi_64, i_64; int i_int; @@ -677,6 +702,12 @@ static int AStreamControl( stream_t *s, int i_query, va_list args ) case STREAM_GET_CONTENT_TYPE: return access_Control( p_access, ACCESS_GET_CONTENT_TYPE, va_arg( args, char ** ) ); + case STREAM_SET_RECORD_STATE: + b_bool = (bool)va_arg( args, int ); + psz_string = NULL; + if( b_bool ) + psz_string = (const char*)va_arg( args, const char* ); + return ARecordSetState( s, b_bool, psz_string ); default: msg_Err( s, "invalid stream_vaControl query=0x%x", i_query ); @@ -685,7 +716,120 @@ static int AStreamControl( stream_t *s, int i_query, va_list args ) return VLC_SUCCESS; } +/**************************************************************************** + * ARecord*: record stream functions + ****************************************************************************/ + +/* TODO FIXME nearly the same logic that snapshot code */ +static char *ARecordGetFileName( stream_t *s, const char *psz_path, const char *psz_prefix, const char *psz_extension ) +{ + char *psz_file; + DIR *path; + + path = utf8_opendir( psz_path ); + if( path ) + { + closedir( path ); + + const char *psz_prefix = "vlc-record-%Y-%m-%d-%H:%M:%S-$p"; // TODO allow conf ? + char *psz_tmp = str_format( s, psz_prefix ); + if( !psz_tmp ) + return NULL; + + filename_sanitize( psz_tmp ); + if( asprintf( &psz_file, "%s"DIR_SEP"%s.%s", + psz_path, psz_tmp, psz_extension ) < 0 ) + psz_file = NULL; + free( psz_tmp ); + return psz_file; + } + else + { + psz_file = str_format( s, psz_path ); + path_sanitize( psz_file ); + return psz_file; + } +} + +static int ARecordStart( stream_t *s, const char *psz_extension ) +{ + stream_sys_t *p_sys = s->p_sys; + + DIR *path; + char *psz_file; + FILE *f; + + /* */ + if( !psz_extension ) + psz_extension = "dat"; + + /* Retreive path */ + char *psz_path = var_CreateGetString( s, "input-record-path" ); + if( !psz_path || *psz_path == '\0' ) + { + free( psz_path ); + psz_path = strdup( config_GetHomeDir() ); + } + + if( !psz_path ) + return VLC_ENOMEM; + + /* Create file name + * TODO allow prefix configuration */ + psz_file = ARecordGetFileName( s, psz_path, "vlc-record-%Y-%m-%d-%H:%M:%S-$p", psz_extension ); + + free( psz_path ); + + if( !psz_file ) + return VLC_ENOMEM; + + f = utf8_fopen( psz_file, "wb" ); + if( !f ) + { + free( psz_file ); + return VLC_EGENERIC; + } + msg_Dbg( s, "Recording into %s", psz_file ); + free( psz_file ); + + /* */ + p_sys->record.f = f; + p_sys->record.b_active = true; + return VLC_SUCCESS; +} +static int ARecordStop( stream_t *s ) +{ + stream_sys_t *p_sys = s->p_sys; + + assert( p_sys->record.b_active ); + msg_Dbg( s, "Recording completed" ); + fclose( p_sys->record.f ); + p_sys->record.b_active = false; + return VLC_SUCCESS; +} + +static int ARecordSetState( stream_t *s, bool b_record, const char *psz_extension ) +{ + stream_sys_t *p_sys = s->p_sys; + + if( !!p_sys->record.b_active == !!b_record ) + return VLC_SUCCESS; + + if( b_record ) + return ARecordStart( s, psz_extension ); + else + return ARecordStop( s ); +} +static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer ) +{ + stream_sys_t *p_sys = s->p_sys; + + assert( p_sys->record.b_active ); + + if( i_buffer ) + fwrite( p_buffer, 1, i_buffer, p_sys->record.f ); +} /**************************************************************************** * Method 1: @@ -821,6 +965,9 @@ static int AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read ) } } + if( p_sys->record.b_active && i_data > 0 ) + ARecordWrite( s, p_read, i_data ); + p_sys->i_pos += i_data; return i_data; } @@ -1165,6 +1312,9 @@ static int AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read ) } } + if( p_sys->record.b_active && i_data > 0 ) + ARecordWrite( s, p_read, i_data ); + return i_data; } @@ -1552,6 +1702,9 @@ static int AStreamReadImmediate( stream_t *s, void *p_read, unsigned int i_read } } + if( p_sys->record.b_active && i_copy > 0 ) + ARecordWrite( s, p_read, i_copy ); + p_sys->i_pos += i_to_read; return i_to_read + i_copy; diff --git a/src/input/var.c b/src/input/var.c index 9cd9629ba9..7dbb405711 100644 --- a/src/input/var.c +++ b/src/input/var.c @@ -61,6 +61,10 @@ static int EsDelayCallback ( vlc_object_t *p_this, char const *psz_cmd, static int BookmarkCallback( vlc_object_t *p_this, char const *psz_cmd, vlc_value_t oldval, vlc_value_t newval, void * ); +static int RecordCallback( vlc_object_t *p_this, char const *psz_cmd, + vlc_value_t oldval, vlc_value_t newval, + void *p_data ); + typedef struct { const char *psz_name; @@ -93,6 +97,7 @@ static const vlc_input_callback_t p_input_callbacks[] = CALLBACK( "video-es", ESCallback ), CALLBACK( "audio-es", ESCallback ), CALLBACK( "spu-es", ESCallback ), + CALLBACK( "record", RecordCallback ), CALLBACK( NULL, NULL ) }; @@ -189,9 +194,6 @@ void input_ControlVarInit ( input_thread_t *p_input ) val.i_time = 0; var_Change( p_input, "spu-delay", VLC_VAR_SETVALUE, &val, NULL ); - p_input->p->pts_adjust.auto_adjust = var_CreateGetBool( - p_input, "auto-adjust-pts-delay" ); - /* Video ES */ var_Create( p_input, "video-es", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE ); text.psz_string = _("Video Track"); @@ -404,8 +406,6 @@ void input_ControlVarTitle( input_thread_t *p_input, int i_title ) *****************************************************************************/ void input_ConfigVarInit ( input_thread_t *p_input ) { - vlc_value_t val; - /* Create Object Variables for private use only */ if( !p_input->b_preparsing ) @@ -460,14 +460,22 @@ void input_ConfigVarInit ( input_thread_t *p_input ) VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); var_Create( p_input, "clock-synchro", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT); + var_Create( p_input, "auto-adjust-pts-delay", + VLC_VAR_BOOL | VLC_VAR_DOINHERIT); } var_Create( p_input, "seekable", VLC_VAR_BOOL ); - val.b_bool = true; /* Fixed later*/ - var_Change( p_input, "seekable", VLC_VAR_SETVALUE, &val, NULL ); + var_SetBool( p_input, "seekable", true ); /* Fixed later*/ + var_Create( p_input, "can-pause", VLC_VAR_BOOL ); - val.b_bool = true; /* Fixed later*/ - var_Change( p_input, "can-pause", VLC_VAR_SETVALUE, &val, NULL ); + var_SetBool( p_input, "can-pause", true ); /* Fixed later*/ + + var_Create( p_input, "can-record", VLC_VAR_BOOL ); + var_SetBool( p_input, "can-record", false ); /* Fixed later*/ + + var_Create( p_input, "record", VLC_VAR_BOOL ); + var_SetBool( p_input, "record", false ); + var_Create( p_input, "teletext-es", VLC_VAR_INTEGER ); var_SetInteger( p_input, "teletext-es", -1 ); @@ -776,3 +784,15 @@ static int BookmarkCallback( vlc_object_t *p_this, char const *psz_cmd, return VLC_SUCCESS; } + +static int RecordCallback( vlc_object_t *p_this, char const *psz_cmd, + vlc_value_t oldval, vlc_value_t newval, + void *p_data ) +{ + input_thread_t *p_input = (input_thread_t*)p_this; + VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED(p_data); + + input_ControlPush( p_input, INPUT_CONTROL_SET_RECORD_STATE, &newval ); + + return VLC_SUCCESS; +} diff --git a/src/libvlc-module.c b/src/libvlc-module.c index fb69a12b23..a7d6310cc0 100644 --- a/src/libvlc-module.c +++ b/src/libvlc-module.c @@ -675,6 +675,10 @@ static const char *const ppsz_clock_descriptions[] = "the form \"{name=bookmark-name,time=optional-time-offset," \ "bytes=optional-byte-offset},{...}\"") +#define INPUT_RECORD_PATH_TEXT N_("Record directory or filename") +#define INPUT_RECORD_PATH_LONGTEXT N_( \ + "Directory or filename where the records will be stored" ) + // DEPRECATED #define SUB_CAT_LONGTEXT N_( \ "These options allow you to modify the behavior of the subpictures " \ @@ -1737,6 +1741,9 @@ vlc_module_begin(); add_bool( "network-synchronisation", false, NULL, NETSYNC_TEXT, NETSYNC_LONGTEXT, true ); + add_string( "input-record-path", NULL, NULL, INPUT_RECORD_PATH_TEXT, + INPUT_RECORD_PATH_LONGTEXT, true ); + /* Decoder options */ add_category_hint( N_("Decoders"), CODEC_CAT_LONGTEXT , true ); add_string( "codec", NULL, NULL, CODEC_TEXT, -- 2.39.2