Commit aa407ea9 authored by Laurent Aimar's avatar Laurent Aimar

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.
parent b08b5692
......@@ -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 */
......
......@@ -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 ) );
......
......@@ -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 ) );
......
......@@ -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 );
......
......@@ -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:
......
......@@ -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
{
......
......@@ -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" );
......
......@@ -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 );
}
......
......@@ -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:
......
......@@ -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 )
{
......
......@@ -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 */
......
......@@ -25,7 +25,12 @@
# include "config.h"
#endif
#include <dirent.h>
#include <vlc_common.h>
#include <vlc_charset.h>
#include <vlc_strings.h>
#include <vlc_osd.h>
#include <assert.h>
......@@ -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;
}