/*****************************************************************************
* es_out_timeshift.c: Es Out timeshift.
*****************************************************************************
* Copyright (C) 2008 Laurent Aimar
*
* Authors: Laurent Aimar < fenrir _AT_ videolan _DOT_ org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#if defined (_WIN32)
# include <direct.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#include <vlc_common.h>
#include <vlc_arrays.h>
#include <vlc_fs.h>
#include <vlc_mouse.h>
#include <vlc_es_out.h>
#include <vlc_block.h>
#include "input_internal.h"
#ifdef _WIN32
# include <vlc_charset.h> // FromWide
#endif
#include "es_out.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
/* XXX attribute_packed is (and MUST be) used ONLY to reduce memory usage */
#ifdef HAVE_ATTRIBUTE_PACKED
# define attribute_packed __attribute__((__packed__))
#else
# define attribute_packed
#endif
enum ts_storage_cmd_type_e
{
C_ADD = 0,
C_SEND,
C_DEL,
C_CONTROL,
C_PRIVCONTROL,
};
typedef struct attribute_packed
{
int8_t i_type;
vlc_tick_t i_date;
} ts_cmd_header_t;
typedef struct attribute_packed
{
ts_cmd_header_t header;
input_source_t *in;
es_out_id_t *p_es;
es_format_t *p_fmt;
} ts_cmd_add_t;
typedef struct attribute_packed
{
ts_cmd_header_t header;
es_out_id_t *p_es;
} ts_cmd_del_t;
typedef struct attribute_packed
{
ts_cmd_header_t header;
es_out_id_t *p_es;
union{
block_t *p_block;
int i_offset; /* We do not use file > INT_MAX */
};
} ts_cmd_send_t;
typedef struct attribute_packed
{
ts_cmd_header_t header;
int i_query;
input_source_t *in;
union
{
bool b_bool;
int i_int;
int64_t i_i64;
es_out_id_t *p_es;
struct
{
int i_int;
int64_t i_i64;
} int_i64;
struct
{
int i_int;
vlc_meta_t *p_meta;
} int_meta;
struct
{
int i_int;
vlc_epg_t *p_epg;
} int_epg;
struct
{
int i_int;
vlc_epg_event_t *p_evt;
} int_epg_evt;
struct
{
es_out_id_t *p_es;
bool b_bool;
} es_bool;
struct
{
es_out_id_t *p_es;
es_format_t *p_fmt;
} es_fmt;
struct
{
int i_cat;
int i_policy;
} es_policy;
} u;
} ts_cmd_control_t;
typedef struct attribute_packed
{
ts_cmd_header_t header;
int i_query;
input_source_t *in;
union
{
int i_int;
struct
{
vlc_tick_t i_pts_delay;
vlc_tick_t i_pts_jitter;
int i_cr_average;
} jitter;
struct
{
double f_position;
vlc_tick_t i_time;
vlc_tick_t i_normal_time;
vlc_tick_t i_length;
vlc_tick_t b_live;
} times;
} u;
} ts_cmd_privcontrol_t;
typedef union
{
ts_cmd_header_t header;
ts_cmd_add_t add;
ts_cmd_del_t del;
ts_cmd_send_t send;
ts_cmd_control_t control;
ts_cmd_privcontrol_t privcontrol;
} ts_cmd_t;
static_assert(offsetof(ts_cmd_t, header) == offsetof(ts_cmd_add_t, header), "invalid packing");
static_assert(offsetof(ts_cmd_t, header) == offsetof(ts_cmd_del_t, header), "invalid packing");
static_assert(offsetof(ts_cmd_t, header) == offsetof(ts_cmd_send_t, header), "invalid packing");
static_assert(offsetof(ts_cmd_t, header) == offsetof(ts_cmd_control_t, header), "invalid packing");
static_assert(offsetof(ts_cmd_t, header) == offsetof(ts_cmd_privcontrol_t, header), "invalid packing");
typedef struct ts_storage_t ts_storage_t;
struct ts_storage_t
{
ts_storage_t *p_next;
/* */
#ifdef _WIN32
char *psz_file; /* Filename */
#endif
size_t i_file_max; /* Max size in bytes */
int64_t i_file_size;/* Current size in bytes */
FILE *p_filew; /* FILE handle for data writing */
FILE *p_filer; /* FILE handle for data reading */
/* */
uint8_t *p_cmd_r;
uint8_t *p_cmd_w;
uint8_t *p_cmd_buf;
size_t i_cmd_buf;
};
typedef struct
{
vlc_thread_t thread;
struct es_out_timeshift *ts;
input_thread_t *p_input;
es_out_t *p_tsout;
struct vlc_input_es_out *p_out;
int64_t i_tmp_size_max;
const char *psz_tmp_path;
/* Lock for all following fields */
vlc_mutex_t lock;
vlc_cond_t wait;
vlc_sem_t done;
/* */
bool b_paused;
vlc_tick_t i_pause_date;
/* */
float rate;
float rate_source;
vlc_tick_t i_rate_date;
vlc_tick_t i_rate_delay;
/* */
vlc_tick_t i_buffering_delay;
/* */
ts_storage_t *p_storage_r;
ts_storage_t *p_storage_w;
vlc_tick_t i_cmd_delay;
} ts_thread_t;
struct es_out_id_t
{
es_out_id_t *p_es;
};
struct es_out_timeshift
{
input_thread_t *p_input;
struct vlc_input_es_out *p_out;
/* Configuration */
int64_t i_tmp_size_max; /* Maximal temporary file size in byte */
char *psz_tmp_path; /* Path for temporary files */
/* Lock for all following fields */
vlc_mutex_t lock;
/* */
bool b_delayed;
ts_thread_t *p_ts;
/* */
bool b_input_paused;
bool b_input_paused_source;
float input_rate;
float input_rate_source;
/* */
int i_es;
es_out_id_t **pp_es;
struct vlc_input_es_out out;
};
static void Del ( es_out_t *, es_out_id_t * );
static int TsStart(struct es_out_timeshift *);
static void TsAutoStop( es_out_t * );
static void TsStop( ts_thread_t * );
static void TsPushCmd( ts_thread_t *, ts_cmd_t * );
static int TsPopCmdLocked( ts_thread_t *, ts_cmd_t *, bool b_flush );
static bool TsHasCmd( ts_thread_t * );
static bool TsIsUnused( ts_thread_t * );
static int TsChangePause( ts_thread_t *, bool b_source_paused, bool b_paused, vlc_tick_t i_date );
static int TsChangeRate( ts_thread_t *, float src_rate, float rate );
static void *TsRun( void * );
static ts_storage_t *TsStorageNew( const char *psz_path, int64_t i_tmp_size_max );
static void TsStorageDelete( ts_storage_t * );
static void TsStoragePack( ts_storage_t *p_storage );
static bool TsStorageIsFull( ts_storage_t *, const ts_cmd_t *p_cmd );
static bool TsStorageIsEmpty( ts_storage_t * );
static void TsStoragePushCmd( ts_storage_t *, const ts_cmd_t *p_cmd, bool b_flush );
static void TsStoragePopCmd( ts_storage_t *p_storage, ts_cmd_t *p_cmd, bool b_flush );
static void CmdClean( ts_cmd_t * );
static int CmdInitAdd ( ts_cmd_add_t *, input_source_t *, es_out_id_t *, const es_format_t *, bool b_copy );
static void CmdInitSend ( ts_cmd_send_t *, es_out_id_t *, block_t * );
static int CmdInitDel ( ts_cmd_del_t *, es_out_id_t * );
static int CmdInitControl( ts_cmd_control_t *, input_source_t *, int i_query, va_list, bool b_copy );
static int CmdInitPrivControl( ts_cmd_privcontrol_t *, input_source_t *, int i_query, va_list, bool b_copy );
/* */
static void CmdCleanAdd ( ts_cmd_add_t * );
static void CmdCleanSend ( ts_cmd_send_t * );
static void CmdCleanControl( ts_cmd_control_t * );
static void CmdCleanPrivControl( ts_cmd_privcontrol_t * );
/* */
static void CmdExecuteAdd (struct es_out_timeshift *, ts_cmd_add_t *);
static int CmdExecuteSend (struct es_out_timeshift *, ts_cmd_send_t *);
static void CmdExecuteDel (struct es_out_timeshift *, ts_cmd_del_t *);
static int CmdExecuteControl(struct es_out_timeshift *, ts_cmd_control_t *);
static int CmdExecutePrivControl(struct es_out_timeshift *, ts_cmd_privcontrol_t *);
/* File helpers */
static int GetTmpFile( char **filename, const char *dirname )
{
if( dirname != NULL
&& asprintf( filename, "%s"DIR_SEP PACKAGE_NAME"-timeshift.XXXXXX",
dirname ) >= 0 )
{
vlc_mkdir( dirname, 0700 );
int fd = vlc_mkstemp( *filename );
if( fd != -1 )
return fd;
free( *filename );
}
*filename = strdup( DIR_SEP"tmp"DIR_SEP PACKAGE_NAME"-timeshift.XXXXXX" );
if( unlikely(*filename == NULL) )
return -1;
int fd = vlc_mkstemp( *filename );
if( fd != -1 )
return fd;
free( *filename );
return -1;
}
/*****************************************************************************
* Internal functions
*****************************************************************************/
static struct es_out_timeshift * PRIV(es_out_t *out)
{
struct vlc_input_es_out *parent = container_of(out, struct vlc_input_es_out, out);
struct es_out_timeshift *sys = container_of(parent, struct es_out_timeshift, out);
return sys;
}
static void Destroy( es_out_t *p_out )
{
struct es_out_timeshift *p_sys = PRIV(p_out);
if( p_sys->b_delayed )
{
TsStop( p_sys->p_ts );
p_sys->b_delayed = false;
}
while( p_sys->i_es > 0 )
Del( p_out, p_sys->pp_es[0] );
TAB_CLEAN( p_sys->i_es, p_sys->pp_es );
free( p_sys->psz_tmp_path );
free( p_sys );
}
static es_out_id_t *Add( es_out_t *p_out, input_source_t *in, const es_format_t *p_fmt )
{
struct es_out_timeshift *p_sys = PRIV(p_out);
ts_cmd_add_t cmd;
es_out_id_t *p_es = malloc( sizeof( *p_es ) );
if( !p_es )
return NULL;
vlc_mutex_lock( &p_sys->lock );
TsAutoStop( p_out );
if( CmdInitAdd( &cmd, in, p_es, p_fmt, p_sys->b_delayed ) )
{
vlc_mutex_unlock( &p_sys->lock );
free( p_es );
return NULL;
}
if( p_sys->b_delayed )
TsPushCmd( p_sys->p_ts, (ts_cmd_t *) &cmd );
else
CmdExecuteAdd(p_sys, &cmd);
vlc_mutex_unlock( &p_sys->lock );
return p_es;
}
static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
{
struct es_out_timeshift *p_sys = PRIV(p_out);
ts_cmd_send_t cmd;
int i_ret = VLC_SUCCESS;
vlc_mutex_lock( &p_sys->lock );
TsAutoStop( p_out );
CmdInitSend( &cmd, p_es, p_block );
if( p_sys->b_delayed )
TsPushCmd( p_sys->p_ts, (ts_cmd_t *)&cmd );
else
i_ret = CmdExecuteSend(p_sys, &cmd) ;
vlc_mutex_unlock( &p_sys->lock );
return i_ret;
}
static void Del( es_out_t *p_out, es_out_id_t *p_es )
{
struct es_out_timeshift *p_sys = PRIV(p_out);
ts_cmd_del_t cmd;
vlc_mutex_lock( &p_sys->lock );
TsAutoStop( p_out );
CmdInitDel( &cmd, p_es );
if( p_sys->b_delayed )
TsPushCmd( p_sys->p_ts, (ts_cmd_t *)&cmd );
else
CmdExecuteDel(p_sys, &cmd);
vlc_mutex_unlock( &p_sys->lock );
}
static inline int es_out_in_vaControl(struct vlc_input_es_out *p_out,
input_source_t *in,
int i_query,
va_list args)
{
return p_out->out.cbs->control(&p_out->out, in, i_query, args);
}
static inline int es_out_in_Control(struct vlc_input_es_out *p_out,
input_source_t *in,
int i_query, ...)
{
va_list args;
int i_result;
va_start( args, i_query );
i_result = es_out_in_vaControl( p_out, in, i_query, args );
va_end( args );
return i_result;
}
static inline int es_out_in_vaPrivControl(struct vlc_input_es_out *out,
input_source_t *in,
int i_query,
va_list args)
{
return out->ops->priv_control(out, in, i_query, args);
}
static inline int es_out_in_PrivControl(struct vlc_input_es_out *p_out,
input_source_t *in,
int i_query, ... )
{
va_list args;
int i_result;
va_start( args, i_query );
i_result = es_out_in_vaPrivControl( p_out, in, i_query, args );
va_end( args );
return i_result;
}
static int
ControlLockedIsEmpty(struct es_out_timeshift *p_sys, input_source_t *in,
bool *pb_empty)
{
if( p_sys->b_delayed && TsHasCmd( p_sys->p_ts ) )
*pb_empty = false;
else
{
int ret = es_out_in_Control( p_sys->p_out, in, ES_OUT_IS_EMPTY,
pb_empty );
assert( !ret );
}
return VLC_SUCCESS;
}
static int
ControlLockedGetWakeup(struct es_out_timeshift *p_sys,
input_source_t *in,
vlc_tick_t *pi_wakeup)
{
if( p_sys->b_delayed )
{
assert( !input_CanPaceControl( p_sys->p_input ) );
*pi_wakeup = 0;
}
else
{
int ret = es_out_in_PrivControl( p_sys->p_out, in, ES_OUT_PRIV_GET_WAKE_UP,
pi_wakeup );
assert( !ret );
}
return VLC_SUCCESS;
}
static int
ControlLockedGetBuffering(struct es_out_timeshift *p_sys,
input_source_t *in,
bool *pb_buffering)
{
if( p_sys->b_delayed )
*pb_buffering = true;
else
{
int ret = es_out_in_PrivControl( p_sys->p_out, in, ES_OUT_PRIV_GET_BUFFERING,
pb_buffering );
assert( !ret );
}
return VLC_SUCCESS;
}
static int
ControlLockedSetPauseState(struct es_out_timeshift *p_sys,
input_source_t *in,
bool b_source_paused,
bool b_paused,
vlc_tick_t i_date)
{
int i_ret;
if( !p_sys->b_delayed && !b_source_paused == !b_paused )
{
i_ret = es_out_in_PrivControl( p_sys->p_out, in, ES_OUT_PRIV_SET_PAUSE_STATE,
b_source_paused, b_paused, i_date );
}
else
{
i_ret = VLC_EGENERIC;
if( !input_CanPaceControl( p_sys->p_input ) )
{
if( !p_sys->b_delayed )
TsStart(p_sys);
if( p_sys->b_delayed )
i_ret = TsChangePause( p_sys->p_ts, b_source_paused, b_paused, i_date );
}
else
{
/* XXX we may do it BUT it would be better to finish the clock clean up+improvements
* and so be able to advertise correctly pace control property in access
* module */
msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have pace control" );
}
}
if( !i_ret )
{
p_sys->b_input_paused_source = b_source_paused;
p_sys->b_input_paused = b_paused;
}
return i_ret;
}
static int
ControlLockedSetRate(struct es_out_timeshift *p_sys,
input_source_t *in,
float src_rate,
float rate)
{
int i_ret;
if( !p_sys->b_delayed && src_rate == rate )
{
i_ret = es_out_in_PrivControl( p_sys->p_out, in, ES_OUT_PRIV_SET_RATE,
src_rate, rate );
}
else
{
i_ret = VLC_EGENERIC;
if( !input_CanPaceControl( p_sys->p_input ) )
{
if( !p_sys->b_delayed )
TsStart(p_sys);
if( p_sys->b_delayed )
i_ret = TsChangeRate( p_sys->p_ts, src_rate, rate );
}
else
{
/* XXX we may do it BUT it would be better to finish the clock clean up+improvements
* and so be able to advertise correctly pace control property in access
* module */
msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have pace control" );
}
}
if( !i_ret )
{
p_sys->input_rate_source = src_rate;
p_sys->input_rate = rate;
}
return i_ret;
}
static int ControlLockedResetPcrDecoder(struct es_out_timeshift *p_sys, input_source_t *in,
vlc_tick_t duration)
{
return es_out_in_PrivControl(p_sys->p_out, in, ES_OUT_PRIV_RESET_PCR_FRAME_PREV, duration);
}
static int ControlLockedSetFrameNext(struct es_out_timeshift *p_sys, input_source_t *in )
{
return es_out_in_PrivControl( p_sys->p_out, in, ES_OUT_PRIV_SET_FRAME_NEXT );
}
static int ControlLockedSetFramePrevious(struct es_out_timeshift *p_sys, input_source_t *in )
{
return es_out_in_PrivControl( p_sys->p_out, in, ES_OUT_PRIV_SET_FRAME_PREVIOUS );
}
static int ControlLocked( es_out_t *p_out, input_source_t *in, int i_query,
va_list args )
{
struct es_out_timeshift *p_sys = PRIV(p_out);
switch( i_query )
{
/* Pass-through control */
case ES_OUT_SET_GROUP:
case ES_OUT_SET_PCR:
case ES_OUT_SET_GROUP_PCR:
case ES_OUT_RESET_PCR:
case ES_OUT_SET_NEXT_DISPLAY_TIME:
case ES_OUT_SET_GROUP_META:
case ES_OUT_SET_GROUP_EPG:
case ES_OUT_SET_GROUP_EPG_EVENT:
case ES_OUT_SET_EPG_TIME:
case ES_OUT_SET_ES_SCRAMBLED_STATE:
case ES_OUT_DEL_GROUP:
case ES_OUT_SET_META:
case ES_OUT_SET_ES:
case ES_OUT_UNSET_ES:
case ES_OUT_RESTART_ES:
case ES_OUT_SET_ES_DEFAULT:
case ES_OUT_SET_ES_STATE:
case ES_OUT_SET_ES_CAT_POLICY:
case ES_OUT_SET_ES_FMT:
case ES_OUT_DRAIN:
{
ts_cmd_control_t cmd;
if( CmdInitControl( &cmd, in, i_query, args, p_sys->b_delayed ) )
return VLC_EGENERIC;
if( p_sys->b_delayed )
{
TsPushCmd( p_sys->p_ts, (ts_cmd_t *) &cmd );
return VLC_SUCCESS;
}
return CmdExecuteControl(p_sys, &cmd);
}
/* Special control when delayed */
case ES_OUT_GET_ES_STATE:
{
es_out_id_t *p_es = va_arg( args, es_out_id_t * );
bool *pb_enabled = va_arg( args, bool* );
if( p_sys->b_delayed )
{
*pb_enabled = true;
return VLC_SUCCESS;
}
return es_out_in_Control( p_sys->p_out, in, ES_OUT_GET_ES_STATE,
p_es->p_es, pb_enabled );
}
case ES_OUT_VOUT_SET_MOUSE_EVENT:
{
es_out_id_t *p_es = va_arg( args, es_out_id_t * );
vlc_mouse_event cb = va_arg( args, vlc_mouse_event );
void *user_data = va_arg( args, void * );
return es_out_in_Control( p_sys->p_out, in, ES_OUT_VOUT_SET_MOUSE_EVENT,
p_es->p_es, cb, user_data );
}
case ES_OUT_VOUT_ADD_OVERLAY:
{
es_out_id_t *p_es = va_arg( args, es_out_id_t * );
subpicture_t *sub = va_arg( args, subpicture_t * );
size_t *channel = va_arg( args, size_t * );
return es_out_in_Control( p_sys->p_out, in, ES_OUT_VOUT_ADD_OVERLAY,
p_es->p_es, sub, channel );
}
case ES_OUT_VOUT_DEL_OVERLAY:
{
es_out_id_t *p_es = va_arg( args, es_out_id_t * );
size_t channel = va_arg( args, size_t );
return es_out_in_Control( p_sys->p_out, in, ES_OUT_VOUT_DEL_OVERLAY,
p_es->p_es, channel );
}
case ES_OUT_SPU_SET_HIGHLIGHT:
{
es_out_id_t *p_es = va_arg( args, es_out_id_t * );
const struct vlc_spu_highlight_t *p_hl = va_arg( args, const struct vlc_spu_highlight_t * );
return es_out_in_Control( p_sys->p_out, in, ES_OUT_SPU_SET_HIGHLIGHT,
p_es->p_es, p_hl );
}
/* Special internal input control */
case ES_OUT_IS_EMPTY:
{
bool *pb_empty = va_arg( args, bool* );
return ControlLockedIsEmpty(p_sys, in, pb_empty);
}
case ES_OUT_POST_SUBNODE:
return es_out_in_vaControl( p_sys->p_out, in, i_query, args );
default:
vlc_assert_unreachable();
return VLC_EGENERIC;
}
}
static int Control( es_out_t *p_tsout, input_source_t *in, int i_query, va_list args )
{
struct es_out_timeshift *p_sys = PRIV(p_tsout);
int i_ret;
vlc_mutex_lock( &p_sys->lock );
TsAutoStop( p_tsout );
i_ret = ControlLocked( p_tsout, in, i_query, args );
vlc_mutex_unlock( &p_sys->lock );
return i_ret;
}
static int PrivControlLocked(struct vlc_input_es_out *p_tsout,
input_source_t *in,
int i_query,
va_list args )
{
struct es_out_timeshift *p_sys = PRIV(&p_tsout->out);
switch( i_query )
{
/* Pass-through control */
case ES_OUT_PRIV_SET_MODE:
case ES_OUT_PRIV_SET_TIMES:
case ES_OUT_PRIV_SET_JITTER:
case ES_OUT_PRIV_SET_EOS:
{
ts_cmd_t cmd;
if( CmdInitPrivControl( &cmd.privcontrol, in, i_query, args, p_sys->b_delayed ) )
return VLC_EGENERIC;
if( p_sys->b_delayed )
{
TsPushCmd( p_sys->p_ts, &cmd );
return VLC_SUCCESS;
}
return CmdExecutePrivControl(p_sys, &cmd.privcontrol);
}
case ES_OUT_PRIV_GET_WAKE_UP: /* TODO ? */
{
vlc_tick_t *pi_wakeup = va_arg( args, vlc_tick_t* );
return ControlLockedGetWakeup(p_sys, in, pi_wakeup);
}
case ES_OUT_PRIV_GET_BUFFERING:
{
bool *pb_buffering = va_arg( args, bool* );
return ControlLockedGetBuffering(p_sys, in, pb_buffering);
}
case ES_OUT_PRIV_SET_PAUSE_STATE:
{
const bool b_source_paused = (bool)va_arg( args, int );
const bool b_paused = (bool)va_arg( args, int );
const vlc_tick_t i_date = va_arg( args, vlc_tick_t );
return ControlLockedSetPauseState(p_sys, in, b_source_paused, b_paused, i_date);
}
case ES_OUT_PRIV_SET_REBUFFER_STATE:
{
const bool b_rebuffering = (bool)va_arg( args, int );
const vlc_tick_t i_date = va_arg( args, vlc_tick_t );
return es_out_in_PrivControl(p_sys->p_out, in,
ES_OUT_PRIV_SET_REBUFFER_STATE,
b_rebuffering, i_date);
}
case ES_OUT_PRIV_SET_RATE:
{
const float src_rate = va_arg( args, double );
const float rate = va_arg( args, double );
return ControlLockedSetRate(p_sys, in, src_rate, rate);
}
case ES_OUT_PRIV_RESET_PCR_FRAME_PREV:
{
const vlc_tick_t duration = va_arg( args, vlc_tick_t );
return ControlLockedResetPcrDecoder(p_sys, in, duration);
}
case ES_OUT_PRIV_SET_FRAME_NEXT:
{
return ControlLockedSetFrameNext(p_sys, in);
}
case ES_OUT_PRIV_SET_FRAME_PREVIOUS:
{
return ControlLockedSetFramePrevious(p_sys, in);
}
case ES_OUT_PRIV_GET_GROUP_FORCED:
return es_out_in_vaPrivControl( p_sys->p_out, in, i_query, args );
/* Invalid queries for this es_out level */
case ES_OUT_PRIV_SET_ES:
case ES_OUT_PRIV_UNSET_ES:
case ES_OUT_PRIV_RESTART_ES:
case ES_OUT_PRIV_SET_ES_CAT_IDS:
case ES_OUT_PRIV_SET_ES_LIST:
case ES_OUT_PRIV_STOP_ALL_ES:
case ES_OUT_PRIV_START_ALL_ES:
case ES_OUT_PRIV_SET_ES_DELAY:
case ES_OUT_PRIV_SET_DELAY:
case ES_OUT_PRIV_SET_RECORD_STATE:
case ES_OUT_PRIV_SET_VBI_PAGE:
case ES_OUT_PRIV_SET_VBI_TRANSPARENCY:
default: vlc_assert_unreachable();
}
}
static int PrivControl(struct vlc_input_es_out *p_tsout, input_source_t *in, int i_query, va_list args )
{
struct es_out_timeshift *p_sys = PRIV(&p_tsout->out);
int i_ret;
vlc_mutex_lock( &p_sys->lock );
TsAutoStop(&p_tsout->out);
i_ret = PrivControlLocked(p_tsout, in, i_query, args);
vlc_mutex_unlock( &p_sys->lock );
return i_ret;
}
static const struct es_out_callbacks es_out_timeshift_cbs =
{
.add = Add,
.send = Send,
.del = Del,
.control = Control,
.destroy = Destroy,
};
/*****************************************************************************
* input_EsOutTimeshiftNew:
*****************************************************************************/
struct vlc_input_es_out *
input_EsOutTimeshiftNew(input_thread_t *p_input,
struct vlc_input_es_out *p_next_out,
float rate)
{
struct es_out_timeshift *p_sys = malloc( sizeof(*p_sys) );
if( !p_sys )
return NULL;
static const struct vlc_input_es_out_ops timeshift_ops =
{
.priv_control = PrivControl,
};
p_sys->out.ops = ×hift_ops;
p_sys->out.out.cbs = &es_out_timeshift_cbs;
/* */
p_sys->b_input_paused = false;
p_sys->b_input_paused_source = false;
p_sys->p_input = p_input;
p_sys->input_rate = rate;
p_sys->input_rate_source = rate;
p_sys->p_out = p_next_out;
vlc_mutex_init_recursive( &p_sys->lock );
p_sys->b_delayed = false;
p_sys->p_ts = NULL;
TAB_INIT( p_sys->i_es, p_sys->pp_es );
/* */
const int i_tmp_size_max = var_CreateGetInteger( p_input, "input-timeshift-granularity" );
if( i_tmp_size_max < 0 )
p_sys->i_tmp_size_max = 50*1024*1024;
else
p_sys->i_tmp_size_max = __MAX( i_tmp_size_max, 1*1024*1024 );
msg_Dbg( p_input, "using timeshift granularity of %d MiB",
(int)p_sys->i_tmp_size_max/(1024*1024) );
p_sys->psz_tmp_path = var_InheritString( p_input, "input-timeshift-path" );
#if defined (_WIN32)
if( p_sys->psz_tmp_path == NULL )
{
const DWORD count = GetTempPathW( 0, NULL );
if( count > 0 )
{
WCHAR *path = vlc_alloc( count + 1, sizeof(WCHAR) );
if( path != NULL )
{
DWORD ret = GetTempPathW( count + 1, path );
if( ret != 0 && ret <= count )
p_sys->psz_tmp_path = FromWide( path );
free( path );
}
}
}
if( p_sys->psz_tmp_path == NULL )
{
wchar_t *wpath = _wgetcwd( NULL, 0 );
if( wpath != NULL )
{
p_sys->psz_tmp_path = FromWide( wpath );
free( wpath );
}
}
if( p_sys->psz_tmp_path == NULL )
p_sys->psz_tmp_path = strdup( "C:" );
if( p_sys->psz_tmp_path != NULL )
{
size_t len = strlen( p_sys->psz_tmp_path );
while( len > 0 && p_sys->psz_tmp_path[len - 1] == DIR_SEP_CHAR )
len--;
p_sys->psz_tmp_path[len] = '\0';
}
#endif
if( p_sys->psz_tmp_path != NULL )
msg_Dbg( p_input, "using timeshift path: %s", p_sys->psz_tmp_path );
else
msg_Dbg( p_input, "using default timeshift path" );
#if 0
#define S(t) msg_Err( p_input, "SIZEOF("#t")=%zd", sizeof(t) )
S(ts_cmd_t);
S(ts_cmd_control_t);
S(ts_cmd_privcontrol_t);
S(ts_cmd_send_t);
S(ts_cmd_del_t);
S(ts_cmd_add_t);
#undef S
#endif
return &p_sys->out;
}
/*****************************************************************************
*
*****************************************************************************/
static void TsDestroy( ts_thread_t *p_ts )
{
free( p_ts );
}
static int TsStart(struct es_out_timeshift *p_sys)
{
ts_thread_t *p_ts;
es_out_t *p_out = &p_sys->out.out;
assert( !p_sys->b_delayed );
p_sys->p_ts = p_ts = calloc(1, sizeof(*p_ts));
if( !p_ts )
return VLC_EGENERIC;
p_ts->i_tmp_size_max = p_sys->i_tmp_size_max;
p_ts->psz_tmp_path = p_sys->psz_tmp_path;
p_ts->p_input = p_sys->p_input;
p_ts->ts = p_sys;
p_ts->p_out = p_sys->p_out;
p_ts->p_tsout = p_out;
vlc_mutex_init( &p_ts->lock );
vlc_cond_init( &p_ts->wait );
vlc_sem_init( &p_ts->done, 0 );
p_ts->b_paused = p_sys->b_input_paused && !p_sys->b_input_paused_source;
p_ts->i_pause_date = p_ts->b_paused ? vlc_tick_now() : -1;
p_ts->rate_source = p_sys->input_rate_source;
p_ts->rate = p_sys->input_rate;
p_ts->i_rate_date = -1;
p_ts->i_rate_delay = 0;
p_ts->i_buffering_delay = 0;
p_ts->i_cmd_delay = 0;
p_ts->p_storage_r = NULL;
p_ts->p_storage_w = NULL;
p_sys->b_delayed = true;
if( vlc_clone( &p_ts->thread, TsRun, p_ts ) )
{
msg_Err( p_sys->p_input, "cannot create timeshift thread" );
TsDestroy( p_ts );
p_sys->b_delayed = false;
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static void TsAutoStop( es_out_t *p_out )
{
struct es_out_timeshift *p_sys = PRIV(p_out);
if( !p_sys->b_delayed || !TsIsUnused( p_sys->p_ts ) )
return;
msg_Warn( p_sys->p_input, "es out timeshift: auto stop" );
TsStop( p_sys->p_ts );
p_sys->b_delayed = false;
}
static void TsStop( ts_thread_t *p_ts )
{
vlc_mutex_lock( &p_ts->lock );
vlc_sem_post( &p_ts->done );
vlc_cond_signal( &p_ts->wait );
vlc_mutex_unlock( &p_ts->lock );
vlc_join( p_ts->thread, NULL );
vlc_mutex_lock( &p_ts->lock );
for( ;; )
{
ts_cmd_t cmd;
if( TsPopCmdLocked( p_ts, &cmd, true ) )
break;
CmdClean( &cmd );
}
assert( !p_ts->p_storage_r || !p_ts->p_storage_r->p_next );
if( p_ts->p_storage_r )
TsStorageDelete( p_ts->p_storage_r );
vlc_mutex_unlock( &p_ts->lock );
TsDestroy( p_ts );
}
static void TsPushCmd( ts_thread_t *p_ts, ts_cmd_t *p_cmd )
{
vlc_mutex_lock( &p_ts->lock );
if( !p_ts->p_storage_w || TsStorageIsFull( p_ts->p_storage_w, p_cmd ) )
{
ts_storage_t *p_storage = TsStorageNew( p_ts->psz_tmp_path, p_ts->i_tmp_size_max );
if( !p_storage )
{
CmdClean( p_cmd );
vlc_mutex_unlock( &p_ts->lock );
/* TODO warn the user (but only once) */
return;
}
if( !p_ts->p_storage_w )
{
p_ts->p_storage_r = p_ts->p_storage_w = p_storage;
}
else
{
TsStoragePack( p_ts->p_storage_w );
p_ts->p_storage_w->p_next = p_storage;
p_ts->p_storage_w = p_storage;
}
}
/* TODO return error and warn the user (but only once) */
TsStoragePushCmd( p_ts->p_storage_w, p_cmd, p_ts->p_storage_r == p_ts->p_storage_w );
vlc_cond_signal( &p_ts->wait );
vlc_mutex_unlock( &p_ts->lock );
}
static int TsPopCmdLocked( ts_thread_t *p_ts, ts_cmd_t *p_cmd, bool b_flush )
{
vlc_mutex_assert( &p_ts->lock );
if( TsStorageIsEmpty( p_ts->p_storage_r ) )
return VLC_EGENERIC;
TsStoragePopCmd( p_ts->p_storage_r, p_cmd, b_flush );
while( TsStorageIsEmpty( p_ts->p_storage_r ) )
{
ts_storage_t *p_next = p_ts->p_storage_r->p_next;
if( !p_next )
break;
TsStorageDelete( p_ts->p_storage_r );
p_ts->p_storage_r = p_next;
}
return VLC_SUCCESS;
}
static bool TsHasCmd( ts_thread_t *p_ts )
{
bool b_cmd;
vlc_mutex_lock( &p_ts->lock );
b_cmd = !TsStorageIsEmpty( p_ts->p_storage_r );
vlc_mutex_unlock( &p_ts->lock );
return b_cmd;
}
static bool TsIsUnused( ts_thread_t *p_ts )
{
bool b_unused;
vlc_mutex_lock( &p_ts->lock );
b_unused = !p_ts->b_paused &&
p_ts->rate == p_ts->rate_source &&
TsStorageIsEmpty( p_ts->p_storage_r );
vlc_mutex_unlock( &p_ts->lock );
return b_unused;
}
static int TsChangePause( ts_thread_t *p_ts, bool b_source_paused, bool b_paused, vlc_tick_t i_date )
{
vlc_mutex_lock( &p_ts->lock );
int i_ret;
if( b_paused )
{
assert( !b_source_paused );
i_ret = es_out_SetPauseState( p_ts->p_out, true, true, i_date );
}
else
{
i_ret = es_out_SetPauseState( p_ts->p_out, false, false, i_date );
}
if( !i_ret )
{
if( !b_paused )
{
assert( p_ts->i_pause_date > 0 );
p_ts->i_cmd_delay += i_date - p_ts->i_pause_date;
}
p_ts->b_paused = b_paused;
p_ts->i_pause_date = i_date;
vlc_cond_signal( &p_ts->wait );
}
vlc_mutex_unlock( &p_ts->lock );
return i_ret;
}
static int TsChangeRate( ts_thread_t *p_ts, float src_rate, float rate )
{
int i_ret;
vlc_mutex_lock( &p_ts->lock );
p_ts->i_cmd_delay += p_ts->i_rate_delay;
p_ts->i_rate_date = -1;
p_ts->i_rate_delay = 0;
p_ts->rate = rate;
p_ts->rate_source = src_rate;
i_ret = es_out_SetRate( p_ts->p_out, rate, rate );
vlc_mutex_unlock( &p_ts->lock );
return i_ret;
}
static void *TsRun( void *p_data )
{
vlc_thread_set_name("vlc-timeshift");
ts_thread_t *p_ts = p_data;
vlc_tick_t i_buffering_date = -1;
vlc_mutex_lock( &p_ts->lock );
while( vlc_sem_trywait( &p_ts->done ) != 0 )
{
ts_cmd_t cmd;
vlc_tick_t i_deadline;
/* Pop a command to execute */
bool b_buffering = es_out_GetBuffering( p_ts->p_out );
if( ( p_ts->b_paused && !b_buffering )
|| TsPopCmdLocked( p_ts, &cmd, false ) )
{
vlc_cond_wait( &p_ts->wait, &p_ts->lock );
continue;
}
if( b_buffering && i_buffering_date < 0 )
{
i_buffering_date = cmd.header.i_date;
}
else if( i_buffering_date > 0 )
{
p_ts->i_buffering_delay += i_buffering_date - cmd.header.i_date; /* It is < 0 */
if( b_buffering )
i_buffering_date = cmd.header.i_date;
else
i_buffering_date = -1;
}
if( p_ts->i_rate_date < 0 )
p_ts->i_rate_date = cmd.header.i_date;
p_ts->i_rate_delay = 0;
if( p_ts->rate_source != p_ts->rate )
{
const vlc_tick_t i_duration = cmd.header.i_date - p_ts->i_rate_date;
p_ts->i_rate_delay = i_duration * p_ts->rate_source / p_ts->rate - i_duration;
}
if( p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay < 0 && p_ts->rate != p_ts->rate_source )
{
/* Auto reset to rate 1.0 */
msg_Warn( p_ts->p_input, "es out timeshift: auto reset rate to %f", p_ts->rate_source );
p_ts->i_cmd_delay = 0;
p_ts->i_buffering_delay = 0;
p_ts->i_rate_delay = 0;
p_ts->i_rate_date = -1;
p_ts->rate = p_ts->rate_source;
if( !es_out_SetRate( p_ts->p_out, p_ts->rate_source, p_ts->rate ) )
{
vlc_value_t val = { .f_float = p_ts->rate };
/* Warn back input
* FIXME it is perfectly safe BUT it is ugly as it may hide a
* rate change requested by user */
input_ControlPushHelper( p_ts->p_input, INPUT_CONTROL_SET_RATE, &val );
}
}
i_deadline = cmd.header.i_date + p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay;
vlc_mutex_unlock( &p_ts->lock );
/* Regulate the speed of command processing to the same one than
* reading */
if( vlc_sem_timedwait( &p_ts->done, i_deadline ) == 0 )
{
CmdClean( &cmd );
return NULL;
}
/* Execute the command */
switch( cmd.header.i_type )
{
case C_ADD:
CmdExecuteAdd(p_ts->ts, &cmd.add);
CmdCleanAdd( &cmd.add );
break;
case C_SEND:
CmdExecuteSend(p_ts->ts, &cmd.send );
CmdCleanSend( &cmd.send );
break;
case C_CONTROL:
CmdExecuteControl(p_ts->ts, &cmd.control);
CmdCleanControl( &cmd.control );
break;
case C_PRIVCONTROL:
CmdExecutePrivControl(p_ts->ts, &cmd.privcontrol);
CmdCleanPrivControl( &cmd.privcontrol );
break;
case C_DEL:
CmdExecuteDel(p_ts->ts, &cmd.del);
break;
default:
vlc_assert_unreachable();
break;
}
vlc_mutex_lock( &p_ts->lock );
}
vlc_mutex_unlock( &p_ts->lock );
return NULL;
}
/*****************************************************************************
*
*****************************************************************************/
#define MAX_COMMAND_SIZE sizeof(ts_cmd_t)
#define TS_STORAGE_COMMAND_PREALLOC 30000
static const size_t TsStorageSizeofCommand[] =
{
[C_ADD] = sizeof(ts_cmd_add_t),
[C_SEND] = sizeof(ts_cmd_send_t),
[C_DEL] = sizeof(ts_cmd_del_t),
[C_CONTROL] = sizeof(ts_cmd_control_t),
[C_PRIVCONTROL] = sizeof(ts_cmd_privcontrol_t)
};
static ts_storage_t *TsStorageNew( const char *psz_tmp_path, int64_t i_tmp_size_max )
{
ts_storage_t *p_storage = malloc( sizeof (*p_storage) );
if( unlikely(p_storage == NULL) )
return NULL;
char *psz_file;
int fd = GetTmpFile( &psz_file, psz_tmp_path );
if( fd == -1 )
{
free( p_storage );
return NULL;
}
p_storage->p_filew = fdopen( fd, "w+b" );
if( p_storage->p_filew == NULL )
{
vlc_close( fd );
vlc_unlink( psz_file );
goto error;
}
p_storage->p_filer = vlc_fopen( psz_file, "rb" );
if( p_storage->p_filer == NULL )
{
fclose( p_storage->p_filew );
vlc_unlink( psz_file );
goto error;
}
#ifndef _WIN32
vlc_unlink( psz_file );
free( psz_file );
#else
p_storage->psz_file = psz_file;
#endif
p_storage->p_next = NULL;
/* */
p_storage->i_file_max = i_tmp_size_max;
p_storage->i_file_size = 0;
/* */
p_storage->p_cmd_buf = vlc_alloc( TS_STORAGE_COMMAND_PREALLOC, MAX_COMMAND_SIZE );
p_storage->i_cmd_buf = TS_STORAGE_COMMAND_PREALLOC * MAX_COMMAND_SIZE;
p_storage->p_cmd_w = p_storage->p_cmd_buf;
p_storage->p_cmd_r = p_storage->p_cmd_buf;
//fprintf( stderr, "\nSTORAGE name=%s size=%d KiB\n", p_storage->psz_file, p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) /1024 );
if( !p_storage->p_cmd_buf )
{
TsStorageDelete( p_storage );
return NULL;
}
return p_storage;
error:
free( psz_file );
free( p_storage );
return NULL;
}
static void TsStorageDelete( ts_storage_t *p_storage )
{
while( p_storage->p_cmd_r < p_storage->p_cmd_w )
{
ts_cmd_t cmd;
TsStoragePopCmd( p_storage, &cmd, true );
CmdClean( &cmd );
}
free( p_storage->p_cmd_buf );
fclose( p_storage->p_filer );
fclose( p_storage->p_filew );
#ifdef _WIN32
vlc_unlink( p_storage->psz_file );
free( p_storage->psz_file );
#endif
free( p_storage );
}
static void TsStoragePack( ts_storage_t *p_storage )
{
/* Try to release a bit of memory */
if( (size_t)(p_storage->p_cmd_w - p_storage->p_cmd_buf) == p_storage->i_cmd_buf )
return;
size_t i_realloc = p_storage->p_cmd_w - p_storage->p_cmd_buf;
uint8_t *p_realloc = realloc( p_storage->p_cmd_buf, i_realloc );
if( p_realloc )
{
p_storage->p_cmd_r = p_realloc + (p_storage->p_cmd_r - p_storage->p_cmd_buf);
p_storage->p_cmd_w = p_realloc + i_realloc;
p_storage->i_cmd_buf = i_realloc;
p_storage->p_cmd_buf = p_realloc;
}
}
static bool TsStorageIsFull( ts_storage_t *p_storage, const ts_cmd_t *p_cmd )
{
if( p_cmd && p_cmd->header.i_type == C_SEND && p_storage->p_cmd_w )
{
size_t i_size = sizeof(*p_cmd->send.p_block) + p_cmd->send.p_block->i_buffer;
if( p_storage->i_file_size + i_size >= p_storage->i_file_max )
return true;
}
return (size_t)(p_storage->p_cmd_w - p_storage->p_cmd_buf) > p_storage->i_cmd_buf - MAX_COMMAND_SIZE;
}
static bool TsStorageIsEmpty( ts_storage_t *p_storage )
{
return !p_storage || p_storage->p_cmd_r >= p_storage->p_cmd_w;
}
static void TsStoragePushCmd( ts_storage_t *p_storage, const ts_cmd_t *p_cmd, bool b_flush )
{
assert( !TsStorageIsFull( p_storage, p_cmd ) );
ts_cmd_t cmd;
memcpy(&cmd, p_cmd, TsStorageSizeofCommand[p_cmd->header.i_type]);
if( cmd.header.i_type == C_SEND )
{
block_t *p_block = cmd.send.p_block;
cmd.send.p_block = NULL;
cmd.send.i_offset = ftell( p_storage->p_filew );
if( fwrite( p_block, sizeof(*p_block), 1, p_storage->p_filew ) != 1 )
{
block_Release( p_block );
return;
}
p_storage->i_file_size += sizeof(*p_block);
if( p_block->i_buffer > 0 )
{
if( fwrite( p_block->p_buffer, p_block->i_buffer, 1, p_storage->p_filew ) != 1 )
{
block_Release( p_block );
return;
}
}
p_storage->i_file_size += p_block->i_buffer;
block_Release( p_block );
if( b_flush )
fflush( p_storage->p_filew );
}
size_t i_cmdsize = TsStorageSizeofCommand[ cmd.header.i_type ];
memcpy( p_storage->p_cmd_w, &cmd, i_cmdsize );
p_storage->p_cmd_w += i_cmdsize;
}
static void TsStoragePopCmd( ts_storage_t *p_storage, ts_cmd_t *p_cmd, bool b_flush )
{
assert( !TsStorageIsEmpty( p_storage ) );
p_cmd->header.i_type = p_storage->p_cmd_r[0];
size_t i_cmdsize = TsStorageSizeofCommand[ p_cmd->header.i_type ];
memcpy(p_cmd, p_storage->p_cmd_r, i_cmdsize);
p_storage->p_cmd_r += i_cmdsize;
if( p_cmd->header.i_type == C_SEND )
{
block_t block;
if( !b_flush &&
!fseek( p_storage->p_filer, p_cmd->send.i_offset, SEEK_SET ) &&
fread( &block, sizeof(block), 1, p_storage->p_filer ) == 1 )
{
block_t *p_block = block_Alloc( block.i_buffer );
if( p_block )
{
p_block->i_dts = block.i_dts;
p_block->i_pts = block.i_pts;
p_block->i_flags = block.i_flags;
p_block->i_length = block.i_length;
p_block->i_nb_samples = block.i_nb_samples;
p_block->i_buffer = fread( p_block->p_buffer, 1, block.i_buffer, p_storage->p_filer );
}
p_cmd->send.p_block = p_block;
}
else
{
//perror( "TsStoragePopCmd" );
p_cmd->send.p_block = block_Alloc( 1 );
}
}
}
/*****************************************************************************
*
*****************************************************************************/
static void CmdClean( ts_cmd_t *p_cmd )
{
switch( p_cmd->header.i_type )
{
case C_ADD:
CmdCleanAdd( &p_cmd->add );
break;
case C_SEND:
CmdCleanSend( &p_cmd->send );
break;
case C_CONTROL:
CmdCleanControl( &p_cmd->control );
break;
case C_PRIVCONTROL:
CmdCleanPrivControl( &p_cmd->privcontrol );
break;
case C_DEL:
break;
default:
vlc_assert_unreachable();
break;
}
}
static int CmdInitAdd( ts_cmd_add_t *p_cmd, input_source_t *in, es_out_id_t *p_es,
const es_format_t *p_fmt, bool b_copy )
{
p_cmd->header.i_type = C_ADD;
p_cmd->header.i_date = vlc_tick_now();
p_cmd->p_es = p_es;
if( b_copy )
{
p_cmd->p_fmt = malloc( sizeof(*p_fmt) );
if( !p_cmd->p_fmt )
return VLC_EGENERIC;
es_format_Copy( p_cmd->p_fmt, p_fmt );
p_cmd->in = in ? input_source_Hold( in ) : NULL;
}
else
{
p_cmd->p_fmt = (es_format_t*)p_fmt;
p_cmd->in = in;
}
return VLC_SUCCESS;
}
static void CmdExecuteAdd(struct es_out_timeshift *p_sys, ts_cmd_add_t *p_cmd)
{
es_out_t *out = &p_sys->p_out->out;
p_cmd->p_es->p_es = out->cbs->add(out, p_cmd->in, p_cmd->p_fmt);
TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_cmd->p_es );
}
static void CmdCleanAdd( ts_cmd_add_t *p_cmd )
{
es_format_Clean( p_cmd->p_fmt );
if( p_cmd->in )
input_source_Release( p_cmd->in );
free( p_cmd->p_fmt );
}
static void CmdInitSend( ts_cmd_send_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
{
p_cmd->header.i_type = C_SEND;
p_cmd->header.i_date = vlc_tick_now();
p_cmd->p_es = p_es;
p_cmd->p_block = p_block;
}
static int CmdExecuteSend(struct es_out_timeshift *p_sys, ts_cmd_send_t *p_cmd)
{
es_out_t *out = &p_sys->p_out->out;
block_t *p_block = p_cmd->p_block;
p_cmd->p_block = NULL;
if( p_block )
{
if( p_cmd->p_es->p_es )
return es_out_Send(out, p_cmd->p_es->p_es, p_block);
block_Release( p_block );
}
return VLC_EGENERIC;
}
static void CmdCleanSend( ts_cmd_send_t *p_cmd )
{
if( p_cmd->p_block )
block_Release( p_cmd->p_block );
}
static int CmdInitDel( ts_cmd_del_t *p_cmd, es_out_id_t *p_es )
{
p_cmd->header.i_type = C_DEL;
p_cmd->header.i_date = vlc_tick_now();
p_cmd->p_es = p_es;
return VLC_SUCCESS;
}
static void CmdExecuteDel(struct es_out_timeshift *p_sys, ts_cmd_del_t *p_cmd)
{
if( p_cmd->p_es->p_es )
es_out_Del(&p_sys->p_out->out, p_cmd->p_es->p_es );
TAB_REMOVE( p_sys->i_es, p_sys->pp_es, p_cmd->p_es );
free( p_cmd->p_es );
}
static int CmdInitControl( ts_cmd_control_t *p_cmd, input_source_t *in,
int i_query, va_list args, bool b_copy )
{
p_cmd->header.i_type = C_CONTROL;
p_cmd->header.i_date = vlc_tick_now();
p_cmd->i_query = i_query;
if( b_copy )
p_cmd->in = in ? input_source_Hold( in ) : NULL;
else
p_cmd->in = in;
switch( i_query )
{
/* Pass-through control */
case ES_OUT_SET_GROUP: /* arg1= int */
case ES_OUT_DEL_GROUP: /* arg1=int i_group */
p_cmd->u.i_int = va_arg( args, int );
break;
case ES_OUT_SET_PCR: /* arg1=vlc_tick_t i_pcr(microsecond!) (using default group 0)*/
case ES_OUT_SET_NEXT_DISPLAY_TIME: /* arg1=vlc_tick_t i_pts(microsecond) */
p_cmd->u.i_i64 = va_arg( args, vlc_tick_t );
break;
case ES_OUT_SET_GROUP_PCR: /* arg1= int i_group, arg2=vlc_tick_t i_pcr(microsecond!)*/
p_cmd->u.int_i64.i_int = va_arg( args, int );
p_cmd->u.int_i64.i_i64 = va_arg( args, vlc_tick_t );
break;
case ES_OUT_SET_ES_SCRAMBLED_STATE:
p_cmd->u.es_bool.p_es = va_arg( args, es_out_id_t * );
p_cmd->u.es_bool.b_bool = (bool)va_arg( args, int );
break;
case ES_OUT_DRAIN:
case ES_OUT_RESET_PCR: /* no arg */
break;
case ES_OUT_SET_META: /* arg1=const vlc_meta_t* */
case ES_OUT_SET_GROUP_META: /* arg1=int i_group arg2=const vlc_meta_t* */
{
if( i_query == ES_OUT_SET_GROUP_META )
p_cmd->u.int_meta.i_int = va_arg( args, int );
const vlc_meta_t *p_meta = va_arg( args, const vlc_meta_t * );
if( b_copy )
{
p_cmd->u.int_meta.p_meta = vlc_meta_New();
if( !p_cmd->u.int_meta.p_meta )
return VLC_EGENERIC;
vlc_meta_Merge( p_cmd->u.int_meta.p_meta, p_meta );
}
else
{
/* The cast is only needed to avoid warning */
p_cmd->u.int_meta.p_meta = (vlc_meta_t*)p_meta;
}
break;
}
case ES_OUT_SET_GROUP_EPG: /* arg1=int i_group arg2=const vlc_epg_t* */
{
p_cmd->u.int_epg.i_int = va_arg( args, int );
const vlc_epg_t *p_epg = va_arg( args, const vlc_epg_t * );
if( b_copy )
{
p_cmd->u.int_epg.p_epg = vlc_epg_Duplicate( p_epg );
if( !p_cmd->u.int_epg.p_epg )
return VLC_EGENERIC;
}
else
{
/* The cast is only needed to avoid warning */
p_cmd->u.int_epg.p_epg = (vlc_epg_t*)p_epg;
}
break;
}
case ES_OUT_SET_GROUP_EPG_EVENT: /* arg1=int i_group arg2=const vlc_epg_event_t* */
{
p_cmd->u.int_epg_evt.i_int = va_arg( args, int );
const vlc_epg_event_t *p_evt = va_arg( args, const vlc_epg_event_t * );
if( b_copy )
{
p_cmd->u.int_epg_evt.p_evt = vlc_epg_event_Duplicate( p_evt );
if( !p_cmd->u.int_epg_evt.p_evt )
return VLC_EGENERIC;
}
else
{
/* The cast is only needed to avoid warning */
p_cmd->u.int_epg_evt.p_evt = (vlc_epg_event_t*)p_evt;
}
break;
}
case ES_OUT_SET_EPG_TIME: /* arg1=int64_t (seconds) */
p_cmd->u.i_i64 = va_arg( args, int64_t );
break;
/* Modified control */
case ES_OUT_SET_ES: /* arg1= es_out_id_t* */
case ES_OUT_UNSET_ES: /* arg1= es_out_id_t* */
case ES_OUT_RESTART_ES: /* arg1= es_out_id_t* */
case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t* */
p_cmd->u.p_es = va_arg( args, es_out_id_t * );
break;
case ES_OUT_SET_ES_CAT_POLICY:
p_cmd->u.es_policy.i_cat = va_arg( args, int );
p_cmd->u.es_policy.i_policy = va_arg( args, int );
break;
case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool */
p_cmd->u.es_bool.p_es = va_arg( args, es_out_id_t * );
p_cmd->u.es_bool.b_bool = (bool)va_arg( args, int );
break;
case ES_OUT_SET_ES_FMT: /* arg1= es_out_id_t* arg2=es_format_t* */
{
p_cmd->u.es_fmt.p_es = va_arg( args, es_out_id_t * );
es_format_t *p_fmt = va_arg( args, es_format_t * );
if( b_copy )
{
p_cmd->u.es_fmt.p_fmt = malloc( sizeof(*p_fmt) );
if( !p_cmd->u.es_fmt.p_fmt )
return VLC_EGENERIC;
es_format_Copy( p_cmd->u.es_fmt.p_fmt, p_fmt );
}
else
{
p_cmd->u.es_fmt.p_fmt = p_fmt;
}
break;
}
default:
vlc_assert_unreachable();
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static int
CmdExecuteControl(struct es_out_timeshift *p_sys,
ts_cmd_control_t *p_cmd)
{
const int i_query = p_cmd->i_query;
input_source_t *in = p_cmd->in;
switch( i_query )
{
/* Pass-through control */
case ES_OUT_SET_GROUP: /* arg1= int */
case ES_OUT_DEL_GROUP: /* arg1=int i_group */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.i_int );
case ES_OUT_SET_PCR: /* arg1=vlc_tick_t i_pcr(microsecond!) (using default group 0)*/
case ES_OUT_SET_NEXT_DISPLAY_TIME: /* arg1=vlc_tick_t i_pts(microsecond) */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.i_i64 );
case ES_OUT_SET_GROUP_PCR: /* arg1= int i_group, arg2=vlc_tick_t i_pcr(microsecond!)*/
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.int_i64.i_int,
p_cmd->u.int_i64.i_i64 );
case ES_OUT_DRAIN:
case ES_OUT_RESET_PCR: /* no arg */
return es_out_in_Control( p_sys->p_out, in, i_query );
case ES_OUT_SET_GROUP_META: /* arg1=int i_group arg2=const vlc_meta_t* */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.int_meta.i_int,
p_cmd->u.int_meta.p_meta );
case ES_OUT_SET_GROUP_EPG: /* arg1=int i_group arg2=const vlc_epg_t* */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.int_epg.i_int,
p_cmd->u.int_epg.p_epg );
case ES_OUT_SET_GROUP_EPG_EVENT: /* arg1=int i_group arg2=const vlc_epg_event_t* */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.int_epg_evt.i_int,
p_cmd->u.int_epg_evt.p_evt );
case ES_OUT_SET_EPG_TIME: /* arg1=int64_t */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.i_i64 );
case ES_OUT_SET_ES_SCRAMBLED_STATE: /* arg1=int es_out_id_t* arg2=bool */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.es_bool.p_es->p_es,
p_cmd->u.es_bool.b_bool );
case ES_OUT_SET_META: /* arg1=const vlc_meta_t* */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.int_meta.p_meta );
/* Modified control */
case ES_OUT_SET_ES: /* arg1= es_out_id_t* */
case ES_OUT_UNSET_ES: /* arg1= es_out_id_t* */
case ES_OUT_RESTART_ES: /* arg1= es_out_id_t* */
case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t* */
return es_out_in_Control( p_sys->p_out, in, i_query, !p_cmd->u.p_es ? NULL :
p_cmd->u.p_es->p_es );
case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.es_bool.p_es->p_es,
p_cmd->u.es_bool.b_bool );
case ES_OUT_SET_ES_CAT_POLICY:
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.es_policy.i_cat,
p_cmd->u.es_policy.i_policy );
case ES_OUT_SET_ES_FMT: /* arg1= es_out_id_t* arg2=es_format_t* */
return es_out_in_Control( p_sys->p_out, in, i_query, p_cmd->u.es_fmt.p_es->p_es,
p_cmd->u.es_fmt.p_fmt );
default:
vlc_assert_unreachable();
return VLC_EGENERIC;
}
}
static void CmdCleanControl( ts_cmd_control_t *p_cmd )
{
if( p_cmd->in )
input_source_Release( p_cmd->in );
switch( p_cmd->i_query )
{
case ES_OUT_SET_GROUP_META:
case ES_OUT_SET_META:
if( p_cmd->u.int_meta.p_meta )
vlc_meta_Delete( p_cmd->u.int_meta.p_meta );
break;
case ES_OUT_SET_GROUP_EPG:
if( p_cmd->u.int_epg.p_epg )
vlc_epg_Delete( p_cmd->u.int_epg.p_epg );
break;
case ES_OUT_SET_GROUP_EPG_EVENT:
if( p_cmd->u.int_epg_evt.p_evt )
vlc_epg_event_Delete( p_cmd->u.int_epg_evt.p_evt );
break;
case ES_OUT_SET_ES_FMT:
if( p_cmd->u.es_fmt.p_fmt )
{
es_format_Clean( p_cmd->u.es_fmt.p_fmt );
free( p_cmd->u.es_fmt.p_fmt );
}
break;
}
}
static int CmdInitPrivControl( ts_cmd_privcontrol_t *p_cmd, input_source_t *in, int i_query, va_list args, bool b_copy )
{
p_cmd->header.i_type = C_PRIVCONTROL;
p_cmd->header.i_date = vlc_tick_now();
p_cmd->i_query = i_query;
if( b_copy )
p_cmd->in = in ? input_source_Hold( in ) : NULL;
else
p_cmd->in = in;
switch( i_query )
{
/* Pass-through control */
case ES_OUT_PRIV_SET_MODE: /* arg1= int */
p_cmd->u.i_int = va_arg( args, int );
break;
case ES_OUT_PRIV_SET_JITTER:
{
vlc_tick_t i_pts_delay = va_arg( args, vlc_tick_t );
vlc_tick_t i_pts_jitter = va_arg( args, vlc_tick_t );
int i_cr_average = va_arg( args, int );
p_cmd->u.jitter.i_pts_delay = i_pts_delay;
p_cmd->u.jitter.i_pts_jitter = i_pts_jitter;
p_cmd->u.jitter.i_cr_average = i_cr_average;
break;
}
case ES_OUT_PRIV_SET_TIMES:
{
double f_position = va_arg( args, double );
vlc_tick_t i_time = va_arg( args, vlc_tick_t );
vlc_tick_t i_normal_time = va_arg( args, vlc_tick_t );
vlc_tick_t i_length = va_arg( args, vlc_tick_t );
bool b_live = va_arg( args, int );
p_cmd->u.times.f_position = f_position;
p_cmd->u.times.i_time = i_time;
p_cmd->u.times.i_normal_time = i_normal_time;
p_cmd->u.times.i_length = i_length;
p_cmd->u.times.b_live = b_live;
break;
}
case ES_OUT_PRIV_SET_EOS: /* no arg */
break;
default: vlc_assert_unreachable();
}
return VLC_SUCCESS;
}
static int CmdExecutePrivControl(struct es_out_timeshift *p_sys, ts_cmd_privcontrol_t *p_cmd)
{
const int i_query = p_cmd->i_query;
input_source_t *in = p_cmd->in;
switch( i_query )
{
/* Pass-through control */
case ES_OUT_PRIV_SET_MODE: /* arg1= int */
return es_out_in_PrivControl( p_sys->p_out, in, i_query, p_cmd->u.i_int );
case ES_OUT_PRIV_SET_JITTER:
return es_out_in_PrivControl( p_sys->p_out, in, i_query, p_cmd->u.jitter.i_pts_delay,
p_cmd->u.jitter.i_pts_jitter,
p_cmd->u.jitter.i_cr_average );
case ES_OUT_PRIV_SET_TIMES:
return es_out_in_PrivControl( p_sys->p_out, in, i_query,
p_cmd->u.times.f_position,
p_cmd->u.times.i_time,
p_cmd->u.times.i_normal_time,
p_cmd->u.times.i_length,
p_cmd->u.times.b_live );
case ES_OUT_PRIV_SET_EOS: /* no arg */
return es_out_in_PrivControl( p_sys->p_out, in, i_query );
default: vlc_assert_unreachable();
}
}
static void CmdCleanPrivControl( ts_cmd_privcontrol_t *p_cmd )
{
if( p_cmd->in )
input_source_Release( p_cmd->in );
}
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|