X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Finput%2Fes_out.c;h=635b2e9893e310cd60edef9842e26e9c1ccc8da0;hb=12ade3e3bc975d5426ba4af155b7372c31093b31;hp=2d25cb7c4c363ff3b78b398dd0b02fd311af6cd8;hpb=987d3a8595102368d0098c5d0f817247a9b10ecf;p=vlc diff --git a/src/input/es_out.c b/src/input/es_out.c index 2d25cb7c4c..635b2e9893 100644 --- a/src/input/es_out.c +++ b/src/input/es_out.c @@ -37,8 +37,14 @@ #include #include #include +#include #include "input_internal.h" +#include "clock.h" +#include "decoder.h" +#include "es_out.h" +#include "event.h" +#include "info.h" #include "../stream_output/stream_output.h" @@ -58,15 +64,14 @@ typedef struct int i_es; bool b_selected; + bool b_scrambled; /* Clock for this program */ - input_clock_t clock; + input_clock_t *p_clock; char *psz_name; char *psz_now_playing; char *psz_publisher; - - vlc_epg_t *p_epg; } es_out_pgrm_t; struct es_out_id_t @@ -75,8 +80,8 @@ struct es_out_id_t int i_id; es_out_pgrm_t *p_pgrm; - /* Misc. */ - int64_t i_preroll_end; + /* */ + bool b_scrambled; /* Channel in the track type */ int i_channel; @@ -93,12 +98,18 @@ struct es_out_id_t /* Field for CC track from a master video */ es_out_id_t *p_master; + + /* ID for the meta data */ + int i_meta_id; }; struct es_out_sys_t { input_thread_t *p_input; + /* */ + vlc_mutex_t lock; + /* all programs */ int i_pgrm; es_out_pgrm_t **pgrm; @@ -119,7 +130,8 @@ struct es_out_sys_t int i_video; int i_sub; - /* es to select */ + /* es/group to select */ + int i_group_id; int i_audio_last, i_audio_id; int i_sub_last, i_sub_id; int i_default_sub_id; /* As specified in container; if applicable */ @@ -135,27 +147,52 @@ struct es_out_sys_t int64_t i_audio_delay; int64_t i_spu_delay; - /* Rate used to rescale ES ts */ + /* Clock configuration */ + mtime_t i_pts_delay; + mtime_t i_pts_jitter; + int i_cr_average; int i_rate; + /* */ + bool b_paused; + mtime_t i_pause_date; + + /* Current preroll */ + mtime_t i_preroll_end; + + /* Used for buffering */ + bool b_buffering; + mtime_t i_buffering_extra_initial; + mtime_t i_buffering_extra_stream; + mtime_t i_buffering_extra_system; + /* Record */ sout_instance_t *p_sout_record; }; -static es_out_id_t *EsOutAdd ( es_out_t *, es_format_t * ); +static es_out_id_t *EsOutAdd ( es_out_t *, const es_format_t * ); static int EsOutSend ( es_out_t *, es_out_id_t *, block_t * ); static void EsOutDel ( es_out_t *, es_out_id_t * ); -static void EsOutSelect( es_out_t *out, es_out_id_t *es, bool b_force ); static int EsOutControl( es_out_t *, int i_query, va_list ); +static void EsOutDelete ( es_out_t * ); -static void EsOutAddInfo( es_out_t *, es_out_id_t *es ); +static void EsOutTerminate( es_out_t * ); +static void EsOutSelect( es_out_t *, es_out_id_t *es, bool b_force ); +static void EsOutUpdateInfo( es_out_t *, es_out_id_t *es, const es_format_t *, const vlc_meta_t * ); +static int EsOutSetRecord( es_out_t *, bool b_record ); static bool EsIsSelected( es_out_id_t *es ); static void EsSelect( es_out_t *out, es_out_id_t *es ); static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update ); +static void EsOutDecoderChangeDelay( es_out_t *out, es_out_id_t *p_es ); +static void EsOutDecodersChangePause( es_out_t *out, bool b_paused, mtime_t i_date ); +static void EsOutProgramChangePause( es_out_t *out, bool b_paused, mtime_t i_date ); +static void EsOutProgramsChangeRate( es_out_t *out ); +static void EsOutDecodersStopBuffering( es_out_t *out, bool b_forced ); + static char *LanguageGetName( const char *psz_code ); static char *LanguageGetCode( const char *psz_lang ); -static char **LanguageSplit( const char *psz_langs ); +static char **LanguageSplit( const char *psz_langs, bool b_default_any ); static int LanguageArrayIndex( char **ppsz_langs, char *psz_lang ); static char *EsOutProgramGetMetaName( es_out_pgrm_t *p_pgrm ); @@ -176,20 +213,21 @@ static inline int EsOutGetClosedCaptionsChannel( vlc_fourcc_t fcc ) } return -1; } - +static inline bool EsFmtIsTeletext( const es_format_t *p_fmt ) +{ + return p_fmt->i_cat == SPU_ES && p_fmt->i_codec == VLC_CODEC_TELETEXT; +} /***************************************************************************** * input_EsOutNew: *****************************************************************************/ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) { - vlc_value_t val; - int i; - - es_out_t *out = malloc( sizeof( es_out_t ) ); - if( !out ) return NULL; + es_out_t *out = malloc( sizeof( *out ) ); + if( !out ) + return NULL; - es_out_sys_t *p_sys = malloc( sizeof( es_out_sys_t ) ); + es_out_sys_t *p_sys = malloc( sizeof( *p_sys ) ); if( !p_sys ) { free( out ); @@ -200,13 +238,14 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) out->pf_send = EsOutSend; out->pf_del = EsOutDel; out->pf_control = EsOutControl; + out->pf_destroy = EsOutDelete; out->p_sys = p_sys; - out->b_sout = p_input->p->p_sout != NULL; + vlc_mutex_init_recursive( &p_sys->lock ); p_sys->p_input = p_input; p_sys->b_active = false; - p_sys->i_mode = ES_OUT_MODE_AUTO; + p_sys->i_mode = ES_OUT_MODE_NONE; TAB_INIT( p_sys->i_pgrm, p_sys->pgrm ); @@ -221,35 +260,37 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) p_sys->i_sub = 0; /* */ - var_Get( p_input, "audio-track", &val ); - p_sys->i_audio_last = val.i_int; + p_sys->i_group_id = var_GetInteger( p_input, "program" ); - var_Get( p_input, "sub-track", &val ); - p_sys->i_sub_last = val.i_int; + p_sys->i_audio_last = var_GetInteger( p_input, "audio-track" ); + + p_sys->i_sub_last = var_GetInteger( p_input, "sub-track" ); p_sys->i_default_sub_id = -1; if( !p_input->b_preparsing ) { - var_Get( p_input, "audio-language", &val ); - p_sys->ppsz_audio_language = LanguageSplit(val.psz_string); + char *psz_string; + + psz_string = var_GetString( p_input, "audio-language" ); + p_sys->ppsz_audio_language = LanguageSplit( psz_string, true ); if( p_sys->ppsz_audio_language ) { - for( i = 0; p_sys->ppsz_audio_language[i]; i++ ) + for( int i = 0; p_sys->ppsz_audio_language[i]; i++ ) msg_Dbg( p_input, "selected audio language[%d] %s", i, p_sys->ppsz_audio_language[i] ); } - free( val.psz_string ); + free( psz_string ); - var_Get( p_input, "sub-language", &val ); - p_sys->ppsz_sub_language = LanguageSplit(val.psz_string); + psz_string = var_GetString( p_input, "sub-language" ); + p_sys->ppsz_sub_language = LanguageSplit( psz_string, false ); if( p_sys->ppsz_sub_language ) { - for( i = 0; p_sys->ppsz_sub_language[i]; i++ ) + for( int i = 0; p_sys->ppsz_sub_language[i]; i++ ) msg_Dbg( p_input, "selected subtitle language[%d] %s", i, p_sys->ppsz_sub_language[i] ); } - free( val.psz_string ); + free( psz_string ); } else { @@ -257,11 +298,9 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) p_sys->ppsz_audio_language = NULL; } - var_Get( p_input, "audio-track-id", &val ); - p_sys->i_audio_id = val.i_int; + p_sys->i_audio_id = var_GetInteger( p_input, "audio-track-id" ); - var_Get( p_input, "sub-track-id", &val ); - p_sys->i_sub_id = val.i_int; + p_sys->i_sub_id = var_GetInteger( p_input, "sub-track-id" ); p_sys->p_es_audio = NULL; p_sys->p_es_video = NULL; @@ -270,7 +309,19 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) p_sys->i_audio_delay= 0; p_sys->i_spu_delay = 0; + p_sys->b_paused = false; + p_sys->i_pause_date = -1; + p_sys->i_rate = i_rate; + p_sys->i_pts_delay = 0; + p_sys->i_pts_jitter = 0; + p_sys->i_cr_average = 0; + + p_sys->b_buffering = true; + p_sys->i_buffering_extra_initial = 0; + p_sys->i_buffering_extra_stream = 0; + p_sys->i_buffering_extra_system = 0; + p_sys->i_preroll_end = -1; p_sys->p_sout_record = NULL; @@ -278,17 +329,40 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) } /***************************************************************************** - * input_EsOutDelete: + * *****************************************************************************/ -void input_EsOutDelete( es_out_t *out ) +static void EsOutDelete( es_out_t *out ) +{ + es_out_sys_t *p_sys = out->p_sys; + + assert( !p_sys->i_es && !p_sys->i_pgrm && !p_sys->p_pgrm ); + if( p_sys->ppsz_audio_language ) + { + for( int i = 0; p_sys->ppsz_audio_language[i]; i++ ) + free( p_sys->ppsz_audio_language[i] ); + free( p_sys->ppsz_audio_language ); + } + if( p_sys->ppsz_sub_language ) + { + for( int i = 0; p_sys->ppsz_sub_language[i]; i++ ) + free( p_sys->ppsz_sub_language[i] ); + free( p_sys->ppsz_sub_language ); + } + + vlc_mutex_destroy( &p_sys->lock ); + + free( p_sys ); + free( out ); +} + +static void EsOutTerminate( es_out_t *out ) { es_out_sys_t *p_sys = out->p_sys; - int i; if( p_sys->p_sout_record ) - input_EsOutSetRecord( out, false ); + EsOutSetRecord( out, false ); - for( i = 0; i < p_sys->i_es; i++ ) + for( int i = 0; i < p_sys->i_es; i++ ) { if( p_sys->es[i]->p_dec ) input_DecoderDelete( p_sys->es[i]->p_dec ); @@ -299,44 +373,53 @@ void input_EsOutDelete( es_out_t *out ) free( p_sys->es[i] ); } - if( p_sys->ppsz_audio_language ) - { - for( i = 0; p_sys->ppsz_audio_language[i]; i++ ) - free( p_sys->ppsz_audio_language[i] ); - free( p_sys->ppsz_audio_language ); - } - if( p_sys->ppsz_sub_language ) - { - for( i = 0; p_sys->ppsz_sub_language[i]; i++ ) - free( p_sys->ppsz_sub_language[i] ); - free( p_sys->ppsz_sub_language ); - } - free( p_sys->es ); + TAB_CLEAN( p_sys->i_es, p_sys->es ); /* FIXME duplicate work EsOutProgramDel (but we cannot use it) add a EsOutProgramClean ? */ - for( i = 0; i < p_sys->i_pgrm; i++ ) + for( int i = 0; i < p_sys->i_pgrm; i++ ) { es_out_pgrm_t *p_pgrm = p_sys->pgrm[i]; + input_clock_Delete( p_pgrm->p_clock ); free( p_pgrm->psz_now_playing ); free( p_pgrm->psz_publisher ); free( p_pgrm->psz_name ); - if( p_pgrm->p_epg ) - vlc_epg_Delete( p_pgrm->p_epg ); free( p_pgrm ); } TAB_CLEAN( p_sys->i_pgrm, p_sys->pgrm ); - free( p_sys ); - free( out ); + p_sys->p_pgrm = NULL; + + input_item_SetEpgOffline( p_sys->p_input->p->p_item ); + input_SendEventMetaEpg( p_sys->p_input ); +} + +static mtime_t EsOutGetWakeup( es_out_t *out ) +{ + es_out_sys_t *p_sys = out->p_sys; + input_thread_t *p_input = p_sys->p_input; + + if( !p_sys->p_pgrm ) + return 0; + + /* We do not have a wake up date if the input cannot have its speed + * controlled or sout is imposing its own or while buffering + * + * FIXME for !p_input->p->b_can_pace_control a wkeup time is still needed to avoid too strong buffering */ + if( !p_input->p->b_can_pace_control || + p_input->p->b_out_pace_control || + p_sys->b_buffering ) + return 0; + + return input_clock_GetWakeup( p_sys->p_pgrm->p_clock ); } -es_out_id_t *input_EsOutGetFromID( es_out_t *out, int i_id ) +static es_out_id_t *EsOutGetFromID( es_out_t *out, int i_id ) { int i; if( i_id < 0 ) { - /* Special HACK, -i_id is tha cat of the stream */ + /* Special HACK, -i_id is the cat of the stream */ return (es_out_id_t*)((uint8_t*)NULL-i_id); } @@ -348,47 +431,44 @@ es_out_id_t *input_EsOutGetFromID( es_out_t *out, int i_id ) return NULL; } -mtime_t input_EsOutGetWakeup( es_out_t *out ) -{ - es_out_sys_t *p_sys = out->p_sys; - - if( !p_sys->p_pgrm ) - return 0; - return input_ClockGetWakeup( p_sys->p_input, &p_sys->p_pgrm->clock ); -} - -static void EsOutDiscontinuity( es_out_t *out, bool b_flush, bool b_audio ) +static bool EsOutDecodersIsEmpty( es_out_t *out ) { es_out_sys_t *p_sys = out->p_sys; int i; + if( p_sys->b_buffering && p_sys->p_pgrm ) + { + EsOutDecodersStopBuffering( out, true ); + if( p_sys->b_buffering ) + return true; + } + for( i = 0; i < p_sys->i_es; i++ ) { es_out_id_t *es = p_sys->es[i]; - /* Send a dummy block to let decoder know that - * there is a discontinuity */ - if( es->p_dec && ( !b_audio || es->fmt.i_cat == AUDIO_ES ) ) - { - input_DecoderDiscontinuity( es->p_dec, b_flush ); - if( es->p_dec_record ) - input_DecoderDiscontinuity( es->p_dec_record, b_flush ); - } + if( es->p_dec && !input_DecoderIsEmpty( es->p_dec ) ) + return false; + if( es->p_dec_record && !input_DecoderIsEmpty( es->p_dec_record ) ) + return false; } + return true; } -void input_EsOutChangeRate( es_out_t *out, int i_rate ) + +static void EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay ) { - es_out_sys_t *p_sys = out->p_sys; - int i; + es_out_sys_t *p_sys = out->p_sys; - p_sys->i_rate = i_rate; - EsOutDiscontinuity( out, false, false ); + if( i_cat == AUDIO_ES ) + p_sys->i_audio_delay = i_delay; + else if( i_cat == SPU_ES ) + p_sys->i_spu_delay = i_delay; - for( i = 0; i < p_sys->i_pgrm; i++ ) - input_ClockSetRate( &p_sys->pgrm[i]->clock, i_rate ); + for( int i = 0; i < p_sys->i_es; i++ ) + EsOutDecoderChangeDelay( out, p_sys->es[i] ); } -int input_EsOutSetRecord( es_out_t *out, bool b_record ) +static int EsOutSetRecord( es_out_t *out, bool b_record ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; @@ -397,11 +477,15 @@ int input_EsOutSetRecord( es_out_t *out, bool b_record ) if( b_record ) { - char *psz_path = var_CreateGetString( p_input, "input-record-path" ); - if( !psz_path || *psz_path == '\0' ) + char *psz_path = var_CreateGetNonEmptyString( p_input, "input-record-path" ); + if( !psz_path ) { - free( psz_path ); - psz_path = strdup( config_GetHomeDir() ); + if( var_CountChoices( p_input, "video-es" ) ) + psz_path = config_GetUserDir( VLC_VIDEOS_DIR ); + else if( var_CountChoices( p_input, "audio-es" ) ) + psz_path = config_GetUserDir( VLC_MUSIC_DIR ); + else + psz_path = config_GetUserDir( VLC_DOWNLOAD_DIR ); } char *psz_sout = NULL; // TODO conf @@ -436,7 +520,9 @@ int input_EsOutSetRecord( es_out_t *out, bool b_record ) if( !p_es->p_dec || p_es->p_master ) continue; - p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_sys->p_sout_record ); + p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_es->p_pgrm->p_clock, p_sys->p_sout_record ); + if( p_es->p_dec_record && p_sys->b_buffering ) + input_DecoderStartBuffering( p_es->p_dec_record ); } } else @@ -459,92 +545,386 @@ int input_EsOutSetRecord( es_out_t *out, bool b_record ) return VLC_SUCCESS; } -void input_EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay ) +static void EsOutChangePause( es_out_t *out, bool b_paused, mtime_t i_date ) { es_out_sys_t *p_sys = out->p_sys; - if( i_cat == AUDIO_ES ) - p_sys->i_audio_delay = i_delay; - else if( i_cat == SPU_ES ) - p_sys->i_spu_delay = i_delay; + /* XXX the order is important */ + if( b_paused ) + { + EsOutDecodersChangePause( out, true, i_date ); + EsOutProgramChangePause( out, true, i_date ); + } + else + { + if( p_sys->i_buffering_extra_initial > 0 ) + { + mtime_t i_stream_start; + mtime_t i_system_start; + mtime_t i_stream_duration; + mtime_t i_system_duration; + int i_ret; + i_ret = input_clock_GetState( p_sys->p_pgrm->p_clock, + &i_stream_start, &i_system_start, + &i_stream_duration, &i_system_duration ); + if( !i_ret ) + { + /* FIXME pcr != exactly what wanted */ + const mtime_t i_used = /*(i_stream_duration - p_sys->p_input->p->i_pts_delay)*/ p_sys->i_buffering_extra_system - p_sys->i_buffering_extra_initial; + i_date -= i_used; + } + p_sys->i_buffering_extra_initial = 0; + p_sys->i_buffering_extra_stream = 0; + p_sys->i_buffering_extra_system = 0; + } + EsOutProgramChangePause( out, false, i_date ); + EsOutDecodersChangePause( out, false, i_date ); + + EsOutProgramsChangeRate( out ); + } + p_sys->b_paused = b_paused; + p_sys->i_pause_date = i_date; +} + +static void EsOutChangeRate( es_out_t *out, int i_rate ) +{ + es_out_sys_t *p_sys = out->p_sys; + + p_sys->i_rate = i_rate; + EsOutProgramsChangeRate( out ); } -void input_EsOutChangeState( es_out_t *out ) + +static void EsOutChangePosition( es_out_t *out ) +{ + es_out_sys_t *p_sys = out->p_sys; + + input_SendEventCache( p_sys->p_input, 0.0 ); + + for( int i = 0; i < p_sys->i_es; i++ ) + { + es_out_id_t *p_es = p_sys->es[i]; + + if( !p_es->p_dec ) + continue; + + input_DecoderStartBuffering( p_es->p_dec ); + + if( p_es->p_dec_record ) + input_DecoderStartBuffering( p_es->p_dec_record ); + } + + for( int i = 0; i < p_sys->i_pgrm; i++ ) + input_clock_Reset( p_sys->pgrm[i]->p_clock ); + + p_sys->b_buffering = true; + p_sys->i_buffering_extra_initial = 0; + p_sys->i_buffering_extra_stream = 0; + p_sys->i_buffering_extra_system = 0; + p_sys->i_preroll_end = -1; +} + + + +static void EsOutDecodersStopBuffering( es_out_t *out, bool b_forced ) { es_out_sys_t *p_sys = out->p_sys; - input_thread_t *p_input = p_sys->p_input; + int i_ret; + + mtime_t i_stream_start; + mtime_t i_system_start; + mtime_t i_stream_duration; + mtime_t i_system_duration; + i_ret = input_clock_GetState( p_sys->p_pgrm->p_clock, + &i_stream_start, &i_system_start, + &i_stream_duration, &i_system_duration ); + assert( !i_ret || b_forced ); + if( i_ret ) + return; + + mtime_t i_preroll_duration = 0; + if( p_sys->i_preroll_end >= 0 ) + i_preroll_duration = __MAX( p_sys->i_preroll_end - i_stream_start, 0 ); + + const mtime_t i_buffering_duration = p_sys->i_pts_delay + + i_preroll_duration + + p_sys->i_buffering_extra_stream - p_sys->i_buffering_extra_initial; - if( p_input->i_state == PAUSE_S ) + if( i_stream_duration <= i_buffering_duration && !b_forced ) { - /* Send discontinuity to decoders (it will allow them to flush - * * if implemented */ - EsOutDiscontinuity( out, false, false ); + const double f_level = __MAX( (double)i_stream_duration / i_buffering_duration, 0 ); + input_SendEventCache( p_sys->p_input, f_level ); + + msg_Dbg( p_sys->p_input, "Buffering %d%%", (int)(100 * f_level) ); + return; } - else + input_SendEventCache( p_sys->p_input, 1.0 ); + + msg_Dbg( p_sys->p_input, "Stream buffering done (%d ms in %d ms)", + (int)(i_stream_duration/1000), (int)(i_system_duration/1000) ); + p_sys->b_buffering = false; + p_sys->i_preroll_end = -1; + + if( p_sys->i_buffering_extra_initial > 0 ) + { + /* FIXME wrong ? */ + return; + } + + const mtime_t i_decoder_buffering_start = mdate(); + for( int i = 0; i < p_sys->i_es; i++ ) { - /* Out of pause, reset pcr */ - es_out_Control( out, ES_OUT_RESET_PCR ); + es_out_id_t *p_es = p_sys->es[i]; + + if( !p_es->p_dec || p_es->fmt.i_cat == SPU_ES ) + continue; + input_DecoderWaitBuffering( p_es->p_dec ); + if( p_es->p_dec_record ) + input_DecoderWaitBuffering( p_es->p_dec_record ); } + + msg_Dbg( p_sys->p_input, "Decoder buffering done in %d ms", + (int)(mdate() - i_decoder_buffering_start)/1000 ); + + /* Here is a good place to destroy unused vout with every demuxer */ + input_resource_TerminateVout( p_sys->p_input->p->p_resource ); + + /* */ + const mtime_t i_wakeup_delay = 10*1000; /* FIXME CLEANUP thread wake up time*/ + const mtime_t i_current_date = p_sys->b_paused ? p_sys->i_pause_date : mdate(); + + input_clock_ChangeSystemOrigin( p_sys->p_pgrm->p_clock, true, + i_current_date + i_wakeup_delay - i_buffering_duration ); + + for( int i = 0; i < p_sys->i_es; i++ ) + { + es_out_id_t *p_es = p_sys->es[i]; + + if( !p_es->p_dec ) + continue; + + input_DecoderStopBuffering( p_es->p_dec ); + if( p_es->p_dec_record ) + input_DecoderStopBuffering( p_es->p_dec_record ); + } +} +static void EsOutDecodersChangePause( es_out_t *out, bool b_paused, mtime_t i_date ) +{ + es_out_sys_t *p_sys = out->p_sys; + + /* Pause decoders first */ + for( int i = 0; i < p_sys->i_es; i++ ) + { + es_out_id_t *es = p_sys->es[i]; + + if( es->p_dec ) + { + input_DecoderChangePause( es->p_dec, b_paused, i_date ); + if( es->p_dec_record ) + input_DecoderChangePause( es->p_dec_record, b_paused, i_date ); + } + } +} + +static bool EsOutIsExtraBufferingAllowed( es_out_t *out ) +{ + es_out_sys_t *p_sys = out->p_sys; + + size_t i_size = 0; + for( int i = 0; i < p_sys->i_es; i++ ) + { + es_out_id_t *p_es = p_sys->es[i]; + + if( p_es->p_dec ) + i_size += input_DecoderGetFifoSize( p_es->p_dec ); + if( p_es->p_dec_record ) + i_size += input_DecoderGetFifoSize( p_es->p_dec_record ); + } + //msg_Info( out, "----- EsOutIsExtraBufferingAllowed =% 5d KiB -- ", i_size / 1024 ); + + /* TODO maybe we want to be able to tune it ? */ +#if defined(OPTIMIZE_MEMORY) + const size_t i_level_high = 512*1024; /* 0.5 MiB */ +#else + const size_t i_level_high = 10*1024*1024; /* 10 MiB */ +#endif + return i_size < i_level_high; } -void input_EsOutChangePosition( es_out_t *out ) + +static void EsOutProgramChangePause( es_out_t *out, bool b_paused, mtime_t i_date ) { - //es_out_sys_t *p_sys = out->p_sys; + es_out_sys_t *p_sys = out->p_sys; - es_out_Control( out, ES_OUT_RESET_PCR ); - EsOutDiscontinuity( out, true, false ); + for( int i = 0; i < p_sys->i_pgrm; i++ ) + input_clock_ChangePause( p_sys->pgrm[i]->p_clock, b_paused, i_date ); } -bool input_EsOutDecodersEmpty( es_out_t *out ) +static void EsOutDecoderChangeDelay( es_out_t *out, es_out_id_t *p_es ) +{ + es_out_sys_t *p_sys = out->p_sys; + + mtime_t i_delay = 0; + if( p_es->fmt.i_cat == AUDIO_ES ) + i_delay = p_sys->i_audio_delay; + else if( p_es->fmt.i_cat == SPU_ES ) + i_delay = p_sys->i_spu_delay; + + if( i_delay != 0 ) + { + if( p_es->p_dec ) + input_DecoderChangeDelay( p_es->p_dec, i_delay ); + if( p_es->p_dec_record ) + input_DecoderChangeDelay( p_es->p_dec_record, i_delay ); + } +} +static void EsOutProgramsChangeRate( es_out_t *out ) { es_out_sys_t *p_sys = out->p_sys; - int i; - for( i = 0; i < p_sys->i_es; i++ ) + for( int i = 0; i < p_sys->i_pgrm; i++ ) + input_clock_ChangeRate( p_sys->pgrm[i]->p_clock, p_sys->i_rate ); +} + +static void EsOutFrameNext( es_out_t *out ) +{ + es_out_sys_t *p_sys = out->p_sys; + es_out_id_t *p_es_video = NULL; + + if( p_sys->b_buffering ) { - es_out_id_t *es = p_sys->es[i]; + msg_Warn( p_sys->p_input, "buffering, ignoring 'frame next'" ); + return; + } - if( es->p_dec && !input_DecoderEmpty( es->p_dec ) ) - return false; - if( es->p_dec_record && !input_DecoderEmpty( es->p_dec_record ) ) - return false; + assert( p_sys->b_paused ); + + for( int i = 0; i < p_sys->i_es; i++ ) + { + es_out_id_t *p_es = p_sys->es[i]; + + if( p_es->fmt.i_cat == VIDEO_ES && p_es->p_dec ) + { + p_es_video = p_es; + break; + } } - return true; + + if( !p_es_video ) + { + msg_Warn( p_sys->p_input, "No video track selected, ignoring 'frame next'" ); + return; + } + + mtime_t i_duration; + input_DecoderFrameNext( p_es_video->p_dec, &i_duration ); + + msg_Dbg( out->p_sys->p_input, "EsOutFrameNext consummed %d ms", (int)(i_duration/1000) ); + + if( i_duration <= 0 ) + i_duration = 40*1000; + + /* FIXME it is not a clean way ? */ + if( p_sys->i_buffering_extra_initial <= 0 ) + { + mtime_t i_stream_start; + mtime_t i_system_start; + mtime_t i_stream_duration; + mtime_t i_system_duration; + int i_ret; + + i_ret = input_clock_GetState( p_sys->p_pgrm->p_clock, + &i_stream_start, &i_system_start, + &i_stream_duration, &i_system_duration ); + if( i_ret ) + return; + + p_sys->i_buffering_extra_initial = 1 + i_stream_duration - p_sys->i_pts_delay; /* FIXME < 0 ? */ + p_sys->i_buffering_extra_system = + p_sys->i_buffering_extra_stream = p_sys->i_buffering_extra_initial; + } + + const int i_rate = input_clock_GetRate( p_sys->p_pgrm->p_clock ); + + p_sys->b_buffering = true; + p_sys->i_buffering_extra_system += i_duration; + p_sys->i_buffering_extra_stream = p_sys->i_buffering_extra_initial + + ( p_sys->i_buffering_extra_system - p_sys->i_buffering_extra_initial ) * + INPUT_RATE_DEFAULT / i_rate; + + p_sys->i_preroll_end = -1; } +static mtime_t EsOutGetBuffering( es_out_t *out ) +{ + es_out_sys_t *p_sys = out->p_sys; -/***************************************************************************** - * - *****************************************************************************/ -static void EsOutESVarUpdateGeneric( es_out_t *out, int i_id, es_format_t *fmt, const char *psz_language, + if( !p_sys->p_pgrm ) + return 0; + + int i_ret; + mtime_t i_stream_start; + mtime_t i_system_start; + mtime_t i_stream_duration; + mtime_t i_system_duration; + i_ret = input_clock_GetState( p_sys->p_pgrm->p_clock, + &i_stream_start, &i_system_start, + &i_stream_duration, &i_system_duration ); + + if( i_ret ) + return 0; + + mtime_t i_delay; + + if( p_sys->b_buffering && p_sys->i_buffering_extra_initial <= 0 ) + { + i_delay = i_stream_duration; + } + else + { + mtime_t i_system_duration; + if( p_sys->b_paused ) + { + i_system_duration = p_sys->i_pause_date - i_system_start; + if( p_sys->i_buffering_extra_initial > 0 ) + i_system_duration += p_sys->i_buffering_extra_system - p_sys->i_buffering_extra_initial; + } + else + { + i_system_duration = mdate() - i_system_start; + } + + const mtime_t i_consumed = i_system_duration * INPUT_RATE_DEFAULT / p_sys->i_rate - i_stream_duration; + i_delay = p_sys->i_pts_delay - i_consumed; + } + if( i_delay < 0 ) + return 0; + return i_delay; +} + +static void EsOutESVarUpdateGeneric( es_out_t *out, int i_id, + const es_format_t *fmt, const char *psz_language, bool b_delete ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - const bool b_teletext = fmt->i_cat == SPU_ES && fmt->i_codec == VLC_FOURCC( 't', 'e', 'l', 'x' ); vlc_value_t val, text; - const char *psz_var; - - if( fmt->i_cat == AUDIO_ES ) - psz_var = "audio-es"; - else if( fmt->i_cat == VIDEO_ES ) - psz_var = "video-es"; - else if( fmt->i_cat == SPU_ES ) - psz_var = "spu-es"; - else - return; - if( b_delete ) { - if( b_teletext ) - var_SetInteger( p_sys->p_input, "teletext-es", -1 ); - - val.i_int = i_id; - var_Change( p_input, psz_var, VLC_VAR_DELCHOICE, &val, NULL ); + if( EsFmtIsTeletext( fmt ) ) + input_SendEventTeletextDel( p_sys->p_input, i_id ); - var_SetBool( p_sys->p_input, "intf-change", true ); + input_SendEventEsDel( p_input, fmt->i_cat, i_id ); return; } /* Get the number of ES already added */ + const char *psz_var; + if( fmt->i_cat == AUDIO_ES ) + psz_var = "audio-es"; + else if( fmt->i_cat == VIDEO_ES ) + psz_var = "video-es"; + else + psz_var = "spu-es"; + var_Change( p_input, psz_var, VLC_VAR_CHOICESCOUNT, &val, NULL ); if( val.i_int == 0 ) { @@ -561,10 +941,9 @@ static void EsOutESVarUpdateGeneric( es_out_t *out, int i_id, es_format_t *fmt, { if( psz_language && *psz_language ) { - text.psz_string = malloc( strlen( fmt->psz_description) + - strlen( psz_language ) + 10 ); - sprintf( text.psz_string, "%s - [%s]", fmt->psz_description, - psz_language ); + if( asprintf( &text.psz_string, "%s - [%s]", fmt->psz_description, + psz_language ) == -1 ) + text.psz_string = NULL; } else text.psz_string = strdup( fmt->psz_description ); } @@ -572,28 +951,28 @@ static void EsOutESVarUpdateGeneric( es_out_t *out, int i_id, es_format_t *fmt, { if( psz_language && *psz_language ) { - if( asprintf( &text.psz_string, "%s %i - [%s]", _( "Track" ), val.i_int, psz_language ) == -1 ) + if( asprintf( &text.psz_string, "%s %"PRId64" - [%s]", _( "Track" ), val.i_int, psz_language ) == -1 ) text.psz_string = NULL; } else { - if( asprintf( &text.psz_string, "%s %i", _( "Track" ), val.i_int ) == -1 ) + if( asprintf( &text.psz_string, "%s %"PRId64, _( "Track" ), val.i_int ) == -1 ) text.psz_string = NULL; } } - val.i_int = i_id; - var_Change( p_input, psz_var, VLC_VAR_ADDCHOICE, &val, &text ); - - free( text.psz_string ); - - if( b_teletext ) + input_SendEventEsAdd( p_input, fmt->i_cat, i_id, text.psz_string ); + if( EsFmtIsTeletext( fmt ) ) { - if( var_GetInteger( p_sys->p_input, "teletext-es" ) < 0 ) - var_SetInteger( p_sys->p_input, "teletext-es", i_id ); + char psz_page[3+1]; + snprintf( psz_page, sizeof(psz_page), "%d%2.2x", + fmt->subs.teletext.i_magazine, + fmt->subs.teletext.i_page ); + input_SendEventTeletextAdd( p_sys->p_input, + i_id, fmt->subs.teletext.i_magazine >= 0 ? psz_page : NULL ); } - var_SetBool( p_sys->p_input, "intf-change", true ); + free( text.psz_string ); } static void EsOutESVarUpdate( es_out_t *out, es_out_id_t *es, @@ -602,6 +981,11 @@ static void EsOutESVarUpdate( es_out_t *out, es_out_id_t *es, EsOutESVarUpdateGeneric( out, es->i_id, &es->fmt, es->psz_language, b_delete ); } +static bool EsOutIsProgramVisible( es_out_t *out, int i_group ) +{ + return out->p_sys->i_group_id == 0 || out->p_sys->i_group_id == i_group; +} + /* EsOutProgramSelect: * Select a program and update the object variable */ @@ -609,7 +993,6 @@ static void EsOutProgramSelect( es_out_t *out, es_out_pgrm_t *p_pgrm ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - vlc_value_t val; int i; if( p_sys->p_pgrm == p_pgrm ) @@ -638,22 +1021,21 @@ static void EsOutProgramSelect( es_out_t *out, es_out_pgrm_t *p_pgrm ) p_pgrm->b_selected = true; /* Switch master stream */ - if( p_sys->p_pgrm && p_sys->p_pgrm->clock.b_master ) - { - p_sys->p_pgrm->clock.b_master = false; - } - p_pgrm->clock.b_master = true; p_sys->p_pgrm = p_pgrm; /* Update "program" */ - val.i_int = p_pgrm->i_id; - var_Change( p_input, "program", VLC_VAR_SETVALUE, &val, NULL ); + input_SendEventProgramSelect( p_input, p_pgrm->i_id ); /* Update "es-*" */ - var_Change( p_input, "audio-es", VLC_VAR_CLEARCHOICES, NULL, NULL ); - var_Change( p_input, "video-es", VLC_VAR_CLEARCHOICES, NULL, NULL ); - var_Change( p_input, "spu-es", VLC_VAR_CLEARCHOICES, NULL, NULL ); + input_SendEventEsDel( p_input, AUDIO_ES, -1 ); + input_SendEventEsDel( p_input, VIDEO_ES, -1 ); + input_SendEventEsDel( p_input, SPU_ES, -1 ); + input_SendEventTeletextDel( p_input, -1 ); + input_SendEventProgramScrambled( p_input, p_pgrm->i_id, p_pgrm->b_scrambled ); + + /* TODO event */ var_SetInteger( p_input, "teletext-es", -1 ); + for( i = 0; i < p_sys->i_es; i++ ) { if( p_sys->es[i]->p_pgrm == p_sys->p_pgrm ) @@ -662,12 +1044,10 @@ static void EsOutProgramSelect( es_out_t *out, es_out_pgrm_t *p_pgrm ) } /* Update now playing */ - input_item_SetNowPlaying( p_input->p->input.p_item, - p_pgrm->psz_now_playing ); - input_item_SetPublisher( p_input->p->input.p_item, - p_pgrm->psz_publisher ); + input_item_SetNowPlaying( p_input->p->p_item, p_pgrm->psz_now_playing ); + input_item_SetPublisher( p_input->p->p_item, p_pgrm->psz_publisher ); - var_SetBool( p_sys->p_input, "intf-change", true ); + input_SendEventMeta( p_input ); } /* EsOutAddProgram: @@ -677,36 +1057,39 @@ static es_out_pgrm_t *EsOutProgramAdd( es_out_t *out, int i_group ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - vlc_value_t val; es_out_pgrm_t *p_pgrm = malloc( sizeof( es_out_pgrm_t ) ); - if( !p_pgrm ) return NULL; + if( !p_pgrm ) + return NULL; /* Init */ p_pgrm->i_id = i_group; p_pgrm->i_es = 0; p_pgrm->b_selected = false; + p_pgrm->b_scrambled = false; p_pgrm->psz_name = NULL; p_pgrm->psz_now_playing = NULL; p_pgrm->psz_publisher = NULL; - p_pgrm->p_epg = NULL; - input_ClockInit( &p_pgrm->clock, false, p_input->p->input.i_cr_average, p_sys->i_rate ); + p_pgrm->p_clock = input_clock_New( p_sys->i_rate ); + if( !p_pgrm->p_clock ) + { + free( p_pgrm ); + return NULL; + } + if( p_sys->b_paused ) + input_clock_ChangePause( p_pgrm->p_clock, p_sys->b_paused, p_sys->i_pause_date ); + input_clock_SetJitter( p_pgrm->p_clock, p_sys->i_pts_delay, p_sys->i_cr_average ); /* Append it */ TAB_APPEND( p_sys->i_pgrm, p_sys->pgrm, p_pgrm ); /* Update "program" variable */ - val.i_int = i_group; - var_Change( p_input, "program", VLC_VAR_ADDCHOICE, &val, NULL ); + if( EsOutIsProgramVisible( out, i_group ) ) + input_SendEventProgramAdd( p_input, i_group, NULL ); - if( i_group == var_GetInteger( p_input, "program" ) ) - { + if( i_group == p_sys->i_group_id || ( !p_sys->p_pgrm && p_sys->i_group_id == 0 ) ) EsOutProgramSelect( out, p_pgrm ); - } - else - { - var_SetBool( p_sys->p_input, "intf-change", true ); - } + return p_pgrm; } @@ -718,7 +1101,6 @@ static int EsOutProgramDel( es_out_t *out, int i_group ) es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; es_out_pgrm_t *p_pgrm = NULL; - vlc_value_t val; int i; for( i = 0; i < p_sys->i_pgrm; i++ ) @@ -743,24 +1125,36 @@ static int EsOutProgramDel( es_out_t *out, int i_group ) TAB_REMOVE( p_sys->i_pgrm, p_sys->pgrm, p_pgrm ); /* If program is selected we need to unselect it */ - if( p_sys->p_pgrm == p_pgrm ) p_sys->p_pgrm = NULL; + if( p_sys->p_pgrm == p_pgrm ) + p_sys->p_pgrm = NULL; + + input_clock_Delete( p_pgrm->p_clock ); free( p_pgrm->psz_name ); free( p_pgrm->psz_now_playing ); free( p_pgrm->psz_publisher ); - if( p_pgrm->p_epg ) - vlc_epg_Delete( p_pgrm->p_epg ); free( p_pgrm ); /* Update "program" variable */ - val.i_int = i_group; - var_Change( p_input, "program", VLC_VAR_DELCHOICE, &val, NULL ); - - var_SetBool( p_sys->p_input, "intf-change", true ); + input_SendEventProgramDel( p_input, i_group ); return VLC_SUCCESS; } +/* EsOutProgramFind + */ +static es_out_pgrm_t *EsOutProgramFind( es_out_t *p_out, int i_group ) +{ + es_out_sys_t *p_sys = p_out->p_sys; + + for( int i = 0; i < p_sys->i_pgrm; i++ ) + { + if( p_sys->pgrm[i]->i_id == i_group ) + return p_sys->pgrm[i]; + } + return EsOutProgramAdd( p_out, i_group ); +} + /* EsOutProgramMeta: */ static char *EsOutProgramGetMetaName( es_out_pgrm_t *p_pgrm ) @@ -779,12 +1173,11 @@ static char *EsOutProgramGetMetaName( es_out_pgrm_t *p_pgrm ) return psz; } -static void EsOutProgramMeta( es_out_t *out, int i_group, vlc_meta_t *p_meta ) +static void EsOutProgramMeta( es_out_t *out, int i_group, const vlc_meta_t *p_meta ) { es_out_sys_t *p_sys = out->p_sys; - es_out_pgrm_t *p_pgrm = NULL; + es_out_pgrm_t *p_pgrm; input_thread_t *p_input = p_sys->p_input; - char *psz_cat; const char *psz_title = NULL; const char *psz_provider = NULL; int i; @@ -795,21 +1188,16 @@ static void EsOutProgramMeta( es_out_t *out, int i_group, vlc_meta_t *p_meta ) if( !vlc_meta_Get( p_meta, vlc_meta_Title) && !vlc_meta_Get( p_meta, vlc_meta_NowPlaying) && !vlc_meta_Get( p_meta, vlc_meta_Publisher) && - vlc_dictionary_keys_count( &p_meta->extra_tags ) <= 0 ) + vlc_meta_GetExtraCount( p_meta ) <= 0 ) { return; } /* Find program */ - for( i = 0; i < p_sys->i_pgrm; i++ ) - { - if( p_sys->pgrm[i]->i_id == i_group ) - { - p_pgrm = p_sys->pgrm[i]; - break; - } - } - if( p_pgrm == NULL ) - p_pgrm = EsOutProgramAdd( out, i_group ); /* Create it */ + if( !EsOutIsProgramVisible( out, i_group ) ) + return; + p_pgrm = EsOutProgramFind( out, i_group ); + if( !p_pgrm ) + return; /* */ psz_title = vlc_meta_Get( p_meta, vlc_meta_Title); @@ -818,232 +1206,278 @@ static void EsOutProgramMeta( es_out_t *out, int i_group, vlc_meta_t *p_meta ) /* Update the description text of the program */ if( psz_title && *psz_title ) { - vlc_value_t val; - vlc_value_t text; - if( !p_pgrm->psz_name || strcmp( p_pgrm->psz_name, psz_title ) ) { char *psz_cat = EsOutProgramGetMetaName( p_pgrm ); /* Remove old entries */ input_Control( p_input, INPUT_DEL_INFO, psz_cat, NULL ); - /* TODO update epg name */ + /* TODO update epg name ? + * TODO update scrambled info name ? */ free( psz_cat ); } free( p_pgrm->psz_name ); p_pgrm->psz_name = strdup( psz_title ); - /* ugly but it works */ - val.i_int = i_group; - var_Change( p_input, "program", VLC_VAR_DELCHOICE, &val, NULL ); - + char *psz_text; if( psz_provider && *psz_provider ) { - if( asprintf( &text.psz_string, "%s [%s]", psz_title, psz_provider ) != -1 ) - { - var_Change( p_input, "program", VLC_VAR_ADDCHOICE, &val, &text ); - free( text.psz_string ); - } + if( asprintf( &psz_text, "%s [%s]", psz_title, psz_provider ) < 0 ) + psz_text = NULL; } else { - text.psz_string = (char *)psz_title; - var_Change( p_input, "program", VLC_VAR_ADDCHOICE, &val, &text ); + psz_text = strdup( psz_title ); + } + + /* ugly but it works */ + if( psz_text ) + { + input_SendEventProgramDel( p_input, i_group ); + input_SendEventProgramAdd( p_input, i_group, psz_text ); + if( p_sys->p_pgrm == p_pgrm ) + input_SendEventProgramSelect( p_input, i_group ); + free( psz_text ); } } - psz_cat = EsOutProgramGetMetaName( p_pgrm ); - if( psz_provider ) + /* */ + char **ppsz_all_keys = vlc_meta_CopyExtraNames(p_meta ); + + info_category_t *p_cat = NULL; + if( psz_provider || ( ppsz_all_keys[0] && *ppsz_all_keys[0] ) ) { - if( p_sys->p_pgrm == p_pgrm ) - input_item_SetPublisher( p_input->p->input.p_item, psz_provider ); - input_Control( p_input, INPUT_ADD_INFO, psz_cat, input_MetaTypeToLocalizedString(vlc_meta_Publisher), psz_provider ); + char *psz_cat = EsOutProgramGetMetaName( p_pgrm ); + if( psz_cat ) + p_cat = info_category_New( psz_cat ); + free( psz_cat ); } - char ** ppsz_all_keys = vlc_dictionary_all_keys( &p_meta->extra_tags ); + for( i = 0; ppsz_all_keys[i]; i++ ) { - input_Control( p_input, INPUT_ADD_INFO, psz_cat, _(ppsz_all_keys[i]), - vlc_dictionary_value_for_key( &p_meta->extra_tags, ppsz_all_keys[i] ) ); + if( p_cat ) + info_category_AddInfo( p_cat, vlc_gettext(ppsz_all_keys[i]), "%s", + vlc_meta_GetExtra( p_meta, ppsz_all_keys[i] ) ); free( ppsz_all_keys[i] ); } free( ppsz_all_keys ); - free( psz_cat ); -} - -static void vlc_epg_Merge( vlc_epg_t *p_dst, const vlc_epg_t *p_src ) -{ - int i; - - /* Add new event */ - for( i = 0; i < p_src->i_event; i++ ) + if( psz_provider ) { - vlc_epg_event_t *p_evt = p_src->pp_event[i]; - bool b_add = true; - int j; - - for( j = 0; j < p_dst->i_event; j++ ) - { - if( p_dst->pp_event[j]->i_start == p_evt->i_start && p_dst->pp_event[j]->i_duration == p_evt->i_duration ) - { - b_add = false; - break; - } - if( p_dst->pp_event[j]->i_start > p_evt->i_start ) - break; - } - if( b_add ) + if( p_sys->p_pgrm == p_pgrm ) { - vlc_epg_event_t *p_copy = malloc( sizeof(vlc_epg_event_t) ); - if( !p_copy ) - break; - memset( p_copy, 0, sizeof(vlc_epg_event_t) ); - p_copy->i_start = p_evt->i_start; - p_copy->i_duration = p_evt->i_duration; - p_copy->psz_name = p_evt->psz_name ? strdup( p_evt->psz_name ) : NULL; - p_copy->psz_short_description = p_evt->psz_short_description ? strdup( p_evt->psz_short_description ) : NULL; - p_copy->psz_description = p_evt->psz_description ? strdup( p_evt->psz_description ) : NULL; - TAB_INSERT( p_dst->i_event, p_dst->pp_event, p_copy, j ); + input_item_SetPublisher( p_input->p->p_item, psz_provider ); + input_SendEventMeta( p_input ); } + if( p_cat ) + info_category_AddInfo( p_cat, vlc_meta_TypeToLocalizedString(vlc_meta_Publisher), + "%s",psz_provider ); } - /* Update current */ - vlc_epg_SetCurrent( p_dst, p_src->p_current ? p_src->p_current->i_start : -1 ); - - /* Keep only 1 old event */ - if( p_dst->p_current ) - { - while( p_dst->i_event > 1 && p_dst->pp_event[0] != p_dst->p_current && p_dst->pp_event[1] != p_dst->p_current ) - TAB_REMOVE( p_dst->i_event, p_dst->pp_event, p_dst->pp_event[0] ); - } + if( p_cat ) + input_Control( p_input, INPUT_MERGE_INFOS, p_cat ); } -static void EsOutProgramEpg( es_out_t *out, int i_group, vlc_epg_t *p_epg ) +static void EsOutProgramEpg( es_out_t *out, int i_group, const vlc_epg_t *p_epg ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - es_out_pgrm_t *p_pgrm = NULL; + input_item_t *p_item = p_input->p->p_item; + es_out_pgrm_t *p_pgrm; char *psz_cat; - int i; /* Find program */ - for( i = 0; i < p_sys->i_pgrm; i++ ) - { - if( p_sys->pgrm[i]->i_id == i_group ) - { - p_pgrm = p_sys->pgrm[i]; - break; - } - } - if( p_pgrm == NULL ) - p_pgrm = EsOutProgramAdd( out, i_group ); /* Create it */ - - /* Merge EPG */ - if( !p_pgrm->p_epg ) - p_pgrm->p_epg = vlc_epg_New( p_pgrm->psz_name ); - vlc_epg_Merge( p_pgrm->p_epg, p_epg ); + if( !EsOutIsProgramVisible( out, i_group ) ) + return; + p_pgrm = EsOutProgramFind( out, i_group ); + if( !p_pgrm ) + return; /* Update info */ psz_cat = EsOutProgramGetMetaName( p_pgrm ); -#ifdef HAVE_LOCALTIME_R - char *psz_epg; - if( asprintf( &psz_epg, "EPG %s", psz_cat ) == -1 ) - psz_epg = NULL; - input_Control( p_input, INPUT_DEL_INFO, psz_epg, NULL ); - msg_Dbg( p_input, "EsOutProgramEpg: number=%d name=%s", i_group, p_pgrm->p_epg->psz_name ); - for( i = 0; i < p_pgrm->p_epg->i_event; i++ ) - { - const vlc_epg_event_t *p_evt = p_pgrm->p_epg->pp_event[i]; - time_t t_start = (time_t)p_evt->i_start; - struct tm tm_start; - char psz_start[128]; - - localtime_r( &t_start, &tm_start ); - - snprintf( psz_start, sizeof(psz_start), "%2.2d:%2.2d:%2.2d", tm_start.tm_hour, tm_start.tm_min, tm_start.tm_sec ); - if( p_evt->psz_short_description || p_evt->psz_description ) - input_Control( p_input, INPUT_ADD_INFO, psz_epg, psz_start, "%s (%2.2d:%2.2d) - %s", - p_evt->psz_name, - p_evt->i_duration/60/60, (p_evt->i_duration/60)%60, - p_evt->psz_short_description ? p_evt->psz_short_description : p_evt->psz_description ); - else - input_Control( p_input, INPUT_ADD_INFO, psz_epg, psz_start, "%s (%2.2d:%2.2d)", - p_evt->psz_name, - p_evt->i_duration/60/60, (p_evt->i_duration/60)%60 ); - } - free( psz_epg ); -#endif + msg_Dbg( p_input, "EsOutProgramEpg: number=%d name=%s", i_group, psz_cat ); + + /* Merge EPG */ + vlc_epg_t epg; + + epg = *p_epg; + epg.psz_name = psz_cat; + + input_item_SetEpg( p_item, &epg ); + input_SendEventMetaEpg( p_sys->p_input ); + /* Update now playing */ free( p_pgrm->psz_now_playing ); p_pgrm->psz_now_playing = NULL; - if( p_epg->p_current && p_epg->p_current->psz_name && *p_epg->p_current->psz_name ) - p_pgrm->psz_now_playing = strdup( p_epg->p_current->psz_name ); + + vlc_mutex_lock( &p_item->lock ); + for( int i = 0; i < p_item->i_epg; i++ ) + { + const vlc_epg_t *p_tmp = p_item->pp_epg[i]; + + if( p_tmp->psz_name && !strcmp(p_tmp->psz_name, psz_cat) ) + { + if( p_tmp->p_current && p_tmp->p_current->psz_name && *p_tmp->p_current->psz_name ) + p_pgrm->psz_now_playing = strdup( p_tmp->p_current->psz_name ); + break; + } + } + vlc_mutex_unlock( &p_item->lock ); if( p_pgrm == p_sys->p_pgrm ) - input_item_SetNowPlaying( p_input->p->input.p_item, p_pgrm->psz_now_playing ); + { + input_item_SetNowPlaying( p_input->p->p_item, p_pgrm->psz_now_playing ); + input_SendEventMeta( p_input ); + } if( p_pgrm->psz_now_playing ) { input_Control( p_input, INPUT_ADD_INFO, psz_cat, - input_MetaTypeToLocalizedString(vlc_meta_NowPlaying), + vlc_meta_TypeToLocalizedString(vlc_meta_NowPlaying), p_pgrm->psz_now_playing ); } else { input_Control( p_input, INPUT_DEL_INFO, psz_cat, - input_MetaTypeToLocalizedString(vlc_meta_NowPlaying) ); + vlc_meta_TypeToLocalizedString(vlc_meta_NowPlaying) ); } free( psz_cat ); } +static void EsOutProgramUpdateScrambled( es_out_t *p_out, es_out_pgrm_t *p_pgrm ) +{ + es_out_sys_t *p_sys = p_out->p_sys; + input_thread_t *p_input = p_sys->p_input; + bool b_scrambled = false; + + for( int i = 0; i < p_sys->i_es; i++ ) + { + if( p_sys->es[i]->p_pgrm == p_pgrm && p_sys->es[i]->b_scrambled ) + { + b_scrambled = true; + break; + } + } + if( !p_pgrm->b_scrambled == !b_scrambled ) + return; + + p_pgrm->b_scrambled = b_scrambled; + char *psz_cat = EsOutProgramGetMetaName( p_pgrm ); + + if( b_scrambled ) + input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Scrambled"), _("Yes") ); + else + input_Control( p_input, INPUT_DEL_INFO, psz_cat, _("Scrambled") ); + + input_SendEventProgramScrambled( p_input, p_pgrm->i_id, b_scrambled ); +} + +static void EsOutMeta( es_out_t *p_out, const vlc_meta_t *p_meta ) +{ + es_out_sys_t *p_sys = p_out->p_sys; + input_thread_t *p_input = p_sys->p_input; + + input_item_t *p_item = input_GetItem( p_input ); + + char *psz_title = NULL; + char *psz_arturl = input_item_GetArtURL( p_item ); + + vlc_mutex_lock( &p_item->lock ); + + if( vlc_meta_Get( p_meta, vlc_meta_Title ) && !p_item->b_fixed_name ) + psz_title = strdup( vlc_meta_Get( p_meta, vlc_meta_Title ) ); + + vlc_meta_Merge( p_item->p_meta, p_meta ); + + if( !psz_arturl || *psz_arturl == '\0' ) + { + const char *psz_tmp = vlc_meta_Get( p_item->p_meta, vlc_meta_ArtworkURL ); + if( psz_tmp ) + psz_arturl = strdup( psz_tmp ); + } + vlc_mutex_unlock( &p_item->lock ); + + if( psz_arturl && *psz_arturl ) + { + input_item_SetArtURL( p_item, psz_arturl ); + + if( !strncmp( psz_arturl, "attachment://", strlen("attachment") ) ) + { + /* Don't look for art cover if sout + * XXX It can change when sout has meta data support */ + if( p_input->p->p_sout && !p_input->b_preparsing ) + input_item_SetArtURL( p_item, "" ); + else + input_ExtractAttachmentAndCacheArt( p_input ); + } + } + free( psz_arturl ); + + if( psz_title ) + { + input_item_SetName( p_item, psz_title ); + free( psz_title ); + } + input_item_SetPreparsed( p_item, true ); + + input_SendEventMeta( p_input ); + /* TODO handle sout meta ? */ +} + /* EsOutAdd: * Add an es_out */ -static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) +static es_out_id_t *EsOutAdd( es_out_t *out, const es_format_t *fmt ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - es_out_id_t *es = malloc( sizeof( es_out_id_t ) ); - es_out_pgrm_t *p_pgrm = NULL; - int i; - - if( !es ) return NULL; - if( fmt->i_group < 0 ) { msg_Err( p_input, "invalid group number" ); - free( es ); return NULL; } + es_out_id_t *es = malloc( sizeof( *es ) ); + es_out_pgrm_t *p_pgrm; + int i; + + if( !es ) + return NULL; + + vlc_mutex_lock( &p_sys->lock ); + /* Search the program */ - for( i = 0; i < p_sys->i_pgrm; i++ ) + p_pgrm = EsOutProgramFind( out, fmt->i_group ); + if( !p_pgrm ) { - if( fmt->i_group == p_sys->pgrm[i]->i_id ) - { - p_pgrm = p_sys->pgrm[i]; - break; - } - } - if( p_pgrm == NULL ) - { - /* Create a new one */ - p_pgrm = EsOutProgramAdd( out, fmt->i_group ); + vlc_mutex_unlock( &p_sys->lock ); + free( es ); + return NULL; } /* Increase ref count for program */ p_pgrm->i_es++; /* Set up ES */ - if( fmt->i_id < 0 ) - fmt->i_id = out->p_sys->i_id; - es->i_id = fmt->i_id; es->p_pgrm = p_pgrm; es_format_Copy( &es->fmt, fmt ); - es->i_preroll_end = -1; + if( es->fmt.i_id < 0 ) + es->fmt.i_id = out->p_sys->i_id; + if( !es->fmt.i_original_fourcc ) + es->fmt.i_original_fourcc = es->fmt.i_codec; + if( es->fmt.i_cat == AUDIO_ES ) + es->fmt.i_codec = vlc_fourcc_GetCodecAudio( es->fmt.i_codec, + es->fmt.audio.i_bitspersample ); + else + es->fmt.i_codec = vlc_fourcc_GetCodec( es->fmt.i_cat, + es->fmt.i_codec ); - switch( fmt->i_cat ) + es->i_id = es->fmt.i_id; + es->i_meta_id = out->p_sys->i_id; + es->b_scrambled = false; + + switch( es->fmt.i_cat ) { case AUDIO_ES: { @@ -1052,9 +1486,9 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) es->i_channel = p_sys->i_audio; memset( &rg, 0, sizeof(rg) ); - vlc_mutex_lock( &p_input->p->input.p_item->lock ); - vlc_audio_replay_gain_MergeFromMeta( &rg, p_input->p->input.p_item->p_meta ); - vlc_mutex_unlock( &p_input->p->input.p_item->lock ); + vlc_mutex_lock( &p_input->p->p_item->lock ); + vlc_audio_replay_gain_MergeFromMeta( &rg, p_input->p->p_item->p_meta ); + vlc_mutex_unlock( &p_input->p->p_item->lock ); for( i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ ) { @@ -1074,11 +1508,11 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) case VIDEO_ES: es->i_channel = p_sys->i_video; - if( fmt->video.i_frame_rate && fmt->video.i_frame_rate_base ) + if( es->fmt.video.i_frame_rate && es->fmt.video.i_frame_rate_base ) vlc_ureduce( &es->fmt.video.i_frame_rate, &es->fmt.video.i_frame_rate_base, - fmt->video.i_frame_rate, - fmt->video.i_frame_rate_base, 0 ); + es->fmt.video.i_frame_rate, + es->fmt.video.i_frame_rate_base, 0 ); break; case SPU_ES: @@ -1089,8 +1523,8 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) es->i_channel = 0; break; } - es->psz_language = LanguageGetName( fmt->psz_language ); /* remember so we only need to do it once */ - es->psz_language_code = LanguageGetCode( fmt->psz_language ); + es->psz_language = LanguageGetName( es->fmt.psz_language ); /* remember so we only need to do it once */ + es->psz_language_code = LanguageGetCode( es->fmt.psz_language ); es->p_dec = NULL; es->p_dec_record = NULL; for( i = 0; i < 4; i++ ) @@ -1106,7 +1540,7 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) TAB_APPEND( out->p_sys->i_es, out->p_sys->es, es ); p_sys->i_id++; /* always incremented */ - switch( fmt->i_cat ) + switch( es->fmt.i_cat ) { case AUDIO_ES: p_sys->i_audio++; @@ -1119,7 +1553,12 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) break; } - EsOutAddInfo( out, es ); + EsOutUpdateInfo( out, es, &es->fmt, NULL ); + + if( es->b_scrambled ) + EsOutProgramUpdateScrambled( out, es->p_pgrm ); + + vlc_mutex_unlock( &p_sys->lock ); return es; } @@ -1147,13 +1586,25 @@ static void EsCreateDecoder( es_out_t *out, es_out_id_t *p_es ) es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - p_es->p_dec = input_DecoderNew( p_input, &p_es->fmt, p_input->p->p_sout ); - if( p_es->p_dec && !p_es->p_master && p_sys->p_sout_record ) - p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_sys->p_sout_record ); + p_es->p_dec = input_DecoderNew( p_input, &p_es->fmt, p_es->p_pgrm->p_clock, p_input->p->p_sout ); + if( p_es->p_dec ) + { + if( p_sys->b_buffering ) + input_DecoderStartBuffering( p_es->p_dec ); + + if( !p_es->p_master && p_sys->p_sout_record ) + { + p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_es->p_pgrm->p_clock, p_sys->p_sout_record ); + if( p_es->p_dec_record && p_sys->b_buffering ) + input_DecoderStartBuffering( p_es->p_dec_record ); + } + } + + EsOutDecoderChangeDelay( out, p_es ); } static void EsDestroyDecoder( es_out_t *out, es_out_id_t *p_es ) { - es_out_sys_t *p_sys = out->p_sys; + VLC_UNUSED(out); if( !p_es->p_dec ) return; @@ -1172,8 +1623,6 @@ static void EsSelect( es_out_t *out, es_out_id_t *es ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - vlc_value_t val; - const char *psz_var; if( EsIsSelected( es ) ) { @@ -1193,9 +1642,10 @@ static void EsSelect( es_out_t *out, es_out_id_t *es ) } else { + const bool b_sout = p_input->p->p_sout != NULL; if( es->fmt.i_cat == VIDEO_ES || es->fmt.i_cat == SPU_ES ) { - if( !var_GetBool( p_input, out->b_sout ? "sout-video" : "video" ) ) + if( !var_GetBool( p_input, b_sout ? "sout-video" : "video" ) ) { msg_Dbg( p_input, "video is disabled, not selecting ES 0x%x", es->i_id ); @@ -1204,8 +1654,7 @@ static void EsSelect( es_out_t *out, es_out_id_t *es ) } else if( es->fmt.i_cat == AUDIO_ES ) { - var_Get( p_input, "audio", &val ); - if( !var_GetBool( p_input, out->b_sout ? "sout-audio" : "audio" ) ) + if( !var_GetBool( p_input, b_sout ? "sout-audio" : "audio" ) ) { msg_Dbg( p_input, "audio is disabled, not selecting ES 0x%x", es->i_id ); @@ -1214,8 +1663,7 @@ static void EsSelect( es_out_t *out, es_out_id_t *es ) } if( es->fmt.i_cat == SPU_ES ) { - var_Get( p_input, "spu", &val ); - if( !var_GetBool( p_input, out->b_sout ? "sout-spu" : "spu" ) ) + if( !var_GetBool( p_input, b_sout ? "sout-spu" : "spu" ) ) { msg_Dbg( p_input, "spu is disabled, not selecting ES 0x%x", es->i_id ); @@ -1223,35 +1671,21 @@ static void EsSelect( es_out_t *out, es_out_id_t *es ) } } - es->i_preroll_end = -1; EsCreateDecoder( out, es ); if( es->p_dec == NULL || es->p_pgrm != p_sys->p_pgrm ) return; } - if( es->fmt.i_cat == VIDEO_ES ) - psz_var = "video-es"; - else if( es->fmt.i_cat == AUDIO_ES ) - psz_var = "audio-es"; - else if( es->fmt.i_cat == SPU_ES ) - psz_var = "spu-es"; - else - return; - /* Mark it as selected */ - val.i_int = es->i_id; - var_Change( p_input, psz_var, VLC_VAR_SETVALUE, &val, NULL ); - - var_SetBool( p_sys->p_input, "intf-change", true ); + input_SendEventEsSelect( p_input, es->fmt.i_cat, es->i_id ); + input_SendEventTeletextSelect( p_input, EsFmtIsTeletext( &es->fmt ) ? es->i_id : -1 ); } static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - vlc_value_t val; - const char *psz_var; if( !EsIsSelected( es ) ) { @@ -1280,10 +1714,7 @@ static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update ) if( i_spu_id == es->pp_cc_es[i]->i_id ) { /* Force unselection of the CC */ - val.i_int = -1; - var_Change( p_input, "spu-es", VLC_VAR_SETVALUE, &val, NULL ); - if( !b_update ) - var_SetBool( p_sys->p_input, "intf-change", true ); + input_SendEventEsSelect( p_input, SPU_ES, -1 ); } EsOutDel( out, es->pp_cc_es[i] ); @@ -1295,23 +1726,10 @@ static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update ) if( !b_update ) return; - /* Update var */ - if( es->p_dec == NULL ) - return; - if( es->fmt.i_cat == VIDEO_ES ) - psz_var = "video-es"; - else if( es->fmt.i_cat == AUDIO_ES ) - psz_var = "audio-es"; - else if( es->fmt.i_cat == SPU_ES ) - psz_var = "spu-es"; - else - return; - /* Mark it as unselected */ - val.i_int = -1; - var_Change( p_input, psz_var, VLC_VAR_SETVALUE, &val, NULL ); - - var_SetBool( p_sys->p_input, "intf-change", true ); + input_SendEventEsSelect( p_input, es->fmt.i_cat, -1 ); + if( EsFmtIsTeletext( &es->fmt ) ) + input_SendEventTeletextSelect( p_input, -1 ); } /** @@ -1342,19 +1760,24 @@ static void EsOutSelect( es_out_t *out, es_out_id_t *es, bool b_force ) } else if( p_sys->i_mode == ES_OUT_MODE_PARTIAL ) { - vlc_value_t val; - int i; - var_Get( p_sys->p_input, "programs", &val ); - for ( i = 0; i < val.p_list->i_count; i++ ) + char *prgms = var_GetNonEmptyString( p_sys->p_input, "programs" ); + if( prgms != NULL ) { - if ( val.p_list->p_values[i].i_int == es->p_pgrm->i_id || b_force ) + char *buf; + + for ( const char *prgm = strtok_r( prgms, ",", &buf ); + prgm != NULL; + prgm = strtok_r( NULL, ",", &buf ) ) { - if( !EsIsSelected( es ) ) - EsSelect( out, es ); - break; + if( atoi( prgm ) == es->p_pgrm->i_id || b_force ) + { + if( !EsIsSelected( es ) ) + EsSelect( out, es ); + break; + } } + free( prgms ); } - var_Change( p_sys->p_input, "programs", VLC_VAR_FREELIST, &val, NULL ); } else if( p_sys->i_mode == ES_OUT_MODE_AUTO ) { @@ -1489,137 +1912,128 @@ static void EsOutSelect( es_out_t *out, es_out_id_t *es, bool b_force ) /** * Send a block for the given es_out * - * \param out the es_out to send from - * \param es the es_out_id - * \param p_block the data block to send - */ -static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block ) -{ - es_out_sys_t *p_sys = out->p_sys; - input_thread_t *p_input = p_sys->p_input; - es_out_pgrm_t *p_pgrm = es->p_pgrm; - int64_t i_delay; - int i_total=0; - - if( es->fmt.i_cat == AUDIO_ES ) - i_delay = p_sys->i_audio_delay; - else if( es->fmt.i_cat == SPU_ES ) - i_delay = p_sys->i_spu_delay; - else - i_delay = 0; + * \param out the es_out to send from + * \param es the es_out_id + * \param p_block the data block to send + */ +static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block ) +{ + es_out_sys_t *p_sys = out->p_sys; + input_thread_t *p_input = p_sys->p_input; + int i_total = 0; - if( libvlc_stats (p_input) ) + if( libvlc_stats( p_input ) ) { vlc_mutex_lock( &p_input->p->counters.counters_lock ); stats_UpdateInteger( p_input, p_input->p->counters.p_demux_read, p_block->i_buffer, &i_total ); stats_UpdateFloat( p_input , p_input->p->counters.p_demux_bitrate, (float)i_total, NULL ); + + /* Update number of corrupted data packats */ + if( p_block->i_flags & BLOCK_FLAG_CORRUPTED ) + { + stats_UpdateInteger( p_input, p_input->p->counters.p_demux_corrupted, + 1, NULL ); + } + /* Update number of discontinuities */ + if( p_block->i_flags & BLOCK_FLAG_DISCONTINUITY ) + { + stats_UpdateInteger( p_input, p_input->p->counters.p_demux_discontinuity, + 1, NULL ); + } vlc_mutex_unlock( &p_input->p->counters.counters_lock ); } + vlc_mutex_lock( &p_sys->lock ); + /* Mark preroll blocks */ - if( es->i_preroll_end >= 0 ) + if( p_sys->i_preroll_end >= 0 ) { int64_t i_date = p_block->i_pts; - if( i_date <= 0 ) + if( p_block->i_pts <= VLC_TS_INVALID ) i_date = p_block->i_dts; - if( i_date < es->i_preroll_end ) + if( i_date < p_sys->i_preroll_end ) p_block->i_flags |= BLOCK_FLAG_PREROLL; - else - es->i_preroll_end = -1; } - if( p_block->i_dts > 0 && (p_block->i_flags&BLOCK_FLAG_PREROLL) ) - { - p_block->i_dts += i_delay; - } - else if( p_block->i_dts > 0 ) + p_block->i_rate = 0; + + if( !es->p_dec ) { - p_block->i_dts = - input_ClockGetTS( p_input, &p_pgrm->clock, p_block->i_dts ) + i_delay; + block_Release( p_block ); + vlc_mutex_unlock( &p_sys->lock ); + return VLC_SUCCESS; } - if( p_block->i_pts > 0 && (p_block->i_flags&BLOCK_FLAG_PREROLL) ) + + /* Check for sout mode */ + if( p_input->p->p_sout ) { - p_block->i_pts += i_delay; + /* FIXME review this, proper lock may be missing */ + if( p_input->p->p_sout->i_out_pace_nocontrol > 0 && + p_input->p->b_out_pace_control ) + { + msg_Dbg( p_input, "switching to sync mode" ); + p_input->p->b_out_pace_control = false; + } + else if( p_input->p->p_sout->i_out_pace_nocontrol <= 0 && + !p_input->p->b_out_pace_control ) + { + msg_Dbg( p_input, "switching to async mode" ); + p_input->p->b_out_pace_control = true; + } } - else if( p_block->i_pts > 0 ) + + /* Decode */ + if( es->p_dec_record ) { - p_block->i_pts = - input_ClockGetTS( p_input, &p_pgrm->clock, p_block->i_pts ) + i_delay; + block_t *p_dup = block_Duplicate( p_block ); + if( p_dup ) + input_DecoderDecode( es->p_dec_record, p_dup, + p_input->p->b_out_pace_control ); } - if ( p_block->i_rate == INPUT_RATE_DEFAULT && - es->fmt.i_codec == VLC_FOURCC( 't', 'e', 'l', 'x' ) ) + input_DecoderDecode( es->p_dec, p_block, + p_input->p->b_out_pace_control ); + + es_format_t fmt_dsc; + vlc_meta_t *p_meta_dsc; + if( input_DecoderHasFormatChanged( es->p_dec, &fmt_dsc, &p_meta_dsc ) ) { - mtime_t current_date = mdate(); - if( !p_block->i_pts - || p_block->i_pts > current_date + 10000000 - || current_date > p_block->i_pts ) - { - /* ETSI EN 300 472 Annex A : do not take into account the PTS - * for teletext streams. */ - p_block->i_pts = current_date + 400000 - + p_input->i_pts_delay + i_delay; - } + EsOutUpdateInfo( out, es, &fmt_dsc, p_meta_dsc ); + + es_format_Clean( &fmt_dsc ); + if( p_meta_dsc ) + vlc_meta_Delete( p_meta_dsc ); } - p_block->i_rate = p_sys->i_rate; + /* Check CC status */ + bool pb_cc[4]; - /* TODO handle mute */ - if( es->p_dec && - ( es->fmt.i_cat != AUDIO_ES || - ( p_sys->i_rate >= INPUT_RATE_DEFAULT/AOUT_MAX_INPUT_RATE && - p_sys->i_rate <= INPUT_RATE_DEFAULT*AOUT_MAX_INPUT_RATE ) ) ) + input_DecoderIsCcPresent( es->p_dec, pb_cc ); + for( int i = 0; i < 4; i++ ) { - bool pb_cc[4]; - bool b_cc_new = false; - int i; - if( es->p_dec_record ) - { - block_t *p_dup = block_Duplicate( p_block ); - if( p_dup ) - input_DecoderDecode( es->p_dec_record, p_dup ); - } - input_DecoderDecode( es->p_dec, p_block ); + es_format_t fmt; - /* Check CC status */ - input_DecoderIsCcPresent( es->p_dec, pb_cc ); - for( i = 0; i < 4; i++ ) - { - static const vlc_fourcc_t fcc[4] = { - VLC_FOURCC('c', 'c', '1', ' '), - VLC_FOURCC('c', 'c', '2', ' '), - VLC_FOURCC('c', 'c', '3', ' '), - VLC_FOURCC('c', 'c', '4', ' '), - }; - es_format_t fmt; - - if( es->pb_cc_present[i] || !pb_cc[i] ) - continue; - msg_Dbg( p_input, "Adding CC track %d for es[%d]", 1+i, es->i_id ); + if( es->pb_cc_present[i] || !pb_cc[i] ) + continue; + msg_Dbg( p_input, "Adding CC track %d for es[%d]", 1+i, es->i_id ); - es_format_Init( &fmt, SPU_ES, fcc[i] ); - fmt.i_group = es->fmt.i_group; - if( asprintf( &fmt.psz_description, - _("Closed captions %u"), 1 + i ) == -1 ) - fmt.psz_description = NULL; - es->pp_cc_es[i] = EsOutAdd( out, &fmt ); - es->pp_cc_es[i]->p_master = es; - es_format_Clean( &fmt ); + es_format_Init( &fmt, SPU_ES, EsOutFourccClosedCaptions[i] ); + fmt.i_group = es->fmt.i_group; + if( asprintf( &fmt.psz_description, + _("Closed captions %u"), 1 + i ) == -1 ) + fmt.psz_description = NULL; + es->pp_cc_es[i] = EsOutAdd( out, &fmt ); + es->pp_cc_es[i]->p_master = es; + es_format_Clean( &fmt ); - /* */ - es->pb_cc_present[i] = true; - b_cc_new = true; - } - if( b_cc_new ) - var_SetBool( p_sys->p_input, "intf-change", true ); - } - else - { - block_Release( p_block ); + /* */ + es->pb_cc_present[i] = true; } + vlc_mutex_unlock( &p_sys->lock ); + return VLC_SUCCESS; } @@ -1632,14 +2046,18 @@ static void EsOutDel( es_out_t *out, es_out_id_t *es ) bool b_reselect = false; int i; + vlc_mutex_lock( &p_sys->lock ); + /* We don't try to reselect */ if( es->p_dec ) { - while( !out->p_sys->p_input->b_die && es->p_dec ) + while( !p_sys->p_input->b_die && !p_sys->b_buffering && es->p_dec ) { - if( input_DecoderEmpty( es->p_dec ) && - ( !es->p_dec_record || input_DecoderEmpty( es->p_dec_record ) )) + if( input_DecoderIsEmpty( es->p_dec ) && + ( !es->p_dec_record || input_DecoderIsEmpty( es->p_dec_record ) )) break; + /* FIXME there should be a way to have auto deleted es, but there will be + * a problem when another codec of the same type is created (mainly video) */ msleep( 20*1000 ); } EsUnselect( out, es, es->p_pgrm == p_sys->p_pgrm ); @@ -1650,12 +2068,15 @@ static void EsOutDel( es_out_t *out, es_out_id_t *es ) TAB_REMOVE( p_sys->i_es, p_sys->es, es ); + /* Update program */ es->p_pgrm->i_es--; if( es->p_pgrm->i_es == 0 ) - { msg_Dbg( p_sys->p_input, "Program doesn't contain anymore ES" ); - } + if( es->b_scrambled ) + EsOutProgramUpdateScrambled( out, es->p_pgrm ); + + /* */ if( p_sys->p_es_audio == es || p_sys->p_es_video == es || p_sys->p_es_sub == es ) b_reselect = true; @@ -1678,17 +2099,21 @@ static void EsOutDel( es_out_t *out, es_out_id_t *es ) /* Re-select another track when needed */ if( b_reselect ) + { for( i = 0; i < p_sys->i_es; i++ ) { if( es->fmt.i_cat == p_sys->es[i]->fmt.i_cat ) EsOutSelect( out, p_sys->es[i], false ); } + } free( es->psz_language ); free( es->psz_language_code ); es_format_Clean( &es->fmt ); + vlc_mutex_unlock( &p_sys->lock ); + free( es ); } @@ -1700,19 +2125,16 @@ static void EsOutDel( es_out_t *out, es_out_id_t *es ) * \param args a variable list of arguments for the query * \return VLC_SUCCESS or an error code */ -static int EsOutControl( es_out_t *out, int i_query, va_list args ) +static int EsOutControlLocked( es_out_t *out, int i_query, va_list args ) { es_out_sys_t *p_sys = out->p_sys; - bool b, *pb; - int i, *pi; - - es_out_id_t *es; switch( i_query ) { case ES_OUT_SET_ES_STATE: - es = (es_out_id_t*) va_arg( args, es_out_id_t * ); - b = (bool) va_arg( args, int ); + { + es_out_id_t *es = va_arg( args, es_out_id_t * ); + bool b = va_arg( args, int ); if( b && !EsIsSelected( es ) ) { EsSelect( out, es ); @@ -1724,65 +2146,69 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) return VLC_SUCCESS; } return VLC_SUCCESS; + } case ES_OUT_GET_ES_STATE: - es = (es_out_id_t*) va_arg( args, es_out_id_t * ); - pb = (bool*) va_arg( args, bool * ); + { + es_out_id_t *es = va_arg( args, es_out_id_t * ); + bool *pb = va_arg( args, bool * ); *pb = EsIsSelected( es ); return VLC_SUCCESS; + } - case ES_OUT_SET_ACTIVE: + case ES_OUT_GET_GROUP_FORCED: { - b = (bool) va_arg( args, int ); - p_sys->b_active = b; - /* Needed ? */ - if( b ) - var_SetBool( p_sys->p_input, "intf-change", true ); + int *pi_group = va_arg( args, int * ); + *pi_group = p_sys->i_group_id; return VLC_SUCCESS; } - case ES_OUT_GET_ACTIVE: - pb = (bool*) va_arg( args, bool * ); - *pb = p_sys->b_active; - return VLC_SUCCESS; - case ES_OUT_SET_MODE: - i = (int) va_arg( args, int ); - if( i == ES_OUT_MODE_NONE || i == ES_OUT_MODE_ALL || - i == ES_OUT_MODE_AUTO || i == ES_OUT_MODE_PARTIAL ) - { - p_sys->i_mode = i; + { + const int i_mode = va_arg( args, int ); + assert( i_mode == ES_OUT_MODE_NONE || i_mode == ES_OUT_MODE_ALL || + i_mode == ES_OUT_MODE_AUTO || i_mode == ES_OUT_MODE_PARTIAL || + i_mode == ES_OUT_MODE_END ); - /* Reapply policy mode */ - for( i = 0; i < p_sys->i_es; i++ ) - { - if( EsIsSelected( p_sys->es[i] ) ) - { - EsUnselect( out, p_sys->es[i], - p_sys->es[i]->p_pgrm == p_sys->p_pgrm ); - } - } + if( i_mode != ES_OUT_MODE_NONE && !p_sys->b_active && p_sys->i_es > 0 ) + { + /* XXX Terminate vout if there are tracks but no video one. + * This one is not mandatory but is he earliest place where it + * can be done */ + int i; for( i = 0; i < p_sys->i_es; i++ ) { - EsOutSelect( out, p_sys->es[i], false ); + es_out_id_t *p_es = p_sys->es[i]; + if( p_es->fmt.i_cat == VIDEO_ES ) + break; } - return VLC_SUCCESS; + if( i >= p_sys->i_es ) + input_resource_TerminateVout( p_sys->p_input->p->p_resource ); } - return VLC_EGENERIC; + p_sys->b_active = i_mode != ES_OUT_MODE_NONE; + p_sys->i_mode = i_mode; - case ES_OUT_GET_MODE: - pi = (int*) va_arg( args, int* ); - *pi = p_sys->i_mode; + /* Reapply policy mode */ + for( int i = 0; i < p_sys->i_es; i++ ) + { + if( EsIsSelected( p_sys->es[i] ) ) + EsUnselect( out, p_sys->es[i], + p_sys->es[i]->p_pgrm == p_sys->p_pgrm ); + } + for( int i = 0; i < p_sys->i_es; i++ ) + EsOutSelect( out, p_sys->es[i], false ); + if( i_mode == ES_OUT_MODE_END ) + EsOutTerminate( out ); return VLC_SUCCESS; + } case ES_OUT_SET_ES: case ES_OUT_RESTART_ES: { - int i_cat; - - es = (es_out_id_t*) va_arg( args, es_out_id_t * ); + es_out_id_t *es = va_arg( args, es_out_id_t * ); + int i_cat; if( es == NULL ) i_cat = UNKNOWN_ES; else if( es == (es_out_id_t*)((uint8_t*)NULL+AUDIO_ES) ) @@ -1794,7 +2220,7 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) else i_cat = -1; - for( i = 0; i < p_sys->i_es; i++ ) + for( int i = 0; i < p_sys->i_es; i++ ) { if( i_cat == -1 ) { @@ -1827,18 +2253,12 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) } } } - if( i_query == ES_OUT_SET_ES ) - { - vlc_event_t event; - event.type = vlc_InputSelectedStreamChanged; - vlc_event_send( &p_sys->p_input->p->event_manager, &event ); - } return VLC_SUCCESS; } - case ES_OUT_SET_DEFAULT: + case ES_OUT_SET_ES_DEFAULT: { - es = (es_out_id_t*) va_arg( args, es_out_id_t * ); + es_out_id_t *es = va_arg( args, es_out_id_t * ); if( es == NULL ) { @@ -1879,61 +2299,86 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) int i_group = 0; int64_t i_pcr; + /* Search program */ if( i_query == ES_OUT_SET_PCR ) { p_pgrm = p_sys->p_pgrm; + if( !p_pgrm ) + p_pgrm = EsOutProgramAdd( out, i_group ); /* Create it */ } else { - int i; i_group = (int)va_arg( args, int ); - for( i = 0; i < p_sys->i_pgrm; i++ ) + p_pgrm = EsOutProgramFind( out, i_group ); + } + if( !p_pgrm ) + return VLC_EGENERIC; + + i_pcr = (int64_t)va_arg( args, int64_t ); + if( i_pcr <= VLC_TS_INVALID ) + { + msg_Err( p_sys->p_input, "Invalid PCR value in ES_OUT_SET_(GROUP_)PCR !" ); + return VLC_EGENERIC; + } + + /* TODO do not use mdate() but proper stream acquisition date */ + bool b_late; + input_clock_Update( p_pgrm->p_clock, VLC_OBJECT(p_sys->p_input), + &b_late, + p_sys->p_input->p->b_can_pace_control || p_sys->b_buffering, + EsOutIsExtraBufferingAllowed( out ), + i_pcr, mdate() ); + + if( p_pgrm == p_sys->p_pgrm ) + { + if( p_sys->b_buffering ) + { + /* Check buffering state on master clock update */ + EsOutDecodersStopBuffering( out, false ); + } + else if( b_late && ( !p_sys->p_input->p->p_sout || + !p_sys->p_input->p->b_out_pace_control ) ) { - if( p_sys->pgrm[i]->i_id == i_group ) + const mtime_t i_pts_delay_base = p_sys->i_pts_delay - p_sys->i_pts_jitter; + mtime_t i_pts_delay = input_clock_GetJitter( p_pgrm->p_clock ); + + /* Avoid dangerously high value */ + const mtime_t i_jitter_max = INT64_C(1000) * var_InheritInteger( p_sys->p_input, "clock-jitter" ); + if( i_pts_delay > __MIN( i_pts_delay_base + i_jitter_max, INPUT_PTS_DELAY_MAX ) ) { - p_pgrm = p_sys->pgrm[i]; - break; + msg_Err( p_sys->p_input, + "ES_OUT_SET_(GROUP_)PCR is called too late (jitter of %d ms ignored)", + (int)(i_pts_delay - i_pts_delay_base) / 1000 ); + i_pts_delay = p_sys->i_pts_delay; + } + else + { + msg_Err( p_sys->p_input, + "ES_OUT_SET_(GROUP_)PCR is called too late (pts_delay increased to %d ms)", + (int)(i_pts_delay/1000) ); } + + /* Force a rebufferization when we are too late */ + + /* It is not really good, as we throw away already buffered data + * TODO have a mean to correctly reenter bufferization */ + es_out_Control( out, ES_OUT_RESET_PCR ); + + es_out_SetJitter( out, i_pts_delay_base, i_pts_delay - i_pts_delay_base, p_sys->i_cr_average ); } } - if( p_pgrm == NULL ) - p_pgrm = EsOutProgramAdd( out, i_group ); /* Create it */ - - i_pcr = (int64_t)va_arg( args, int64_t ); - /* search program */ - input_ClockSetPCR( p_sys->p_input, &p_pgrm->clock, i_pcr ); return VLC_SUCCESS; } case ES_OUT_RESET_PCR: - for( i = 0; i < p_sys->i_pgrm; i++ ) - input_ClockResetPCR( &p_sys->pgrm[i]->clock ); - return VLC_SUCCESS; - - case ES_OUT_GET_TS: - if( p_sys->p_pgrm ) - { - int64_t i_ts = (int64_t)va_arg( args, int64_t ); - int64_t *pi_ts = (int64_t *)va_arg( args, int64_t * ); - *pi_ts = input_ClockGetTS( p_sys->p_input, - &p_sys->p_pgrm->clock, i_ts ); - return VLC_SUCCESS; - } - return VLC_EGENERIC; - - case ES_OUT_GET_GROUP: - pi = (int*) va_arg( args, int* ); - if( p_sys->p_pgrm ) - *pi = p_sys->p_pgrm->i_id; - else - *pi = -1; /* FIXME */ + msg_Err( p_sys->p_input, "ES_OUT_RESET_PCR called" ); + EsOutChangePosition( out ); return VLC_SUCCESS; case ES_OUT_SET_GROUP: { - int j; - i = (int) va_arg( args, int ); - for( j = 0; j < p_sys->i_pgrm; j++ ) + int i = va_arg( args, int ); + for( int j = 0; j < p_sys->i_pgrm; j++ ) { es_out_pgrm_t *p_pgrm = p_sys->pgrm[j]; if( p_pgrm->i_id == i ) @@ -1945,20 +2390,19 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) return VLC_EGENERIC; } - case ES_OUT_SET_FMT: + case ES_OUT_SET_ES_FMT: { /* This ain't pretty but is need by some demuxers (eg. Ogg ) * to update the p_extra data */ - es_format_t *p_fmt; - es = (es_out_id_t*) va_arg( args, es_out_id_t * ); - p_fmt = (es_format_t*) va_arg( args, es_format_t * ); + es_out_id_t *es = va_arg( args, es_out_id_t * ); + es_format_t *p_fmt = va_arg( args, es_format_t * ); if( es == NULL ) return VLC_EGENERIC; if( p_fmt->i_extra ) { es->fmt.i_extra = p_fmt->i_extra; - es->fmt.p_extra = realloc( es->fmt.p_extra, p_fmt->i_extra ); + es->fmt.p_extra = xrealloc( es->fmt.p_extra, p_fmt->i_extra ); memcpy( es->fmt.p_extra, p_fmt->p_extra, p_fmt->i_extra ); if( !es->p_dec ) @@ -1970,7 +2414,7 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) #else es->p_dec->fmt_in.i_extra = p_fmt->i_extra; es->p_dec->fmt_in.p_extra = - realloc( es->p_dec->fmt_in.p_extra, p_fmt->i_extra ); + xrealloc( es->p_dec->fmt_in.p_extra, p_fmt->i_extra ); memcpy( es->p_dec->fmt_in.p_extra, p_fmt->p_extra, p_fmt->i_extra ); #endif @@ -1979,27 +2423,34 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) return VLC_SUCCESS; } - case ES_OUT_SET_NEXT_DISPLAY_TIME: + case ES_OUT_SET_ES_SCRAMBLED_STATE: { - int64_t i_date; + es_out_id_t *es = va_arg( args, es_out_id_t * ); + bool b_scrambled = (bool)va_arg( args, int ); + + if( !es->b_scrambled != !b_scrambled ) + { + es->b_scrambled = b_scrambled; + EsOutProgramUpdateScrambled( out, es->p_pgrm ); + } + return VLC_SUCCESS; + } - es = (es_out_id_t*) va_arg( args, es_out_id_t * ); - i_date = (int64_t)va_arg( args, int64_t ); + case ES_OUT_SET_NEXT_DISPLAY_TIME: + { + const int64_t i_date = (int64_t)va_arg( args, int64_t ); - if( !es || !es->p_dec ) + if( i_date < 0 ) return VLC_EGENERIC; - /* XXX We should call input_ClockGetTS but PCR has been reseted - * and it will return 0, so we won't call input_ClockGetTS on all preroll samples - * but that's ugly(more time discontinuity), it need to be improved -- fenrir */ - es->i_preroll_end = i_date; + p_sys->i_preroll_end = i_date; return VLC_SUCCESS; } case ES_OUT_SET_GROUP_META: { int i_group = (int)va_arg( args, int ); - vlc_meta_t *p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t * ); + const vlc_meta_t *p_meta = va_arg( args, const vlc_meta_t * ); EsOutProgramMeta( out, i_group, p_meta ); return VLC_SUCCESS; @@ -2007,11 +2458,12 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) case ES_OUT_SET_GROUP_EPG: { int i_group = (int)va_arg( args, int ); - vlc_epg_t *p_epg = (vlc_epg_t*)va_arg( args, vlc_epg_t * ); + const vlc_epg_t *p_epg = va_arg( args, const vlc_epg_t * ); EsOutProgramEpg( out, i_group, p_epg ); return VLC_SUCCESS; } + case ES_OUT_DEL_GROUP: { int i_group = (int)va_arg( args, int ); @@ -2019,11 +2471,239 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) return EsOutProgramDel( out, i_group ); } + case ES_OUT_SET_META: + { + const vlc_meta_t *p_meta = va_arg( args, const vlc_meta_t * ); + + EsOutMeta( out, p_meta ); + return VLC_SUCCESS; + } + + case ES_OUT_GET_WAKE_UP: + { + mtime_t *pi_wakeup = (mtime_t*)va_arg( args, mtime_t* ); + *pi_wakeup = EsOutGetWakeup( out ); + return VLC_SUCCESS; + } + + case ES_OUT_SET_ES_BY_ID: + case ES_OUT_RESTART_ES_BY_ID: + case ES_OUT_SET_ES_DEFAULT_BY_ID: + { + const int i_id = (int)va_arg( args, int ); + es_out_id_t *p_es = EsOutGetFromID( out, i_id ); + int i_new_query; + + switch( i_query ) + { + case ES_OUT_SET_ES_BY_ID: i_new_query = ES_OUT_SET_ES; break; + case ES_OUT_RESTART_ES_BY_ID: i_new_query = ES_OUT_RESTART_ES; break; + case ES_OUT_SET_ES_DEFAULT_BY_ID: i_new_query = ES_OUT_SET_ES_DEFAULT; break; + default: + assert(0); + } + /* TODO if the lock is made non recursive it should be changed */ + int i_ret = es_out_Control( out, i_new_query, p_es ); + + /* Clean up vout after user action (in active mode only). + * FIXME it does not work well with multiple video windows */ + if( p_sys->b_active ) + input_resource_TerminateVout( p_sys->p_input->p->p_resource ); + return i_ret; + } + + case ES_OUT_GET_ES_OBJECTS_BY_ID: + { + const int i_id = va_arg( args, int ); + es_out_id_t *p_es = EsOutGetFromID( out, i_id ); + if( !p_es ) + return VLC_EGENERIC; + + vlc_object_t **pp_decoder = va_arg( args, vlc_object_t ** ); + vout_thread_t **pp_vout = va_arg( args, vout_thread_t ** ); + aout_instance_t **pp_aout = va_arg( args, aout_instance_t ** ); + if( p_es->p_dec ) + { + if( pp_decoder ) + *pp_decoder = vlc_object_hold( p_es->p_dec ); + input_DecoderGetObjects( p_es->p_dec, pp_vout, pp_aout ); + } + else + { + if( pp_decoder ) + *pp_decoder = NULL; + if( pp_vout ) + *pp_vout = NULL; + if( pp_aout ) + *pp_aout = NULL; + } + return VLC_SUCCESS; + } + + case ES_OUT_GET_BUFFERING: + { + bool *pb = va_arg( args, bool* ); + *pb = p_sys->b_buffering; + return VLC_SUCCESS; + } + + case ES_OUT_GET_EMPTY: + { + bool *pb = va_arg( args, bool* ); + *pb = EsOutDecodersIsEmpty( out ); + return VLC_SUCCESS; + } + + case ES_OUT_SET_DELAY: + { + const int i_cat = (int)va_arg( args, int ); + const mtime_t i_delay = (mtime_t)va_arg( args, mtime_t ); + EsOutSetDelay( out, i_cat, i_delay ); + return VLC_SUCCESS; + } + + case ES_OUT_SET_RECORD_STATE: + { + bool b = va_arg( args, int ); + return EsOutSetRecord( out, b ); + } + + case ES_OUT_SET_PAUSE_STATE: + { + const bool b_source_paused = (bool)va_arg( args, int ); + const bool b_paused = (bool)va_arg( args, int ); + const mtime_t i_date = (mtime_t) va_arg( args, mtime_t ); + + assert( !b_source_paused == !b_paused ); + EsOutChangePause( out, b_paused, i_date ); + + return VLC_SUCCESS; + } + + case ES_OUT_SET_RATE: + { + const int i_src_rate = (int)va_arg( args, int ); + const int i_rate = (int)va_arg( args, int ); + + assert( i_src_rate == i_rate ); + EsOutChangeRate( out, i_rate ); + + return VLC_SUCCESS; + } + + case ES_OUT_SET_TIME: + { + const mtime_t i_date = (mtime_t)va_arg( args, mtime_t ); + + assert( i_date == -1 ); + EsOutChangePosition( out ); + + return VLC_SUCCESS; + } + + case ES_OUT_SET_FRAME_NEXT: + EsOutFrameNext( out ); + return VLC_SUCCESS; + + case ES_OUT_SET_TIMES: + { + double f_position = (double)va_arg( args, double ); + mtime_t i_time = (mtime_t)va_arg( args, mtime_t ); + mtime_t i_length = (mtime_t)va_arg( args, mtime_t ); + + input_SendEventLength( p_sys->p_input, i_length ); + + if( !p_sys->b_buffering ) + { + mtime_t i_delay; + + /* Fix for buffering delay */ + if( !p_sys->p_input->p->p_sout || + !p_sys->p_input->p->b_out_pace_control ) + i_delay = EsOutGetBuffering( out ); + else + i_delay = 0; + + i_time -= i_delay; + if( i_time < 0 ) + i_time = 0; + + if( i_length > 0 ) + f_position -= (double)i_delay / i_length; + if( f_position < 0 ) + f_position = 0; + + input_SendEventPosition( p_sys->p_input, f_position, i_time ); + } + return VLC_SUCCESS; + } + case ES_OUT_SET_JITTER: + { + mtime_t i_pts_delay = (mtime_t)va_arg( args, mtime_t ); + mtime_t i_pts_jitter = (mtime_t)va_arg( args, mtime_t ); + int i_cr_average = (int)va_arg( args, int ); + + bool b_change_clock = + i_pts_delay + i_pts_jitter != p_sys->i_pts_delay || + i_cr_average != p_sys->i_cr_average; + + assert( i_pts_jitter >= 0 ); + p_sys->i_pts_delay = i_pts_delay + i_pts_jitter; + p_sys->i_pts_jitter = i_pts_jitter; + p_sys->i_cr_average = i_cr_average; + + for( int i = 0; i < p_sys->i_pgrm && b_change_clock; i++ ) + input_clock_SetJitter( p_sys->pgrm[i]->p_clock, + i_pts_delay + i_pts_jitter, i_cr_average ); + return VLC_SUCCESS; + } + + case ES_OUT_GET_PCR_SYSTEM: + { + if( p_sys->b_buffering ) + return VLC_EGENERIC; + + es_out_pgrm_t *p_pgrm = p_sys->p_pgrm; + if( !p_pgrm ) + return VLC_EGENERIC; + + mtime_t *pi_system = va_arg( args, mtime_t *); + mtime_t *pi_delay = va_arg( args, mtime_t *); + input_clock_GetSystemOrigin( p_pgrm->p_clock, pi_system, pi_delay ); + return VLC_SUCCESS; + } + + case ES_OUT_MODIFY_PCR_SYSTEM: + { + if( p_sys->b_buffering ) + return VLC_EGENERIC; + + es_out_pgrm_t *p_pgrm = p_sys->p_pgrm; + if( !p_pgrm ) + return VLC_EGENERIC; + + const bool b_absolute = va_arg( args, int ); + const mtime_t i_system = va_arg( args, mtime_t ); + input_clock_ChangeSystemOrigin( p_pgrm->p_clock, b_absolute, i_system ); + return VLC_SUCCESS; + } + default: msg_Err( p_sys->p_input, "unknown query in es_out_Control" ); return VLC_EGENERIC; } } +static int EsOutControl( es_out_t *out, int i_query, va_list args ) +{ + es_out_sys_t *p_sys = out->p_sys; + int i_ret; + + vlc_mutex_lock( &p_sys->lock ); + i_ret = EsOutControlLocked( out, i_query, args ); + vlc_mutex_unlock( &p_sys->lock ); + + return i_ret; +} /**************************************************************************** * LanguageGetName: try to expend iso639 into plain name @@ -2032,7 +2712,7 @@ static char *LanguageGetName( const char *psz_code ) { const iso639_lang_t *pl; - if( psz_code == NULL ) + if( psz_code == NULL || !strcmp( psz_code, "und" ) ) { return strdup( "" ); } @@ -2076,7 +2756,7 @@ static char *LanguageGetCode( const char *psz_lang ) if( psz_lang == NULL || *psz_lang == '\0' ) return strdup("??"); - for( pl = p_languages; pl->psz_iso639_1 != NULL; pl++ ) + for( pl = p_languages; pl->psz_eng_name != NULL; pl++ ) { if( !strcasecmp( pl->psz_eng_name, psz_lang ) || !strcasecmp( pl->psz_native_name, psz_lang ) || @@ -2086,13 +2766,13 @@ static char *LanguageGetCode( const char *psz_lang ) break; } - if( pl->psz_iso639_1 != NULL ) + if( pl->psz_eng_name != NULL ) return strdup( pl->psz_iso639_1 ); return strdup("??"); } -static char **LanguageSplit( const char *psz_langs ) +static char **LanguageSplit( const char *psz_langs, bool b_default_any ) { char *psz_dup; char *psz_parser; @@ -2115,6 +2795,10 @@ static char **LanguageSplit( const char *psz_langs ) { TAB_APPEND( i_psz, ppsz, strdup("any") ); } + else if( !strcmp( psz_parser, "none" ) ) + { + TAB_APPEND( i_psz, ppsz, strdup("none") ); + } else { psz_code = LanguageGetCode( psz_parser ); @@ -2133,6 +2817,8 @@ static char **LanguageSplit( const char *psz_langs ) if( i_psz ) { + if( b_default_any && strcmp( ppsz[i_psz - 1], "none" ) ) + TAB_APPEND( i_psz, ppsz, strdup("any") ); TAB_APPEND( i_psz, ppsz, NULL ); } @@ -2150,102 +2836,176 @@ static int LanguageArrayIndex( char **ppsz_langs, char *psz_lang ) { if( !strcasecmp( ppsz_langs[i], psz_lang ) || !strcasecmp( ppsz_langs[i], "any" ) ) - { return i; - } + if( !strcasecmp( ppsz_langs[i], "none" ) ) + break; } return -1; } /**************************************************************************** - * EsOutAddInfo: + * EsOutUpdateInfo: * - add meta info to the playlist item ****************************************************************************/ -static void EsOutAddInfo( es_out_t *out, es_out_id_t *es ) +static void EsOutUpdateInfo( es_out_t *out, es_out_id_t *es, const es_format_t *fmt, const vlc_meta_t *p_meta ) { es_out_sys_t *p_sys = out->p_sys; input_thread_t *p_input = p_sys->p_input; - es_format_t *fmt = &es->fmt; - char *psz_cat; + const es_format_t *p_fmt_es = &es->fmt; lldiv_t div; - /* Add stream info */ - if( asprintf( &psz_cat, _("Stream %d"), out->p_sys->i_id - 1 ) == -1 ) + /* Create category */ + char psz_cat[128]; + snprintf( psz_cat, sizeof(psz_cat),_("Stream %d"), es->i_meta_id ); + info_category_t *p_cat = info_category_New( psz_cat ); + if( !p_cat ) return; - input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Codec"), - "%.4s", (char*)&fmt->i_codec ); + /* Add information */ + const char *psz_type; + switch( fmt->i_cat ) + { + case AUDIO_ES: + psz_type = _("Audio"); + break; + case VIDEO_ES: + psz_type = _("Video"); + break; + case SPU_ES: + psz_type = _("Subtitle"); + break; + default: + psz_type = NULL; + break; + } + + if( psz_type ) + info_category_AddInfo( p_cat, _("Type"), "%s", psz_type ); - input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Language"), - "%s", es->psz_language ); + if( es->i_meta_id != es->i_id ) + info_category_AddInfo( p_cat, _("Original ID"), + "%d", es->i_id ); + + const char *psz_codec_description = + vlc_fourcc_GetDescription( p_fmt_es->i_cat, p_fmt_es->i_codec ); + const vlc_fourcc_t i_codec_fourcc = ( p_fmt_es->i_original_fourcc )? + p_fmt_es->i_original_fourcc : p_fmt_es->i_codec; + if( psz_codec_description ) + info_category_AddInfo( p_cat, _("Codec"), "%s (%.4s)", + psz_codec_description, (char*)&i_codec_fourcc ); + else + info_category_AddInfo( p_cat, _("Codec"), "%.4s", + (char*)&i_codec_fourcc ); + + if( es->psz_language && *es->psz_language ) + info_category_AddInfo( p_cat, _("Language"), "%s", + es->psz_language ); + if( fmt->psz_description && *fmt->psz_description ) + info_category_AddInfo( p_cat, _("Description"), "%s", + fmt->psz_description ); - /* Add information */ switch( fmt->i_cat ) { case AUDIO_ES: - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Type"), _("Audio") ); + info_category_AddInfo( p_cat, _("Type"), _("Audio") ); - if( fmt->audio.i_channels > 0 ) - input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Channels"), - "%u", fmt->audio.i_channels ); + if( fmt->audio.i_physical_channels & AOUT_CHAN_PHYSMASK ) + info_category_AddInfo( p_cat, _("Channels"), "%s", + _( aout_FormatPrintChannels( &fmt->audio ) ) ); + else if( fmt->audio.i_channels > 0 ) + info_category_AddInfo( p_cat, _("Channels"), "%u", + fmt->audio.i_channels ); if( fmt->audio.i_rate > 0 ) { - input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Sample rate"), - _("%u Hz"), fmt->audio.i_rate ); + info_category_AddInfo( p_cat, _("Sample rate"), _("%u Hz"), + fmt->audio.i_rate ); + /* FIXME that should be removed or improved ! (used by text/strings.c) */ var_SetInteger( p_input, "sample-rate", fmt->audio.i_rate ); } - if( fmt->audio.i_bitspersample > 0 ) - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Bits per sample"), "%u", - fmt->audio.i_bitspersample ); + unsigned int i_bitspersample = fmt->audio.i_bitspersample; + if( i_bitspersample <= 0 ) + i_bitspersample = aout_BitsPerSample( p_fmt_es->i_codec ); + if( i_bitspersample > 0 ) + info_category_AddInfo( p_cat, _("Bits per sample"), "%u", + i_bitspersample ); if( fmt->i_bitrate > 0 ) { - input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Bitrate"), - _("%u kb/s"), fmt->i_bitrate / 1000 ); + info_category_AddInfo( p_cat, _("Bitrate"), _("%u kb/s"), + fmt->i_bitrate / 1000 ); + /* FIXME that should be removed or improved ! (used by text/strings.c) */ var_SetInteger( p_input, "bit-rate", fmt->i_bitrate ); } + for( int i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ ) + { + const audio_replay_gain_t *p_rg = &fmt->audio_replay_gain; + if( !p_rg->pb_gain[i] ) + continue; + const char *psz_name; + if( i == AUDIO_REPLAY_GAIN_TRACK ) + psz_name = _("Track replay gain"); + else + psz_name = _("Album replay gain"); + info_category_AddInfo( p_cat, psz_name, _("%.2f dB"), + p_rg->pf_gain[i] ); + } break; case VIDEO_ES: - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Type"), _("Video") ); + info_category_AddInfo( p_cat, _("Type"), _("Video") ); if( fmt->video.i_width > 0 && fmt->video.i_height > 0 ) - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Resolution"), "%ux%u", - fmt->video.i_width, fmt->video.i_height ); + info_category_AddInfo( p_cat, _("Resolution"), "%ux%u", + fmt->video.i_width, fmt->video.i_height ); if( fmt->video.i_visible_width > 0 && fmt->video.i_visible_height > 0 ) - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Display resolution"), "%ux%u", - fmt->video.i_visible_width, - fmt->video.i_visible_height); + info_category_AddInfo( p_cat, _("Display resolution"), "%ux%u", + fmt->video.i_visible_width, + fmt->video.i_visible_height); if( fmt->video.i_frame_rate > 0 && fmt->video.i_frame_rate_base > 0 ) { div = lldiv( (float)fmt->video.i_frame_rate / fmt->video.i_frame_rate_base * 1000000, 1000000 ); - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Frame rate"), "%"PRId64".%06u", - div.quot, (unsigned int )div.rem ); + if( div.rem > 0 ) + info_category_AddInfo( p_cat, _("Frame rate"), "%"PRId64".%06u", + div.quot, (unsigned int )div.rem ); + else + info_category_AddInfo( p_cat, _("Frame rate"), "%"PRId64, + div.quot ); } break; case SPU_ES: - input_Control( p_input, INPUT_ADD_INFO, psz_cat, - _("Type"), _("Subtitle") ); + info_category_AddInfo( p_cat, _("Type"), _("Subtitle") ); break; default: break; } - free( psz_cat ); + /* Append generic meta */ + if( p_meta ) + { + char **ppsz_all_keys = vlc_meta_CopyExtraNames( p_meta ); + for( int i = 0; ppsz_all_keys && ppsz_all_keys[i]; i++ ) + { + char *psz_key = ppsz_all_keys[i]; + const char *psz_value = vlc_meta_GetExtra( p_meta, psz_key ); + + if( psz_value ) + info_category_AddInfo( p_cat, vlc_gettext(psz_key), "%s", + vlc_gettext(psz_value) ); + free( psz_key ); + } + free( ppsz_all_keys ); + } + /* */ + input_Control( p_input, INPUT_REPLACE_INFOS, p_cat ); } +