From cc0cfb8b1cbd757a3591c38e66f7bc4835120a4e Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 26 Jun 2019 13:35:06 +0200 Subject: [PATCH 01/15] input: rename/move input_create_option Rename it to input_type, and pass it to the es_out. --- src/input/es_out.c | 33 +++++++++++-------- src/input/es_out.h | 3 +- src/input/input.c | 67 ++++++++++++++++++-------------------- src/input/input_internal.h | 10 ++++-- src/input/var.c | 2 +- 5 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/input/es_out.c b/src/input/es_out.c index 07e33647af..78c0bb5054 100644 --- a/src/input/es_out.c +++ b/src/input/es_out.c @@ -161,6 +161,7 @@ typedef struct typedef struct { input_thread_t *p_input; + enum input_type input_type; /* */ vlc_mutex_t lock; @@ -436,7 +437,7 @@ static void EsOutPropsCleanup( es_out_es_props_t *p_props ) static void EsOutPropsInit( es_out_es_props_t *p_props, bool autoselect, - input_thread_t *p_input, + input_thread_t *p_input, enum input_type input_type, enum es_out_policy_e e_default_policy, const char *psz_trackidvar, const char *psz_trackvar, @@ -451,7 +452,7 @@ static void EsOutPropsInit( es_out_es_props_t *p_props, p_props->i_demux_id = -1; p_props->p_main_es = NULL; - if( !input_priv(p_input)->b_preparsing && psz_langvar ) + if( input_type != INPUT_TYPE_PREPARSING && psz_langvar ) { char *psz_string = var_GetString( p_input, psz_langvar ); p_props->ppsz_language = LanguageSplit( psz_string ); @@ -470,7 +471,8 @@ static const struct es_out_callbacks es_out_cbs; /***************************************************************************** * input_EsOutNew: *****************************************************************************/ -es_out_t *input_EsOutNew( input_thread_t *p_input, float rate ) +es_out_t *input_EsOutNew( input_thread_t *p_input, float rate, + enum input_type input_type ) { es_out_sys_t *p_sys = calloc( 1, sizeof( *p_sys ) ); if( !p_sys ) @@ -483,17 +485,21 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, float rate ) p_sys->b_active = false; p_sys->i_mode = ES_OUT_MODE_NONE; + p_sys->input_type = input_type; vlc_list_init(&p_sys->programs); vlc_list_init(&p_sys->es); vlc_list_init(&p_sys->es_slaves); /* */ - EsOutPropsInit( &p_sys->video, true, p_input, ES_OUT_ES_POLICY_EXCLUSIVE, + EsOutPropsInit( &p_sys->video, true, p_input, input_type, + ES_OUT_ES_POLICY_EXCLUSIVE, "video-track-id", "video-track", NULL, NULL ); - EsOutPropsInit( &p_sys->audio, true, p_input, ES_OUT_ES_POLICY_EXCLUSIVE, + EsOutPropsInit( &p_sys->audio, true, p_input, input_type, + ES_OUT_ES_POLICY_EXCLUSIVE, "audio-track-id", "audio-track", "audio-language", "audio" ); - EsOutPropsInit( &p_sys->sub, false, p_input, ES_OUT_ES_POLICY_EXCLUSIVE, + EsOutPropsInit( &p_sys->sub, false, p_input, input_type, + ES_OUT_ES_POLICY_EXCLUSIVE, "sub-track-id", "sub-track", "sub-language", "sub" ); p_sys->i_group_id = var_GetInteger( p_input, "program" ); @@ -1593,7 +1599,7 @@ static void EsOutProgramMeta( es_out_t *out, int i_group, const vlc_meta_t *p_me input_item_MergeInfos( p_item, p_cat ); b_has_new_infos = true; } - if( !input_priv(p_input)->b_preparsing && b_has_new_infos ) + if( p_sys->input_type != INPUT_TYPE_PREPARSING && b_has_new_infos ) input_SendEventMetaInfo( p_input ); } @@ -1686,7 +1692,7 @@ static void EsOutProgramEpg( es_out_t *out, int i_group, const vlc_epg_t *p_epg else ret = input_item_DelInfo( p_item, psz_cat, now_playing_tr ); - if( ret == VLC_SUCCESS && !input_priv(p_input)->b_preparsing ) + if( ret == VLC_SUCCESS && p_sys->input_type != INPUT_TYPE_PREPARSING ) input_SendEventMetaInfo( p_input ); } @@ -1730,7 +1736,7 @@ static void EsOutProgramUpdateScrambled( es_out_t *p_out, es_out_pgrm_t *p_pgrm ret = input_item_DelInfo( p_item, psz_cat, _("Scrambled") ); free( psz_cat ); - if( ret == VLC_SUCCESS && !input_priv(p_input)->b_preparsing ) + if( ret == VLC_SUCCESS && p_sys->input_type != INPUT_TYPE_PREPARSING ) input_SendEventMetaInfo( p_input ); input_SendEventProgramScrambled( p_input, p_pgrm->i_id, b_scrambled ); } @@ -2018,7 +2024,8 @@ static void EsOutCreateDecoder( es_out_t *out, es_out_id_t *p_es ) input_thread_private_t *priv = input_priv(p_input); dec = input_DecoderNew( VLC_OBJECT(p_input), &p_es->fmt, p_es->p_clock, priv->p_resource, priv->p_sout, - priv->b_thumbnailing, &decoder_cbs, p_es ); + p_sys->input_type == INPUT_TYPE_THUMBNAILING, + &decoder_cbs, p_es ); if( dec != NULL ) { input_DecoderChangeRate( dec, p_sys->rate ); @@ -2076,7 +2083,7 @@ static void EsOutSelectEs( es_out_t *out, es_out_id_t *es ) { es_out_sys_t *p_sys = container_of(out, es_out_sys_t, out); input_thread_t *p_input = p_sys->p_input; - bool b_thumbnailing = input_priv(p_input)->b_thumbnailing; + bool b_thumbnailing = p_sys->input_type == INPUT_TYPE_THUMBNAILING; if( EsIsSelected( es ) ) { @@ -3989,7 +3996,7 @@ static void EsOutUpdateInfo( es_out_t *out, es_out_id_t *es, const vlc_meta_t *p } /* */ input_item_ReplaceInfos( p_item, p_cat ); - if( !input_priv(p_input)->b_preparsing ) + if( p_sys->input_type != INPUT_TYPE_PREPARSING ) input_SendEventMetaInfo( p_input ); } @@ -4005,7 +4012,7 @@ static void EsOutDeleteInfoEs( es_out_t *out, es_out_id_t *es ) int ret = input_item_DelInfo( p_item, psz_info_category, NULL ); free( psz_info_category ); - if( ret == VLC_SUCCESS && !input_priv(p_input)->b_preparsing ) + if( ret == VLC_SUCCESS && p_sys->input_type != INPUT_TYPE_PREPARSING ) input_SendEventMetaInfo( p_input ); } } diff --git a/src/input/es_out.h b/src/input/es_out.h index 30e199579e..f2d2343362 100644 --- a/src/input/es_out.h +++ b/src/input/es_out.h @@ -174,7 +174,8 @@ static inline void es_out_Eos( es_out_t *p_out ) assert( !i_ret ); } -es_out_t *input_EsOutNew( input_thread_t *, float rate ); +es_out_t *input_EsOutNew( input_thread_t *, float rate, + enum input_type input_type ); es_out_t *input_EsOutTimeshiftNew( input_thread_t *, es_out_t *, float i_rate ); es_out_id_t *vlc_es_id_get_out(vlc_es_id_t *id); diff --git a/src/input/input.c b/src/input/input.c index 9d0bc94e78..c8457fc41c 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -58,17 +58,11 @@ /***************************************************************************** * Local prototypes *****************************************************************************/ -enum input_create_option { - INPUT_CREATE_OPTION_NONE, - INPUT_CREATE_OPTION_PREPARSING, - INPUT_CREATE_OPTION_THUMBNAILING, -}; - static void *Run( void * ); static void *Preparse( void * ); static input_thread_t * Create ( vlc_object_t *, input_thread_events_cb, void *, - input_item_t *, enum input_create_option option, + input_item_t *, enum input_type type, input_resource_t *, vlc_renderer_item_t * ); static void Destroy ( input_thread_t *p_input ); static int Init ( input_thread_t *p_input ); @@ -137,7 +131,7 @@ input_thread_t *input_Create( vlc_object_t *p_parent, vlc_renderer_item_t *p_renderer ) { return Create( p_parent, events_cb, events_data, p_item, - INPUT_CREATE_OPTION_NONE, p_resource, p_renderer ); + INPUT_TYPE_NONE, p_resource, p_renderer ); } input_thread_t *input_CreatePreparser( vlc_object_t *parent, @@ -145,7 +139,7 @@ input_thread_t *input_CreatePreparser( vlc_object_t *parent, void *events_data, input_item_t *item ) { return Create( parent, events_cb, events_data, item, - INPUT_CREATE_OPTION_PREPARSING, NULL, NULL ); + INPUT_TYPE_PREPARSING, NULL, NULL ); } input_thread_t *input_CreateThumbnailer(vlc_object_t *obj, @@ -153,7 +147,7 @@ input_thread_t *input_CreateThumbnailer(vlc_object_t *obj, void *events_data, input_item_t *item) { return Create( obj, events_cb, events_data, item, - INPUT_CREATE_OPTION_THUMBNAILING, NULL, NULL ); + INPUT_TYPE_THUMBNAILING, NULL, NULL ); } /** @@ -168,7 +162,7 @@ int input_Start( input_thread_t *p_input ) input_thread_private_t *priv = input_priv(p_input); void *(*func)(void *) = Run; - if( priv->b_preparsing ) + if( priv->type == INPUT_TYPE_PREPARSING ) func = Preparse; assert( !priv->is_running ); @@ -257,8 +251,7 @@ input_item_t *input_GetItem( input_thread_t *p_input ) *****************************************************************************/ static input_thread_t *Create( vlc_object_t *p_parent, input_thread_events_cb events_cb, void *events_data, - input_item_t *p_item, - enum input_create_option option, + input_item_t *p_item, enum input_type type, input_resource_t *p_resource, vlc_renderer_item_t *p_renderer ) { @@ -272,20 +265,20 @@ static input_thread_t *Create( vlc_object_t *p_parent, input_thread_t *p_input = &priv->input; char * psz_name = input_item_GetName( p_item ); - const char *option_str; - switch (option) + const char *type_str; + switch (type) { - case INPUT_CREATE_OPTION_PREPARSING: - option_str = "preparsing "; + case INPUT_TYPE_PREPARSING: + type_str = "preparsing "; break; - case INPUT_CREATE_OPTION_THUMBNAILING: - option_str = "thumbnailing "; + case INPUT_TYPE_THUMBNAILING: + type_str = "thumbnailing "; break; default: - option_str = ""; + type_str = ""; break; } - msg_Dbg( p_input, "Creating an input for %s'%s'", option_str, psz_name); + msg_Dbg( p_input, "Creating an input for %s'%s'", type_str, psz_name); free( psz_name ); /* Parse input options */ @@ -294,8 +287,7 @@ static input_thread_t *Create( vlc_object_t *p_parent, /* Init Common fields */ priv->events_cb = events_cb; priv->events_data = events_data; - priv->b_preparsing = option == INPUT_CREATE_OPTION_PREPARSING; - priv->b_thumbnailing = option == INPUT_CREATE_OPTION_THUMBNAILING; + priv->type = type; priv->b_can_pace_control = true; priv->i_start = 0; priv->i_time = 0; @@ -309,8 +301,8 @@ static input_thread_t *Create( vlc_object_t *p_parent, TAB_INIT( priv->i_attachment, priv->attachment ); priv->attachment_demux = NULL; priv->p_sout = NULL; - priv->b_out_pace_control = priv->b_thumbnailing; - priv->p_renderer = p_renderer && priv->b_preparsing == false ? + priv->b_out_pace_control = priv->type == INPUT_TYPE_THUMBNAILING; + priv->p_renderer = p_renderer && priv->type != INPUT_TYPE_PREPARSING ? vlc_renderer_item_hold( p_renderer ) : NULL; priv->viewpoint_changed = false; @@ -335,7 +327,8 @@ static input_thread_t *Create( vlc_object_t *p_parent, /* setup the preparse depth of the item * if we are preparsing, use the i_preparse_depth of the parent item */ - if( priv->b_preparsing || priv->b_thumbnailing ) + if( priv->type == INPUT_TYPE_PREPARSING + || priv->type == INPUT_TYPE_THUMBNAILING ) { p_input->obj.logger = NULL; p_input->obj.no_interact = true; @@ -395,12 +388,12 @@ static input_thread_t *Create( vlc_object_t *p_parent, input_item_SetESNowPlaying( p_item, NULL ); /* */ - if( !priv->b_preparsing && var_InheritBool( p_input, "stats" ) ) + if( priv->type != INPUT_TYPE_PREPARSING && var_InheritBool( p_input, "stats" ) ) priv->stats = input_stats_Create(); else priv->stats = NULL; - priv->p_es_out_display = input_EsOutNew( p_input, priv->rate ); + priv->p_es_out_display = input_EsOutNew( p_input, priv->rate, priv->type ); priv->p_es_out = NULL; return p_input; @@ -774,7 +767,7 @@ static int InitSout( input_thread_t * p_input ) { input_thread_private_t *priv = input_priv(p_input); - if( priv->b_preparsing ) + if( priv->type == INPUT_TYPE_PREPARSING ) return VLC_SUCCESS; /* Find a usable sout and attach it to p_input */ @@ -819,7 +812,7 @@ static void InitTitle( input_thread_t * p_input, bool had_titles ) input_thread_private_t *priv = input_priv(p_input); input_source_t *p_master = priv->master; - if( priv->b_preparsing ) + if( priv->type == INPUT_TYPE_PREPARSING ) return; vlc_mutex_lock( &priv->p_item->lock ); @@ -1276,7 +1269,7 @@ static int Init( input_thread_t * p_input ) input_SendEventPosition( p_input, 0.0, 0 ); - if( !priv->b_preparsing ) + if( priv->type != INPUT_TYPE_PREPARSING ) { StartTitle( p_input ); SetSubtitlesOptions( p_input ); @@ -1291,7 +1284,7 @@ static int Init( input_thread_t * p_input ) } } - if( !priv->b_preparsing && priv->p_sout ) + if( priv->type != INPUT_TYPE_PREPARSING && priv->p_sout ) { priv->b_out_pace_control = priv->p_sout->i_out_pace_nocontrol > 0; @@ -2413,7 +2406,8 @@ static demux_t *InputDemuxNew( input_thread_t *p_input, /* create the underlying access stream */ stream_t *p_stream = stream_AccessNew( obj, p_input, priv->p_es_out, - priv->b_preparsing, url ); + priv->type == INPUT_TYPE_PREPARSING, + url ); if( p_stream == NULL ) return NULL; @@ -2446,7 +2440,8 @@ static demux_t *InputDemuxNew( input_thread_t *p_input, /* create a regular demux with the access stream created */ demux_t *demux = demux_NewAdvanced( obj, p_input, psz_demux, url, p_stream, - priv->p_es_out, priv->b_preparsing ); + priv->p_es_out, + priv->type == INPUT_TYPE_PREPARSING ); if( demux != NULL ) return demux; @@ -2650,8 +2645,8 @@ static input_source_t *InputSourceNew( input_thread_t *p_input, input_SendEventCapabilities( p_input, capabilites ); /* get attachment - * FIXME improve for b_preparsing: move it after GET_META and check psz_arturl */ - if( !input_priv(p_input)->b_preparsing ) + * FIXME improve for preparsing: move it after GET_META and check psz_arturl */ + if( input_priv(p_input)->type != INPUT_TYPE_PREPARSING ) { if( demux_Control( in->p_demux, DEMUX_GET_TITLE_INFO, &in->title, &in->i_title, diff --git a/src/input/input_internal.h b/src/input/input_internal.h index b0bc7b6d16..ce0700b002 100644 --- a/src/input/input_internal.h +++ b/src/input/input_internal.h @@ -53,6 +53,12 @@ typedef struct input_thread_t * Input events and variables *****************************************************************************/ +enum input_type { + INPUT_TYPE_NONE, + INPUT_TYPE_PREPARSING, + INPUT_TYPE_THUMBNAILING, +}; + /** * Input state * @@ -431,8 +437,9 @@ typedef struct input_thread_private_t input_thread_events_cb events_cb; void *events_data; + enum input_type type; + /* Global properties */ - bool b_preparsing; bool b_can_pause; bool b_can_rate_control; bool b_can_pace_control; @@ -442,7 +449,6 @@ typedef struct input_thread_private_t bool is_running; bool is_stopped; bool b_recording; - bool b_thumbnailing; float rate; /* Playtime configuration and state */ diff --git a/src/input/var.c b/src/input/var.c index 9f119a838d..35749e83da 100644 --- a/src/input/var.c +++ b/src/input/var.c @@ -40,7 +40,7 @@ void input_ConfigVarInit ( input_thread_t *p_input ) { /* Create Object Variables for private use only */ - if( !input_priv(p_input)->b_preparsing ) + if( input_priv(p_input)->type != INPUT_TYPE_PREPARSING ) { var_Create( p_input, "video", VLC_VAR_BOOL | VLC_VAR_DOINHERIT ); var_Create( p_input, "audio", VLC_VAR_BOOL | VLC_VAR_DOINHERIT ); -- GitLab From 64d137feb35bc565631c7a34065df6c5a3ecb0b0 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 26 Jun 2019 14:48:21 +0200 Subject: [PATCH 02/15] input: merge all Create functions They are all internal now, it's ok to require unused arguments for a given type. --- src/input/input.c | 67 +++++++++----------------------------- src/input/input_internal.h | 28 ++-------------- src/input/item.c | 4 +-- src/input/player.c | 3 +- src/input/thumbnailer.c | 6 ++-- 5 files changed, 26 insertions(+), 82 deletions(-) diff --git a/src/input/input.c b/src/input/input.c index c8457fc41c..4dd8ef3597 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -61,9 +61,6 @@ static void *Run( void * ); static void *Preparse( void * ); -static input_thread_t * Create ( vlc_object_t *, input_thread_events_cb, void *, - input_item_t *, enum input_type type, - input_resource_t *, vlc_renderer_item_t * ); static void Destroy ( input_thread_t *p_input ); static int Init ( input_thread_t *p_input ); static void End ( input_thread_t *p_input ); @@ -112,44 +109,6 @@ static int input_SlaveSourceAdd( input_thread_t *, enum slave_type, static char *input_SubtitleFile2Uri( input_thread_t *, const char * ); static void input_ChangeState( input_thread_t *p_input, int i_state ); /* TODO fix name */ -#undef input_Create -/** - * Create a new input_thread_t. - * - * You need to call input_Start on it when you are done - * adding callback on the variables/events you want to monitor. - * - * \param p_parent a vlc_object - * \param p_item an input item - * \param p_resource an optional input ressource - * \return a pointer to the spawned input thread - */ -input_thread_t *input_Create( vlc_object_t *p_parent, - input_thread_events_cb events_cb, void *events_data, - input_item_t *p_item, - input_resource_t *p_resource, - vlc_renderer_item_t *p_renderer ) -{ - return Create( p_parent, events_cb, events_data, p_item, - INPUT_TYPE_NONE, p_resource, p_renderer ); -} - -input_thread_t *input_CreatePreparser( vlc_object_t *parent, - input_thread_events_cb events_cb, - void *events_data, input_item_t *item ) -{ - return Create( parent, events_cb, events_data, item, - INPUT_TYPE_PREPARSING, NULL, NULL ); -} - -input_thread_t *input_CreateThumbnailer(vlc_object_t *obj, - input_thread_events_cb events_cb, - void *events_data, input_item_t *item) -{ - return Create( obj, events_cb, events_data, item, - INPUT_TYPE_THUMBNAILING, NULL, NULL ); -} - /** * Start a input_thread_t created by input_Create. * @@ -243,17 +202,23 @@ input_item_t *input_GetItem( input_thread_t *p_input ) return input_priv(p_input)->p_item; } -/***************************************************************************** - * This function creates a new input, and returns a pointer - * to its description. On error, it returns NULL. +#undef input_Create +/** + * Create a new input_thread_t. * - * XXX Do not forget to update vlc_input.h if you add new variables. - *****************************************************************************/ -static input_thread_t *Create( vlc_object_t *p_parent, - input_thread_events_cb events_cb, void *events_data, - input_item_t *p_item, enum input_type type, - input_resource_t *p_resource, - vlc_renderer_item_t *p_renderer ) + * You need to call input_Start on it when you are done + * adding callback on the variables/events you want to monitor. + * + * \param p_parent a vlc_object + * \param p_item an input item + * \param p_resource an optional input ressource + * \return a pointer to the spawned input thread + */ +input_thread_t *input_Create( vlc_object_t *p_parent, + input_thread_events_cb events_cb, void *events_data, + input_item_t *p_item, enum input_type type, + input_resource_t *p_resource, + vlc_renderer_item_t *p_renderer ) { /* Allocate descriptor */ input_thread_private_t *priv; diff --git a/src/input/input_internal.h b/src/input/input_internal.h index ce0700b002..68b26bb870 100644 --- a/src/input/input_internal.h +++ b/src/input/input_internal.h @@ -293,31 +293,9 @@ typedef void (*input_thread_events_cb)( input_thread_t *input, *****************************************************************************/ input_thread_t * input_Create( vlc_object_t *p_parent, input_thread_events_cb event_cb, void *events_data, - input_item_t *, input_resource_t *, - vlc_renderer_item_t* p_renderer ) VLC_USED; -#define input_Create(a,b,c,d,e,f) input_Create(VLC_OBJECT(a),b,c,d,e,f) - - -/** - * Creates an item preparser. - * - * Creates an input thread to preparse an item. The input needs to be started - * with input_Start() afterwards. - * - * @param obj parent object - * @param item input item to preparse - * @return an input thread or NULL on error - */ -input_thread_t *input_CreatePreparser(vlc_object_t *obj, - input_thread_events_cb events_cb, - void *events_data, input_item_t *item) -VLC_USED; - -VLC_API -input_thread_t *input_CreateThumbnailer(vlc_object_t *obj, - input_thread_events_cb events_cb, - void *events_data, input_item_t *item) -VLC_USED; + input_item_t *, enum input_type type, + input_resource_t *, vlc_renderer_item_t* p_renderer ) VLC_USED; +#define input_Create(a,b,c,d,e,f,g) input_Create(VLC_OBJECT(a),b,c,d,e,f,g) int input_Start( input_thread_t * ); diff --git a/src/input/item.c b/src/input/item.c index b1b92cdf9b..27db5a026e 100644 --- a/src/input/item.c +++ b/src/input/item.c @@ -1392,8 +1392,8 @@ input_item_Parse(input_item_t *item, vlc_object_t *obj, parser->state = INIT_S; parser->cbs = cbs; parser->userdata = userdata; - parser->input = input_CreatePreparser(obj, input_item_parser_InputEvent, - parser, item); + parser->input = input_Create(obj, input_item_parser_InputEvent, parser, + item, INPUT_TYPE_PREPARSING, NULL, NULL); if (!parser->input || input_Start(parser->input)) { if (parser->input) diff --git a/src/input/player.c b/src/input/player.c index 4d0e13c668..ab5ccb9b5d 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -688,7 +688,8 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item) input->abloop_state[0].set = input->abloop_state[1].set = false; input->thread = input_Create(player, input_thread_Events, input, item, - player->resource, player->renderer); + INPUT_TYPE_NONE, player->resource, + player->renderer); if (!input->thread) { free(input); diff --git a/src/input/thumbnailer.c b/src/input/thumbnailer.c index 524651364d..12622e3490 100644 --- a/src/input/thumbnailer.c +++ b/src/input/thumbnailer.c @@ -126,9 +126,9 @@ static int thumbnailer_request_Start( void* owner, void* entity, void** out ) vlc_thumbnailer_t* thumbnailer = owner; vlc_thumbnailer_request_t* request = entity; input_thread_t* input = request->input_thread = - input_CreateThumbnailer( thumbnailer->parent, - on_thumbnailer_input_event, request, - request->params.input_item ); + input_Create( thumbnailer->parent, on_thumbnailer_input_event, request, + request->params.input_item, INPUT_TYPE_THUMBNAILING, + NULL, NULL ); if ( unlikely( input == NULL ) ) { request->params.cb( request->params.user_data, NULL ); -- GitLab From 1089bb06064984a36d3f8baa09e4fe827c8aba2e Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 16 May 2019 16:27:45 +0200 Subject: [PATCH 03/15] input: let the owner delete the input node This will allow input owner to process the input node later. --- include/vlc_input_item.h | 3 ++- modules/misc/medialibrary/MetadataExtractor.cpp | 1 + modules/misc/medialibrary/fs/directory.cpp | 1 + src/input/es_out.c | 1 - src/input/player.c | 1 + src/preparser/preparser.c | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/vlc_input_item.h b/include/vlc_input_item.h index 492a442036..08807baf59 100644 --- a/include/vlc_input_item.h +++ b/include/vlc_input_item.h @@ -411,7 +411,8 @@ typedef struct input_item_parser_cbs_t * @note This callback is optional. * * @param item the parsed item - * @param subtree sub items of the current item + * @param subtree valid sub items of the current item, to release with + * input_item_node_Delete() * @param userdata user data set by input_item_Parse() */ void (*on_subtree_added)(input_item_t *item, input_item_node_t *subtree, void *userdata); diff --git a/modules/misc/medialibrary/MetadataExtractor.cpp b/modules/misc/medialibrary/MetadataExtractor.cpp index a9c714da1a..4aadfb6c1e 100644 --- a/modules/misc/medialibrary/MetadataExtractor.cpp +++ b/modules/misc/medialibrary/MetadataExtractor.cpp @@ -124,6 +124,7 @@ void MetadataExtractor::onParserSubtreeAdded( input_item_t *, { auto* ctx = static_cast( data ); ctx->mde->addSubtree( *ctx, subtree ); + input_item_node_Delete( subtree ); } void MetadataExtractor::addSubtree( ParseContext& ctx, input_item_node_t *root ) diff --git a/modules/misc/medialibrary/fs/directory.cpp b/modules/misc/medialibrary/fs/directory.cpp index 2e66e8311e..8b900644f5 100644 --- a/modules/misc/medialibrary/fs/directory.cpp +++ b/modules/misc/medialibrary/fs/directory.cpp @@ -116,6 +116,7 @@ static void onParserSubtreeAdded( input_item_t *, input_item_node_t *subtree, input_item_t *media = child->p_item; req->children->emplace_back( media ); } + input_item_node_Delete( subtree ); } } /* extern C */ diff --git a/src/input/es_out.c b/src/input/es_out.c index 78c0bb5054..d955f1ec7b 100644 --- a/src/input/es_out.c +++ b/src/input/es_out.c @@ -3389,7 +3389,6 @@ static int EsOutVaControlLocked( es_out_t *out, int i_query, va_list args ) input_thread_t *input = p_sys->p_input; input_item_node_t *node = va_arg(args, input_item_node_t *); input_SendEventParsing(input, node); - input_item_node_Delete(node); return VLC_SUCCESS; } diff --git a/src/input/player.c b/src/input/player.c index ab5ccb9b5d..5956b48812 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -2265,6 +2265,7 @@ input_thread_Events(input_thread_t *input_thread, case INPUT_EVENT_SUBITEMS: vlc_player_SendEvent(player, on_media_subitems_changed, input_GetItem(input->thread), event->subitems); + input_item_node_Delete(event->subitems); break; case INPUT_EVENT_DEAD: if (input->started) /* Can happen with early input_thread fails */ diff --git a/src/preparser/preparser.c b/src/preparser/preparser.c index f59519aef1..eda268a07a 100644 --- a/src/preparser/preparser.c +++ b/src/preparser/preparser.c @@ -108,6 +108,7 @@ static void OnParserSubtreeAdded(input_item_t *item, input_item_node_t *subtree, if (req->cbs && req->cbs->on_subtree_added) req->cbs->on_subtree_added(req->item, subtree, req->userdata); + input_item_node_Delete(subtree); } static int PreparserOpenInput( void* preparser_, void* req_, void** out ) -- GitLab From 38d6e10382880c2e26583fef329de031a04735d5 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 26 Jun 2019 14:00:52 +0200 Subject: [PATCH 04/15] decoder: pass input_type directly --- src/input/decoder.c | 27 +++++++++++++++------------ src/input/decoder.h | 2 +- src/input/es_out.c | 9 ++++----- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/input/decoder.c b/src/input/decoder.c index 782dc0a422..dd361cf7e8 100644 --- a/src/input/decoder.c +++ b/src/input/decoder.c @@ -46,6 +46,7 @@ #include "audio_output/aout_internal.h" #include "stream_output/stream_output.h" #include "../clock/clock.h" +#include "input_internal.h" #include "decoder.h" #include "resource.h" @@ -1767,13 +1768,15 @@ static const struct decoder_owner_callbacks dec_spu_cbs = static decoder_t * CreateDecoder( vlc_object_t *p_parent, const es_format_t *fmt, vlc_clock_t *p_clock, input_resource_t *p_resource, - sout_instance_t *p_sout, bool b_thumbnailing, + sout_instance_t *p_sout, enum input_type input_type, const struct input_decoder_callbacks *cbs, void *cbs_userdata ) { decoder_t *p_dec; struct decoder_owner *p_owner; + assert(input_type != INPUT_TYPE_PREPARSING); + p_owner = vlc_custom_create( p_parent, sizeof( *p_owner ), "decoder" ); if( p_owner == NULL ) return NULL; @@ -1856,10 +1859,10 @@ static decoder_t * CreateDecoder( vlc_object_t *p_parent, switch( fmt->i_cat ) { case VIDEO_ES: - if( !b_thumbnailing ) - p_dec->cbs = &dec_video_cbs; - else + if( input_type == INPUT_TYPE_THUMBNAILING ) p_dec->cbs = &dec_thumbnailer_cbs; + else + p_dec->cbs = &dec_video_cbs; p_owner->pf_update_stat = DecoderUpdateStatVideo; break; case AUDIO_ES: @@ -2020,7 +2023,7 @@ static void DecoderUnsupportedCodec( decoder_t *p_dec, const es_format_t *fmt, b /* TODO: pass p_sout through p_resource? -- Courmisch */ static decoder_t *decoder_New( vlc_object_t *p_parent, const es_format_t *fmt, vlc_clock_t *p_clock, input_resource_t *p_resource, - sout_instance_t *p_sout, bool thumbnailing, + sout_instance_t *p_sout, enum input_type input_type, const struct input_decoder_callbacks *cbs, void *userdata) { @@ -2030,7 +2033,7 @@ static decoder_t *decoder_New( vlc_object_t *p_parent, const es_format_t *fmt, /* Create the decoder configuration structure */ p_dec = CreateDecoder( p_parent, fmt, p_clock, p_resource, p_sout, - thumbnailing, cbs, userdata ); + input_type, cbs, userdata ); if( p_dec == NULL ) { msg_Err( p_parent, "could not create %s", psz_type ); @@ -2091,11 +2094,11 @@ static decoder_t *decoder_New( vlc_object_t *p_parent, const es_format_t *fmt, */ decoder_t *input_DecoderNew( vlc_object_t *parent, es_format_t *fmt, vlc_clock_t *p_clock, input_resource_t *resource, - sout_instance_t *p_sout, bool thumbnailing, + sout_instance_t *p_sout, enum input_type input_type, const struct input_decoder_callbacks *cbs, void *cbs_userdata) { - return decoder_New( parent, fmt, p_clock, resource, p_sout, thumbnailing, + return decoder_New( parent, fmt, p_clock, resource, p_sout, input_type, cbs, cbs_userdata ); } @@ -2105,8 +2108,8 @@ decoder_t *input_DecoderNew( vlc_object_t *parent, es_format_t *fmt, decoder_t *input_DecoderCreate( vlc_object_t *p_parent, const es_format_t *fmt, input_resource_t *p_resource ) { - return decoder_New( p_parent, fmt, NULL, p_resource, NULL, false, NULL, - NULL ); + return decoder_New( p_parent, fmt, NULL, p_resource, NULL, INPUT_TYPE_NONE, + NULL, NULL ); } @@ -2327,8 +2330,8 @@ int input_DecoderSetCcState( decoder_t *p_dec, vlc_fourcc_t codec, fmt.subs.cc.i_channel = i_channel; fmt.subs.cc.i_reorder_depth = p_owner->cc.desc.i_reorder_depth; p_cc = input_DecoderNew( VLC_OBJECT(p_dec), &fmt, p_owner->p_clock, - p_owner->p_resource, p_owner->p_sout, false, - NULL, NULL ); + p_owner->p_resource, p_owner->p_sout, + INPUT_TYPE_NONE, NULL, NULL ); if( !p_cc ) { msg_Err( p_dec, "could not create decoder" ); diff --git a/src/input/decoder.h b/src/input/decoder.h index 04b64ac713..49581e8fc6 100644 --- a/src/input/decoder.h +++ b/src/input/decoder.h @@ -52,7 +52,7 @@ struct input_decoder_callbacks { decoder_t *input_DecoderNew( vlc_object_t *parent, es_format_t *, vlc_clock_t *, input_resource_t *, sout_instance_t *, - bool thumbnailing, + enum input_type input_type, const struct input_decoder_callbacks *cbs, void *userdata ) VLC_USED; diff --git a/src/input/es_out.c b/src/input/es_out.c index d955f1ec7b..b0a79bcea4 100644 --- a/src/input/es_out.c +++ b/src/input/es_out.c @@ -811,7 +811,7 @@ static int EsOutSetRecord( es_out_t *out, bool b_record ) p_es->p_dec_record = input_DecoderNew( VLC_OBJECT(p_input), &p_es->fmt, NULL, input_priv(p_input)->p_resource, - p_sys->p_sout_record, false, + p_sys->p_sout_record, INPUT_TYPE_NONE, &decoder_cbs, p_es ); if( p_es->p_dec_record && p_sys->b_buffering ) @@ -2024,8 +2024,7 @@ static void EsOutCreateDecoder( es_out_t *out, es_out_id_t *p_es ) input_thread_private_t *priv = input_priv(p_input); dec = input_DecoderNew( VLC_OBJECT(p_input), &p_es->fmt, p_es->p_clock, priv->p_resource, priv->p_sout, - p_sys->input_type == INPUT_TYPE_THUMBNAILING, - &decoder_cbs, p_es ); + p_sys->input_type, &decoder_cbs, p_es ); if( dec != NULL ) { input_DecoderChangeRate( dec, p_sys->rate ); @@ -2037,8 +2036,8 @@ static void EsOutCreateDecoder( es_out_t *out, es_out_id_t *p_es ) { p_es->p_dec_record = input_DecoderNew( VLC_OBJECT(p_input), &p_es->fmt, NULL, - priv->p_resource, p_sys->p_sout_record, false, - &decoder_cbs, p_es ); + priv->p_resource, p_sys->p_sout_record, + INPUT_TYPE_NONE, &decoder_cbs, p_es ); if( p_es->p_dec_record && p_sys->b_buffering ) input_DecoderStartWait( p_es->p_dec_record ); } -- GitLab From ed5c4171d2f7a2e318c94509bb5604ce5e34643c Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 19 Mar 2019 09:58:18 +0100 Subject: [PATCH 05/15] aout: split aout_DecPlay --- src/audio_output/dec.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c index e0c9f32b7e..d9f371956c 100644 --- a/src/audio_output/dec.c +++ b/src/audio_output/dec.c @@ -393,6 +393,24 @@ void aout_RequestRetiming(audio_output_t *aout, vlc_tick_t system_ts, } } +static void aout_Play(audio_output_t *aout, block_t *block, + vlc_tick_t system_now, vlc_tick_t original_pts) +{ + aout_owner_t *owner = aout_owner (aout); + + /* Drift correction */ + aout_DecSynchronize(aout, system_now, original_pts); + + const vlc_tick_t play_date = + vlc_clock_ConvertToSystem(owner->sync.clock, system_now, original_pts, + owner->sync.rate); + /* Output */ + owner->sync.discontinuity = false; + aout->play(aout, block, play_date); + + atomic_fetch_add_explicit(&owner->buffers_played, 1, memory_order_relaxed); +} + /***************************************************************************** * aout_DecPlay : filter & mix the decoded buffer *****************************************************************************/ @@ -452,18 +470,8 @@ int aout_DecPlay(audio_output_t *aout, block_t *block) aout_DecSilence (aout, delta, block->i_pts); } - /* Drift correction */ vlc_tick_t system_now = vlc_tick_now(); - aout_DecSynchronize(aout, system_now, original_pts); - - const vlc_tick_t play_date = - vlc_clock_ConvertToSystem(owner->sync.clock, system_now, original_pts, - owner->sync.rate); - /* Output */ - owner->sync.discontinuity = false; - aout->play(aout, block, play_date); - - atomic_fetch_add_explicit(&owner->buffers_played, 1, memory_order_relaxed); + aout_Play(aout, block, system_now, original_pts); return ret; drop: owner->sync.discontinuity = true; -- GitLab From 29e273be0e805f12a3d0f98e389a776a76f51ecd Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 14 Mar 2019 13:40:15 +0100 Subject: [PATCH 06/15] aout: add aout_FormatEquals --- src/audio_output/dec.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c index d9f371956c..26d1d66b94 100644 --- a/src/audio_output/dec.c +++ b/src/audio_output/dec.c @@ -50,6 +50,21 @@ static void aout_Drain(audio_output_t *aout) } } +static bool aout_FormatEquals(const audio_sample_format_t *f1, + const audio_sample_format_t *f2) +{ + return f1->i_format == f2->i_format + && f1->i_rate == f2->i_rate + && f1->i_physical_channels == f2->i_physical_channels + && f1->i_chan_mode == f2->i_chan_mode + && f1->channel_type == f2->channel_type + && f1->i_bytes_per_frame == f2->i_bytes_per_frame + && f1->i_frame_length == f2->i_frame_length + && f1->i_bitspersample == f2->i_bitspersample + && f1->i_blockalign == f2->i_blockalign + && f1->i_channels == f2->i_channels; +} + /** * Creates an audio output */ -- GitLab From 46dcb9987b237de7f91eac7f89b83458a23c126f Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 14 Mar 2019 15:28:04 +0100 Subject: [PATCH 07/15] aout: add gapless support The player will call aout_OutputSetGaplessEnabled(true|false) when there is a next media to play or not. This call is protected with the same mutex used for start/stop and can be called from anywhere. Then, when the stream is draining (aout_DecDrain), it will postpone the actual drain if gapless is enabled, and do the following depending on the next aout call: - if play is called just after, the postponed drain is executed and the gapless state is canceled. - if flush is called, the gapless state is canceled and everything is flushed - if stop is called, filters are cleaned but the stream is not stopped, it is saved for a future usage: (via aout_OutputSaveStream()). On the next stream start, it will try to recover the previous stream (that could be stopped if the player disabled gapless in the meantime) and will do the following depending on the aout_DecNew parameters: - if format are different: cancel gapless state, drain, flush and stop the previous stream. - if format are equals: use the previous stream (don't call aout->stop(), aout->start()), and set-up new filters chain with the new clock. The final gapless transition will be done from aout_DecPlay(): - if the previous stream delay (time_get) is compatible with the next clock. - if not, cancel the gapless state, drain and flush the (previous=current) stream (but don't restart it), and play all blocks normally. Fixes #549 --- src/audio_output/aout_internal.h | 14 +++ src/audio_output/dec.c | 168 ++++++++++++++++++++++++++++--- src/audio_output/output.c | 83 +++++++++++++++ 3 files changed, 251 insertions(+), 14 deletions(-) diff --git a/src/audio_output/aout_internal.h b/src/audio_output/aout_internal.h index 12fa4dfad9..40f44c4a75 100644 --- a/src/audio_output/aout_internal.h +++ b/src/audio_output/aout_internal.h @@ -85,6 +85,16 @@ typedef struct atomic_uint buffers_played; atomic_uchar restart; + bool stream_saved; + bool gapless_enabled; + enum + { + AOUT_GAPLESS_TRANSITION_NONE, + AOUT_GAPLESS_TRANSITION_DRAINED, + AOUT_GAPLESS_TRANSITION_STOPPED, + AOUT_GAPLESS_TRANSITION_STARTED, + } gapless_transition; + atomic_uintptr_t refs; } aout_owner_t; @@ -120,6 +130,10 @@ void aout_Destroy (audio_output_t *); int aout_OutputNew(audio_output_t *, audio_sample_format_t *, aout_filters_cfg_t *filters_cfg); void aout_OutputDelete( audio_output_t * p_aout ); +void aout_OutputSetGaplessEnabled(audio_output_t *aout, bool enabled); +bool aout_OutputIsGaplessEnabled(audio_output_t *aout); +int aout_OutputRecoverStream(audio_output_t *aout); +int aout_OutputSaveStream(audio_output_t *aout); /* From common.c : */ diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c index 26d1d66b94..54ee48e1c8 100644 --- a/src/audio_output/dec.c +++ b/src/audio_output/dec.c @@ -65,6 +65,79 @@ static bool aout_FormatEquals(const audio_sample_format_t *f1, && f1->i_channels == f2->i_channels; } +static int aout_GaplessReuseStream(audio_output_t *aout, + const audio_sample_format_t *format, + vlc_clock_t *clock, + const audio_replay_gain_t *replay_gain) +{ + aout_owner_t *owner = aout_owner(aout); + + if (aout_OutputRecoverStream(aout) != VLC_SUCCESS) + { + /* Unlikely case: The old stream was already stopped by the player */ + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + return -1; + } + + if (!aout_FormatEquals(format, &owner->input_format)) + goto fail; + + /* Reuse the previous stream but set-up new clock and filters */ + owner->sync.clock = clock; + owner->filters = + aout_FiltersNewWithClock(VLC_OBJECT(aout), clock, + format, &owner->mixer_format, + &owner->filters_cfg); + if (owner->filters == NULL) + goto fail; + + owner->volume = aout_volume_New (aout, replay_gain); + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_STARTED; + msg_Dbg(aout, "trying gapless transition"); + return 0; +fail: + /* No gapless: drain and delete the old stream */ + aout_Drain(aout); + aout_OutputDelete(aout); + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + return -1; +} + +static int aout_GaplessCheckTransition(audio_output_t *aout) +{ + aout_owner_t *owner = aout_owner (aout); + assert(owner->gapless_transition == AOUT_GAPLESS_TRANSITION_STARTED); + + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + + vlc_tick_t delay; + if (aout->time_get(aout, &delay) != 0) + { + msg_Warn(aout, "aout delay invalid: canceling gapless"); + goto fail; + } + + const vlc_tick_t system_now = vlc_tick_now(); + const vlc_tick_t play_date = + vlc_clock_ConvertToSystem(owner->sync.clock, system_now, 0, + owner->sync.rate); + const vlc_tick_t jitter = play_date - system_now; + + if (delay <= jitter) + { + msg_Warn(aout, "clock jitter higher than aout delay: canceling gapless"); + goto fail; + } + + msg_Dbg(aout, "gapless transition successful"); + owner->sync.discontinuity = false; + return 0; +fail: + aout_Drain(aout); + aout_DecFlush(aout); + return -1; +} + /** * Creates an audio output */ @@ -74,6 +147,16 @@ int aout_DecNew(audio_output_t *p_aout, const audio_sample_format_t *p_format, assert(p_aout); assert(p_format); assert(clock); + + aout_owner_t *owner = aout_owner(p_aout); + + assert(owner->gapless_transition == AOUT_GAPLESS_TRANSITION_NONE + || owner->gapless_transition == AOUT_GAPLESS_TRANSITION_STOPPED); + + if (owner->gapless_transition == AOUT_GAPLESS_TRANSITION_STOPPED + && aout_GaplessReuseStream(p_aout, p_format, clock, p_replay_gain) == 0) + return 0; + if( p_format->i_bitspersample > 0 ) { /* Sanitize audio format, input need to have a valid physical channels @@ -100,8 +183,6 @@ int aout_DecNew(audio_output_t *p_aout, const audio_sample_format_t *p_format, return -1; } - aout_owner_t *owner = aout_owner(p_aout); - /* Create the audio output stream */ owner->volume = aout_volume_New (p_aout, p_replay_gain); @@ -147,11 +228,25 @@ void aout_DecDelete (audio_output_t *aout) { aout_owner_t *owner = aout_owner (aout); + assert(owner->gapless_transition != AOUT_GAPLESS_TRANSITION_STOPPED); + if (owner->mixer_format.i_format) { - aout_DecFlush(aout); - aout_FiltersDelete (aout, owner->filters); - aout_OutputDelete (aout); + if (owner->gapless_transition == AOUT_GAPLESS_TRANSITION_DRAINED + && aout_OutputSaveStream(aout) == VLC_SUCCESS) + { + /* Gapless transition: save the stream for the next aout_DecNew + * call */ + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_STOPPED; + aout_FiltersDelete (aout, owner->filters); + } + else + { + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + aout_DecFlush(aout); + aout_FiltersDelete (aout, owner->filters); + aout_OutputDelete (aout); + } } aout_volume_Delete (owner->volume); owner->volume = NULL; @@ -206,6 +301,7 @@ static int aout_CheckReady (audio_output_t *aout) } aout_FiltersSetClockDelay(owner->filters, owner->sync.delay); } + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; /* TODO: This would be a good time to call clean up any video output * left over by an audio visualization: input_resource_TerminatVout(MAGIC HERE); */ @@ -433,6 +529,17 @@ int aout_DecPlay(audio_output_t *aout, block_t *block) { aout_owner_t *owner = aout_owner (aout); + if (unlikely(owner->gapless_transition == AOUT_GAPLESS_TRANSITION_DRAINED)) + { + /* Drained but not stopped: do the drain that was previously ignored + * and cancel the gapless transition */ + aout_Drain(aout); + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + } + + assert(owner->gapless_transition == AOUT_GAPLESS_TRANSITION_NONE + || owner->gapless_transition == AOUT_GAPLESS_TRANSITION_STARTED); + assert (block->i_pts != VLC_TICK_INVALID); block->i_length = vlc_tick_from_samples( block->i_nb_samples, @@ -476,17 +583,23 @@ int aout_DecPlay(audio_output_t *aout, block_t *block) aout_volume_Amplify (owner->volume, block); /* Update delay */ - if (owner->sync.request_delay != owner->sync.delay) + if (owner->gapless_transition == AOUT_GAPLESS_TRANSITION_NONE) { - owner->sync.delay = owner->sync.request_delay; - vlc_tick_t delta = vlc_clock_SetDelay(owner->sync.clock, owner->sync.delay); - aout_FiltersSetClockDelay(owner->filters, owner->sync.delay); - if (delta > 0) - aout_DecSilence (aout, delta, block->i_pts); + if (owner->sync.request_delay != owner->sync.delay) + { + owner->sync.delay = owner->sync.request_delay; + vlc_tick_t delta = vlc_clock_SetDelay(owner->sync.clock, + owner->sync.delay); + aout_FiltersSetClockDelay(owner->filters, owner->sync.delay); + if (delta > 0) + aout_DecSilence (aout, delta, block->i_pts); + } } + else + aout_GaplessCheckTransition(aout); + + aout_Play(aout, block, vlc_tick_now(), original_pts); - vlc_tick_t system_now = vlc_tick_now(); - aout_Play(aout, block, system_now, original_pts); return ret; drop: owner->sync.discontinuity = true; @@ -540,6 +653,8 @@ void aout_DecFlush(audio_output_t *aout) if (owner->mixer_format.i_format) { + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + aout_FiltersFlush (owner->filters); aout->flush(aout); @@ -571,11 +686,36 @@ void aout_DecDrain(audio_output_t *aout) if (!owner->mixer_format.i_format) return; + assert(owner->gapless_transition == AOUT_GAPLESS_TRANSITION_NONE + || owner->gapless_transition == AOUT_GAPLESS_TRANSITION_STARTED); + block_t *block = aout_FiltersDrain (owner->filters); if (block) aout->play(aout, block, vlc_tick_now()); - aout_Drain(aout); + if (AOUT_FMT_LINEAR(&owner->mixer_format) + && aout_OutputIsGaplessEnabled(aout)) + { + /* First gapless state, cancellable if stop/play/flush is not called + * right after */ + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_DRAINED; + + /* Make sure we don't fill the audio buffer too much. This is the + * perfect place to wait since this function is expected to be blocking + * (for now). */ + vlc_tick_t delay; + if (aout->time_get(aout, &delay) == 0) + { + static const vlc_tick_t MIN_GAPLESS_PREPARE_TIME = DEFAULT_PTS_DELAY; + if (delay > MIN_GAPLESS_PREPARE_TIME) + vlc_tick_sleep(delay - MIN_GAPLESS_PREPARE_TIME); + } + } + else + { + aout_Drain(aout); + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; + } vlc_clock_Reset(owner->sync.clock); aout_FiltersResetClock(owner->filters); diff --git a/src/audio_output/output.c b/src/audio_output/output.c index 5c378dbb31..b0b451619e 100644 --- a/src/audio_output/output.c +++ b/src/audio_output/output.c @@ -223,6 +223,9 @@ audio_output_t *aout_New (vlc_object_t *parent) vlc_viewpoint_init (&owner->vp.value); atomic_init (&owner->vp.update, false); atomic_init(&owner->refs, 0); + owner->stream_saved = false; + owner->gapless_enabled = false; + owner->gapless_transition = AOUT_GAPLESS_TRANSITION_NONE; /* Audio output module callbacks */ var_Create (aout, "volume", VLC_VAR_FLOAT); @@ -565,6 +568,7 @@ int aout_OutputNew (audio_output_t *aout, audio_sample_format_t *restrict fmt, aout->current_sink_info.headphones = false; vlc_mutex_lock(&owner->lock); + assert(!owner->stream_saved); int ret = aout->start(aout, fmt); assert(aout->flush && aout->play && aout->time_get && aout->pause); vlc_mutex_unlock(&owner->lock); @@ -592,10 +596,89 @@ void aout_OutputDelete (audio_output_t *aout) { aout_owner_t *owner = aout_owner(aout); vlc_mutex_lock(&owner->lock); + assert(!owner->stream_saved); aout->stop (aout); vlc_mutex_unlock(&owner->lock); } +/** + * Recover the ownership of the stream previously saved + */ +int aout_OutputRecoverStream(audio_output_t *aout) +{ + aout_owner_t *owner = aout_owner(aout); + vlc_mutex_lock(&owner->lock); + if (unlikely(!owner->stream_saved)) + { + /* The stream was already stopped by a previous + * aout_OutputSetGaplessEnabled(false) from an other thread */ + vlc_mutex_unlock(&owner->lock); + return VLC_EGENERIC; + } + + owner->stream_saved = false; + owner->gapless_enabled = false; + vlc_mutex_unlock(&owner->lock); + + return VLC_SUCCESS; +} + +/** + * Save the stream for a future usage + */ +int aout_OutputSaveStream(audio_output_t *aout) +{ + aout_owner_t *owner = aout_owner(aout); + + vlc_mutex_lock(&owner->lock); + + assert(!owner->stream_saved); + + if (unlikely(!owner->gapless_enabled)) + { + vlc_mutex_unlock(&owner->lock); + return VLC_EGENERIC; + } + + /* The caller of aout_OutputSetGaplessEnabled() is now the owner + * of the stream during gapless transition. */ + owner->stream_saved = true; + vlc_mutex_unlock(&owner->lock); + + return VLC_SUCCESS; +} + +/** + * Enable or disable gapless + * + * Called by the audio decoder when it is ready to output audio + */ +void aout_OutputSetGaplessEnabled(audio_output_t *aout, bool enabled) +{ + aout_owner_t *owner = aout_owner (aout); + + vlc_mutex_lock(&owner->lock); + + owner->gapless_enabled = enabled; + + if (!enabled && unlikely(owner->stream_saved)) + { + aout->stop (aout); + owner->stream_saved = false; + } + + vlc_mutex_unlock(&owner->lock); +} + +bool aout_OutputIsGaplessEnabled(audio_output_t *aout) +{ + aout_owner_t *owner = aout_owner(aout); + vlc_mutex_lock(&owner->lock); + const bool enabled = owner->gapless_enabled; + vlc_mutex_unlock(&owner->lock); + return enabled; +} + /** * Gets the volume of the audio output stream (independent of mute). * \return Current audio volume (0. = silent, 1. = nominal), -- GitLab From 165f3de35ad4eb916d6acac9fe77c950486db410 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 19 Mar 2019 16:58:05 +0100 Subject: [PATCH 08/15] player: split vlc_player_InvalidateNextMedia --- src/input/player.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/input/player.c b/src/input/player.c index 5956b48812..958f6682e8 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -2338,6 +2338,18 @@ vlc_player_RemoveListener(vlc_player_t *player, free(id); } +static void +vlc_player_ReleaseNextMedia(vlc_player_t *player) +{ + vlc_player_assert_locked(player); + if (player->next_media) + { + input_item_Release(player->next_media); + player->next_media = NULL; + } + player->next_media_requested = false; +} + int vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media) { @@ -2345,7 +2357,7 @@ vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media) vlc_player_CancelWaitError(player); - vlc_player_InvalidateNextMedia(player); + vlc_player_ReleaseNextMedia(player); if (media) { @@ -2462,13 +2474,7 @@ vlc_player_GetAssociatedSubsFPS(vlc_player_t *player) void vlc_player_InvalidateNextMedia(vlc_player_t *player) { - vlc_player_assert_locked(player); - if (player->next_media) - { - input_item_Release(player->next_media); - player->next_media = NULL; - } - player->next_media_requested = false; + vlc_player_ReleaseNextMedia(player); } @@ -2528,7 +2534,7 @@ vlc_player_Stop(vlc_player_t *player) vlc_player_CancelWaitError(player); - vlc_player_InvalidateNextMedia(player); + vlc_player_ReleaseNextMedia(player); if (!input || !player->started) return; @@ -2536,7 +2542,6 @@ vlc_player_Stop(vlc_player_t *player) vlc_player_destructor_AddInput(player, input); player->input = NULL; - } void -- GitLab From 48f717dabf16853e786dc9df61aeef2b6f7932a4 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 30 Apr 2019 16:58:03 +0200 Subject: [PATCH 09/15] input: add gapless support When started with the INPUT_TYPE_GAPLESS type, an input will start its buffering and wait for the input_WakeGapless() call. Decoders started in gapless mode will wait for the end of the gapless state before outputing their first outputs. The audio decoder started in gapless mode will setup the audio output used by the current input for gapless. Note: If there is not valid audio data during the input buffering, the audio gapless won't be possible. --- src/input/decoder.c | 89 +++++++++++++++++++++++++++++++++++++- src/input/decoder.h | 3 ++ src/input/es_out.c | 16 +++++++ src/input/input.c | 72 ++++++++++++++++++++++++++++-- src/input/input_internal.h | 15 +++++++ 5 files changed, 190 insertions(+), 5 deletions(-) diff --git a/src/input/decoder.c b/src/input/decoder.c index dd361cf7e8..fdabaa5a5a 100644 --- a/src/input/decoder.c +++ b/src/input/decoder.c @@ -101,6 +101,7 @@ struct decoder_owner vlc_cond_t wait_request; vlc_cond_t wait_acknowledge; vlc_cond_t wait_fifo; /* TODO: merge with wait_acknowledge */ + vlc_cond_t wait_gapless; /* -- These variables need locking on write(only) -- */ audio_output_t *p_aout; @@ -124,6 +125,7 @@ struct decoder_owner bool b_waiting; bool b_first; bool b_has_data; + enum input_gapless_status gapless_status; /* Flushing */ bool flushing; @@ -261,6 +263,18 @@ static void DecoderUpdateFormatLocked( decoder_t *p_dec ) memory_order_release ); } +static int DecoderWaitGapless(decoder_t *dec) +{ + struct decoder_owner *owner = dec_get_owner(dec); + + vlc_mutex_lock(&owner->lock); + while (owner->gapless_status == INPUT_GAPLESS_STATUS_WAITING) + vlc_cond_wait(&owner->wait_gapless, &owner->lock); + int ret = owner->gapless_status == INPUT_GAPLESS_STATUS_NONE ? 0 : -1; + vlc_mutex_unlock(&owner->lock); + return ret; +} + static void MouseEvent( const vlc_mouse_t *newmouse, void *user_data ) { decoder_t *dec = user_data; @@ -340,7 +354,48 @@ static int aout_update_format( decoder_t *p_dec ) audio_output_t *p_aout; - p_aout = input_resource_GetAout( p_owner->p_resource ); + vlc_mutex_lock(&p_owner->lock); + switch (p_owner->gapless_status) + { + case INPUT_GAPLESS_STATUS_NONE: + vlc_mutex_unlock(&p_owner->lock); + /* Get a free audio output */ + p_aout = input_resource_GetAout(p_owner->p_resource); + break; + case INPUT_GAPLESS_STATUS_WAITING: + vlc_mutex_unlock(&p_owner->lock); + + /* Hold the audio output used by the last audio decoder and + * enable the gapless mode. */ + p_aout = input_resource_HoldAout(p_owner->p_resource); + if (likely(p_aout)) + aout_OutputSetGaplessEnabled(p_aout, true); + + /* Wait for the last input to finish */ + if (DecoderWaitGapless(p_dec) != 0) + { + /* Disable gapless mode if the current input is aborted */ + if (likely(p_aout)) + { + aout_OutputSetGaplessEnabled(p_aout, false); + aout_Release(p_aout); + } + return -1; + } + + if (likely(p_aout)) + aout_Release(p_aout); + + /* Re-use the last aout that is likely in gapless state */ + p_aout = input_resource_GetAout(p_owner->p_resource); + break; + case INPUT_GAPLESS_STATUS_ABORTED: + vlc_mutex_unlock(&p_owner->lock); + return -1; + default: + vlc_assert_unreachable(); + } + if( p_aout ) { /* TODO: 3.0 HACK: we need to put i_profile inside audio_format_t @@ -457,6 +512,9 @@ static int vout_update_format( decoder_t *p_dec ) return -1; } + if (DecoderWaitGapless(p_dec) != 0) + return -1; + video_format_t fmt = p_dec->fmt_out.video; fmt.i_chroma = p_dec->fmt_out.i_codec; @@ -600,6 +658,9 @@ static subpicture_t *spu_new_buffer( decoder_t *p_dec, if( p_owner->error ) break; + if (DecoderWaitGapless(p_dec) != 0) + return NULL; + p_vout = input_resource_HoldVout( p_owner->p_resource ); if( p_vout ) break; @@ -1808,6 +1869,13 @@ static decoder_t * CreateDecoder( vlc_object_t *p_parent, p_owner->b_waiting = false; p_owner->b_first = true; p_owner->b_has_data = false; + p_owner->gapless_status = input_type == INPUT_TYPE_GAPLESS ? + INPUT_GAPLESS_STATUS_WAITING : INPUT_GAPLESS_STATUS_NONE; + +#ifndef NDEBUG + if (input_type == INPUT_TYPE_GAPLESS) + assert(!p_sout); +#endif p_owner->error = false; @@ -1835,6 +1903,7 @@ static decoder_t * CreateDecoder( vlc_object_t *p_parent, vlc_cond_init( &p_owner->wait_request ); vlc_cond_init( &p_owner->wait_acknowledge ); vlc_cond_init( &p_owner->wait_fifo ); + vlc_cond_init( &p_owner->wait_gapless ); /* Load a packetizer module if the input is not already packetized */ if( p_sout == NULL && !fmt->b_packetized ) @@ -1948,7 +2017,6 @@ static void DeleteDecoder( decoder_t * p_dec ) case AUDIO_ES: if( p_owner->p_aout ) { - /* TODO: REVISIT gap-less audio */ aout_DecDelete( p_owner->p_aout ); input_resource_PutAout( p_owner->p_resource, p_owner->p_aout ); } @@ -1993,6 +2061,7 @@ static void DeleteDecoder( decoder_t * p_dec ) decoder_Destroy( p_owner->p_packetizer ); + vlc_cond_destroy( &p_owner->wait_gapless ); vlc_cond_destroy( &p_owner->wait_fifo ); vlc_cond_destroy( &p_owner->wait_acknowledge ); vlc_cond_destroy( &p_owner->wait_request ); @@ -2448,6 +2517,7 @@ void input_DecoderWait( decoder_t *p_dec ) assert( p_owner->b_waiting ); vlc_mutex_lock( &p_owner->lock ); + while( !p_owner->b_has_data ) { /* Don't need to lock p_owner->paused since it's only modified by the @@ -2467,6 +2537,21 @@ void input_DecoderWait( decoder_t *p_dec ) vlc_mutex_unlock( &p_owner->lock ); } +void input_DecoderSignalGaplessStatus(decoder_t *dec, + enum input_gapless_status status) +{ + struct decoder_owner *owner = dec_get_owner(dec); + + vlc_mutex_lock(&owner->lock); + + assert(status != INPUT_GAPLESS_STATUS_WAITING); + assert(owner->gapless_status == INPUT_GAPLESS_STATUS_WAITING); + + owner->gapless_status = status; + vlc_cond_signal(&owner->wait_gapless); + vlc_mutex_unlock(&owner->lock); +} + void input_DecoderFrameNext( decoder_t *p_dec, vlc_tick_t *pi_duration ) { struct decoder_owner *p_owner = dec_get_owner( p_dec ); diff --git a/src/input/decoder.h b/src/input/decoder.h index 49581e8fc6..fe5a8f1572 100644 --- a/src/input/decoder.h +++ b/src/input/decoder.h @@ -92,6 +92,9 @@ void input_DecoderWait( decoder_t * ); */ void input_DecoderStopWait( decoder_t * ); +void input_DecoderSignalGaplessStatus(decoder_t *, + enum input_gapless_status status); + /** * This function returns true if the decoder fifo is empty and false otherwise. */ diff --git a/src/input/es_out.c b/src/input/es_out.c index b0a79bcea4..6ce8d53eb6 100644 --- a/src/input/es_out.c +++ b/src/input/es_out.c @@ -990,6 +990,22 @@ static void EsOutDecodersStopBuffering( es_out_t *out, bool b_forced ) return; } + if (p_sys->input_type == INPUT_TYPE_GAPLESS) + { + enum input_gapless_status status = + input_WaitGapless(p_sys->p_input); + + /* Don't wait when re-buffering again */ + p_sys->input_type = INPUT_TYPE_NONE; + + foreach_es_then_es_slaves(p_es) + { + if (!p_es->p_dec) + continue; + input_DecoderSignalGaplessStatus(p_es->p_dec, status); + } + } + const vlc_tick_t i_decoder_buffering_start = vlc_tick_now(); foreach_es_then_es_slaves(p_es) { diff --git a/src/input/input.c b/src/input/input.c index 4dd8ef3597..9e43902270 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -109,6 +109,48 @@ static int input_SlaveSourceAdd( input_thread_t *, enum slave_type, static char *input_SubtitleFile2Uri( input_thread_t *, const char * ); static void input_ChangeState( input_thread_t *p_input, int i_state ); /* TODO fix name */ +enum input_gapless_status input_WaitGapless(input_thread_t *input) +{ + input_thread_private_t *priv = input_priv(input); + + vlc_mutex_lock(&priv->lock_gapless); + + while (priv->gapless_status == INPUT_GAPLESS_STATUS_WAITING) + vlc_cond_wait(&priv->wait_gapless, &priv->lock_gapless); + enum input_gapless_status status = priv->gapless_status; + + vlc_mutex_unlock(&priv->lock_gapless); + return status; +} + +static void input_SignalGapless(input_thread_t *input, + enum input_gapless_status status) +{ + input_thread_private_t *priv = input_priv(input); + + vlc_mutex_lock(&priv->lock_gapless); + + assert(status != INPUT_GAPLESS_STATUS_WAITING); + assert(priv->gapless_status == INPUT_GAPLESS_STATUS_WAITING); + + priv->gapless_status = status; + vlc_cond_broadcast(&priv->wait_gapless); + + vlc_mutex_unlock(&priv->lock_gapless); +} + +void input_WakeGapless(input_thread_t *input) +{ + input_thread_private_t *priv = input_priv(input); + + assert(priv->type == INPUT_TYPE_GAPLESS); + priv->type = INPUT_TYPE_NONE; + + input_resource_SetInput(priv->p_resource, input); + + input_SignalGapless(input, INPUT_GAPLESS_STATUS_NONE); +} + /** * Start a input_thread_t created by input_Create. * @@ -157,6 +199,9 @@ void input_Stop( input_thread_t *p_input ) vlc_cond_signal( &sys->wait_control ); vlc_mutex_unlock( &sys->lock_control ); vlc_interrupt_kill( &sys->interrupt ); + + if (sys->type == INPUT_TYPE_GAPLESS) + input_SignalGapless(p_input, INPUT_GAPLESS_STATUS_ABORTED); } /** @@ -239,6 +284,9 @@ input_thread_t *input_Create( vlc_object_t *p_parent, case INPUT_TYPE_THUMBNAILING: type_str = "thumbnailing "; break; + case INPUT_TYPE_GAPLESS: + type_str = "gapless "; + break; default: type_str = ""; break; @@ -337,7 +385,6 @@ input_thread_t *input_Create( vlc_object_t *p_parent, priv->p_resource = input_resource_Hold( p_resource ); else priv->p_resource = input_resource_New( VLC_OBJECT( p_input ) ); - input_resource_SetInput( priv->p_resource, p_input ); /* Init control buffer */ vlc_mutex_init( &priv->lock_control ); @@ -345,6 +392,13 @@ input_thread_t *input_Create( vlc_object_t *p_parent, priv->i_control = 0; vlc_interrupt_init(&priv->interrupt); + if (priv->type == INPUT_TYPE_GAPLESS) + { + priv->gapless_status = INPUT_GAPLESS_STATUS_WAITING; + vlc_mutex_init(&priv->lock_gapless); + vlc_cond_init(&priv->wait_gapless); + } + /* Create Object Variables for private use only */ input_ConfigVarInit( p_input ); @@ -396,6 +450,13 @@ static void Destroy(input_thread_t *input) vlc_cond_destroy(&priv->wait_control); vlc_mutex_destroy(&priv->lock_control); + + if (priv->type == INPUT_TYPE_GAPLESS) + { + vlc_cond_destroy(&priv->wait_gapless); + vlc_mutex_destroy(&priv->lock_gapless); + } + vlc_object_delete(VLC_OBJECT(input)); } @@ -1210,6 +1271,9 @@ static int Init( input_thread_t * p_input ) goto error; #endif + if (priv->type != INPUT_TYPE_GAPLESS) + input_resource_SetInput(priv->p_resource, p_input); + /* Create es out */ priv->p_es_out = input_EsOutTimeshiftNew( p_input, priv->p_es_out_display, priv->rate ); if( priv->p_es_out == NULL ) @@ -1293,7 +1357,8 @@ error: if( input_priv(p_input)->p_sout ) input_resource_RequestSout( input_priv(p_input)->p_resource, input_priv(p_input)->p_sout, NULL ); - input_resource_SetInput( input_priv(p_input)->p_resource, NULL ); + if (priv->type != INPUT_TYPE_GAPLESS) + input_resource_SetInput( input_priv(p_input)->p_resource, NULL ); if( input_priv(p_input)->p_resource ) { input_resource_Release( input_priv(p_input)->p_resource ); @@ -1360,7 +1425,8 @@ static void End( input_thread_t * p_input ) /* */ input_resource_RequestSout( input_priv(p_input)->p_resource, input_priv(p_input)->p_sout, NULL ); - input_resource_SetInput( input_priv(p_input)->p_resource, NULL ); + if (priv->type != INPUT_TYPE_GAPLESS) + input_resource_SetInput(input_priv(p_input)->p_resource, NULL); if( input_priv(p_input)->p_resource ) { input_resource_Release( input_priv(p_input)->p_resource ); diff --git a/src/input/input_internal.h b/src/input/input_internal.h index 68b26bb870..fbb5a2499a 100644 --- a/src/input/input_internal.h +++ b/src/input/input_internal.h @@ -57,6 +57,14 @@ enum input_type { INPUT_TYPE_NONE, INPUT_TYPE_PREPARSING, INPUT_TYPE_THUMBNAILING, + INPUT_TYPE_GAPLESS, +}; + +enum input_gapless_status +{ + INPUT_GAPLESS_STATUS_NONE, + INPUT_GAPLESS_STATUS_WAITING, + INPUT_GAPLESS_STATUS_ABORTED, }; /** @@ -480,6 +488,10 @@ typedef struct input_thread_private_t size_t i_control; input_control_t control[INPUT_CONTROL_FIFO_SIZE]; + vlc_mutex_t lock_gapless; + vlc_cond_t wait_gapless; + enum input_gapless_status gapless_status; + vlc_thread_t thread; vlc_interrupt_t interrupt; } input_thread_private_t; @@ -552,6 +564,9 @@ enum input_control_e INPUT_CONTROL_SET_VBI_TRANSPARENCY, }; +enum input_gapless_status input_WaitGapless(input_thread_t *); +void input_WakeGapless(input_thread_t *); + /* Internal helpers */ void input_ControlPush( input_thread_t *, int, const input_control_param_t * ); -- GitLab From 8f15518dbc9db42fe259299c11ffa149ff203314 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 18 Apr 2019 16:56:48 +0200 Subject: [PATCH 10/15] player: simplify vlc_player_OpenNextMedia No functional changes. --- src/input/player.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/input/player.c b/src/input/player.c index 958f6682e8..1221d85631 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -773,31 +773,31 @@ vlc_player_OpenNextMedia(vlc_player_t *player) player->next_media_requested = false; - int ret = VLC_SUCCESS; if (player->releasing_media) { assert(player->media); input_item_Release(player->media); player->media = NULL; player->releasing_media = false; + vlc_player_SendEvent(player, on_current_media_changed, NULL); + return VLC_SUCCESS; } - else - { - if (!player->next_media) - return VLC_EGENERIC; - if (player->media) - input_item_Release(player->media); - player->media = player->next_media; - player->next_media = NULL; + int ret = VLC_SUCCESS; + if (!player->next_media) + return VLC_EGENERIC; - player->input = vlc_player_input_New(player, player->media); - if (!player->input) - { - input_item_Release(player->media); - player->media = NULL; - ret = VLC_ENOMEM; - } + if (player->media) + input_item_Release(player->media); + player->media = player->next_media; + player->next_media = NULL; + + player->input = vlc_player_input_New(player, player->media); + if (!player->input) + { + input_item_Release(player->media); + player->media = NULL; + ret = VLC_ENOMEM; } vlc_player_SendEvent(player, on_current_media_changed, player->media); return ret; -- GitLab From 950caf057d512fc76bce7f0f3b6157d4f5ebceb0 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Wed, 15 May 2019 16:33:54 +0200 Subject: [PATCH 11/15] player: simplify media_stopped_action handling No functional changes. --- src/input/player.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/input/player.c b/src/input/player.c index 1221d85631..850a8836f0 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -996,25 +996,20 @@ vlc_player_input_HandleState(struct vlc_player_input *input, vlc_player_WaitRetryDelay(player); - if (!player->deleting) + if (!player->deleting + && player->media_stopped_action != VLC_PLAYER_MEDIA_STOPPED_STOP) vlc_player_OpenNextMedia(player); - if (!player->input) - player->started = false; - switch (player->media_stopped_action) + if (player->input) { - case VLC_PLAYER_MEDIA_STOPPED_EXIT: - if (player->input && player->started) - vlc_player_input_Start(player->input); - else - libvlc_Quit(vlc_object_instance(player)); - break; - case VLC_PLAYER_MEDIA_STOPPED_CONTINUE: - if (player->input && player->started) - vlc_player_input_Start(player->input); - break; - default: - break; + assert(player->started); + vlc_player_input_Start(player->input); + } + else + { + player->started = false; + if (player->media_stopped_action == VLC_PLAYER_MEDIA_STOPPED_EXIT) + libvlc_Quit(vlc_object_instance(player)); } send_event = !player->started; -- GitLab From a9a7ee512ec8d0aec207bd5c1b54ce02bd533427 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 18 Apr 2019 14:43:28 +0200 Subject: [PATCH 12/15] player: only send event for the main input For now, it's always the case --- src/input/player.c | 177 ++++++++++++++++++++++++++++----------------- 1 file changed, 111 insertions(+), 66 deletions(-) diff --git a/src/input/player.c b/src/input/player.c index 850a8836f0..fee953a9aa 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -102,6 +102,7 @@ struct vlc_player_input input_thread_t *thread; vlc_player_t *player; bool started; + bool gapless; enum vlc_player_state state; enum vlc_player_error error; @@ -658,6 +659,7 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item) input->player = player; input->started = false; + input->gapless = false; input->state = VLC_PLAYER_STATE_STOPPED; input->error = VLC_PLAYER_ERROR_NONE; @@ -714,8 +716,9 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item) }; input_ControlPush(input->thread, INPUT_CONTROL_SET_CATEGORY_DELAY, ¶m); - vlc_player_SendEvent(player, on_category_delay_changed, i, - cat_delays[i]); + if (!input->gapless) + vlc_player_SendEvent(player, on_category_delay_changed, i, + cat_delays[i]); } } return input; @@ -975,7 +978,7 @@ vlc_player_input_HandleState(struct vlc_player_input *input, /* Override the global state if the player is still playing and has a next * media to play */ - bool send_event = player->global_state != state; + bool send_event = player->global_state != state && !input->gapless; switch (input->state) { case VLC_PLAYER_STATE_STOPPED: @@ -986,7 +989,8 @@ vlc_player_input_HandleState(struct vlc_player_input *input, { vlc_player_title_list_Release(input->titles); input->titles = NULL; - vlc_player_SendEvent(player, on_titles_changed, NULL); + if (!input->gapless) + vlc_player_SendEvent(player, on_titles_changed, NULL); } if (input->error != VLC_PLAYER_ERROR_NONE) @@ -1016,8 +1020,9 @@ vlc_player_input_HandleState(struct vlc_player_input *input, break; case VLC_PLAYER_STATE_STOPPING: input->started = false; - if (input == player->input) - player->input = NULL; + if (input != player->input) + break; + player->input = NULL; if (player->started) { @@ -1171,8 +1176,9 @@ vlc_player_input_HandleProgramEvent(struct vlc_player_input *input, vlc_player_program_Delete(prgm); break; } - vlc_player_SendEvent(player, on_program_list_changed, - VLC_PLAYER_LIST_ADDED, prgm); + if (!input->gapless) + vlc_player_SendEvent(player, on_program_list_changed, + VLC_PLAYER_LIST_ADDED, prgm); break; case VLC_INPUT_PROGRAM_DELETED: { @@ -1180,8 +1186,9 @@ vlc_player_input_HandleProgramEvent(struct vlc_player_input *input, prgm = vlc_player_program_vector_FindById(vec, ev->id, &idx); if (prgm) { - vlc_player_SendEvent(player, on_program_list_changed, - VLC_PLAYER_LIST_REMOVED, prgm); + if (!input->gapless) + vlc_player_SendEvent(player, on_program_list_changed, + VLC_PLAYER_LIST_REMOVED, prgm); vlc_vector_remove(vec, idx); vlc_player_program_Delete(prgm); } @@ -1199,8 +1206,9 @@ vlc_player_input_HandleProgramEvent(struct vlc_player_input *input, } else prgm->scrambled = ev->scrambled; - vlc_player_SendEvent(player, on_program_list_changed, - VLC_PLAYER_LIST_UPDATED, prgm); + if (!input->gapless) + vlc_player_SendEvent(player, on_program_list_changed, + VLC_PLAYER_LIST_UPDATED, prgm); break; case VLC_INPUT_PROGRAM_SELECTED: { @@ -1226,7 +1234,8 @@ vlc_player_input_HandleProgramEvent(struct vlc_player_input *input, } } } - if (unselected_id != -1 || selected_id != -1) + if (!input->gapless + && (unselected_id != -1 || selected_id != -1)) vlc_player_SendEvent(player, on_program_selection_changed, unselected_id, selected_id); break; @@ -1683,10 +1692,8 @@ vlc_player_input_HandleTeletextMenu(struct vlc_player_input *input, } input->teletext_menu = vlc_player_track_New(ev->id, ev->title, ev->fmt); - if (!input->teletext_menu) - return; - - vlc_player_SendEvent(player, on_teletext_menu_changed, true); + if (!input->gapless && input->teletext_menu) + vlc_player_SendEvent(player, on_teletext_menu_changed, true); break; case VLC_INPUT_ES_DELETED: { @@ -1696,7 +1703,8 @@ vlc_player_input_HandleTeletextMenu(struct vlc_player_input *input, vlc_player_track_priv_Delete(input->teletext_menu); input->teletext_menu = NULL; - vlc_player_SendEvent(player, on_teletext_menu_changed, false); + if (!input->gapless) + vlc_player_SendEvent(player, on_teletext_menu_changed, false); } break; } @@ -1707,8 +1715,9 @@ vlc_player_input_HandleTeletextMenu(struct vlc_player_input *input, if (input->teletext_menu->t.es_id == ev->id) { input->teletext_enabled = ev->action == VLC_INPUT_ES_SELECTED; - vlc_player_SendEvent(player, on_teletext_enabled_changed, - input->teletext_enabled); + if (!input->gapless) + vlc_player_SendEvent(player, on_teletext_enabled_changed, + input->teletext_enabled); } break; default: @@ -1823,8 +1832,9 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input, vlc_player_track_priv_Delete(trackpriv); break; } - vlc_player_SendEvent(player, on_track_list_changed, - VLC_PLAYER_LIST_ADDED, &trackpriv->t); + if (!input->gapless) + vlc_player_SendEvent(player, on_track_list_changed, + VLC_PLAYER_LIST_ADDED, &trackpriv->t); break; case VLC_INPUT_ES_DELETED: { @@ -1832,8 +1842,9 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input, trackpriv = vlc_player_track_vector_FindById(vec, ev->id, &idx); if (trackpriv) { - vlc_player_SendEvent(player, on_track_list_changed, - VLC_PLAYER_LIST_REMOVED, &trackpriv->t); + if (!input->gapless) + vlc_player_SendEvent(player, on_track_list_changed, + VLC_PLAYER_LIST_REMOVED, &trackpriv->t); vlc_vector_remove(vec, idx); vlc_player_track_priv_Delete(trackpriv); } @@ -1845,16 +1856,18 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input, break; if (vlc_player_track_priv_Update(trackpriv, ev->title, ev->fmt) != 0) break; - vlc_player_SendEvent(player, on_track_list_changed, - VLC_PLAYER_LIST_UPDATED, &trackpriv->t); + if (!input->gapless) + vlc_player_SendEvent(player, on_track_list_changed, + VLC_PLAYER_LIST_UPDATED, &trackpriv->t); break; case VLC_INPUT_ES_SELECTED: trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL); if (trackpriv) { trackpriv->t.selected = true; - vlc_player_SendEvent(player, on_track_selection_changed, - NULL, trackpriv->t.es_id); + if (!input->gapless) + vlc_player_SendEvent(player, on_track_selection_changed, + NULL, trackpriv->t.es_id); } break; case VLC_INPUT_ES_UNSELECTED: @@ -1862,8 +1875,9 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input, if (trackpriv) { trackpriv->t.selected = false; - vlc_player_SendEvent(player, on_track_selection_changed, - trackpriv->t.es_id, NULL); + if (!input->gapless) + vlc_player_SendEvent(player, on_track_selection_changed, + trackpriv->t.es_id, NULL); } break; default: @@ -1890,10 +1904,13 @@ vlc_player_input_HandleTitleEvent(struct vlc_player_input *input, input->titles = vlc_player_title_list_Create(ev->list.array, ev->list.count, title_offset, chapter_offset); - vlc_player_SendEvent(player, on_titles_changed, input->titles); - if (input->titles) - vlc_player_SendEvent(player, on_title_selection_changed, - &input->titles->array[0], 0); + if (!input->gapless) + { + vlc_player_SendEvent(player, on_titles_changed, input->titles); + if (input->titles) + vlc_player_SendEvent(player, on_title_selection_changed, + &input->titles->array[0], 0); + } break; } case VLC_INPUT_TITLE_SELECTED: @@ -1901,9 +1918,10 @@ vlc_player_input_HandleTitleEvent(struct vlc_player_input *input, return; /* a previous VLC_INPUT_TITLE_NEW_LIST failed */ assert(ev->selected_idx < input->titles->count); input->title_selected = ev->selected_idx; - vlc_player_SendEvent(player, on_title_selection_changed, - &input->titles->array[input->title_selected], - input->title_selected); + if (!input->gapless) + vlc_player_SendEvent(player, on_title_selection_changed, + &input->titles->array[input->title_selected], + input->title_selected); break; default: vlc_assert_unreachable(); @@ -1927,9 +1945,13 @@ vlc_player_input_HandleChapterEvent(struct vlc_player_input *input, input->title_selected = ev->title; input->chapter_selected = ev->seekpoint; - const struct vlc_player_chapter *chapter = &title->chapters[ev->seekpoint]; - vlc_player_SendEvent(player, on_chapter_selection_changed, title, ev->title, - chapter, ev->seekpoint); + if (!input->gapless) + { + const struct vlc_player_chapter *chapter = + &title->chapters[ev->seekpoint]; + vlc_player_SendEvent(player, on_chapter_selection_changed, title, + ev->title, chapter, ev->seekpoint); + } } struct vlc_player_title_list * @@ -2072,6 +2094,8 @@ vlc_player_input_HandleVoutEvent(struct vlc_player_input *input, const bool is_video_es = trackpriv->t.fmt.i_cat == VIDEO_ES; + assert(!input->gapless); + switch (ev->action) { case VLC_INPUT_EVENT_VOUT_ADDED: @@ -2140,7 +2164,9 @@ vlc_player_input_HandleStateEvent(struct vlc_player_input *input, { /* Contrary to the input_thead_t, an error is not a state */ input->error = VLC_PLAYER_ERROR_GENERIC; - vlc_player_SendEvent(input->player, on_error_changed, input->error); + if (!input->gapless) + vlc_player_SendEvent(input->player, on_error_changed, + input->error); } break; default: @@ -2184,14 +2210,16 @@ input_thread_Events(input_thread_t *input_thread, break; case INPUT_EVENT_RATE: input->rate = event->rate; - vlc_player_SendEvent(player, on_rate_changed, input->rate); + if (!input->gapless) + vlc_player_SendEvent(player, on_rate_changed, input->rate); break; case INPUT_EVENT_CAPABILITIES: { int old_caps = input->capabilities; input->capabilities = event->capabilities; - vlc_player_SendEvent(player, on_capabilities_changed, - old_caps, input->capabilities); + if (!input->gapless) + vlc_player_SendEvent(player, on_capabilities_changed, + old_caps, input->capabilities); break; } case INPUT_EVENT_POSITION: @@ -2200,20 +2228,25 @@ input_thread_Events(input_thread_t *input_thread, { input->time = event->position.ms; input->position = event->position.percentage; - vlc_player_SendEvent(player, on_position_changed, - input->time, - input->position); + if (!input->gapless) + { + vlc_player_SendEvent(player, on_position_changed, + input->time, + input->position); - if (input->abloop_state[0].set && input->abloop_state[1].set - && input == player->input) - vlc_player_HandleAtoBLoop(player); + if (input->abloop_state[0].set && input->abloop_state[1].set + && !input->gapless) + vlc_player_HandleAtoBLoop(player); + } } break; case INPUT_EVENT_LENGTH: if (input->length != event->length) { input->length = event->length; - vlc_player_SendEvent(player, on_length_changed, input->length); + if (!input->gapless) + vlc_player_SendEvent(player, on_length_changed, + input->length); } break; case INPUT_EVENT_PROGRAM: @@ -2230,36 +2263,46 @@ input_thread_Events(input_thread_t *input_thread, break; case INPUT_EVENT_RECORD: input->recording = event->record; - vlc_player_SendEvent(player, on_recording_changed, input->recording); + if (!input->gapless) + vlc_player_SendEvent(player, on_recording_changed, + input->recording); break; case INPUT_EVENT_STATISTICS: input->stats = *event->stats; - vlc_player_SendEvent(player, on_statistics_changed, &input->stats); + if (!input->gapless) + vlc_player_SendEvent(player, on_statistics_changed, + &input->stats); break; case INPUT_EVENT_SIGNAL: input->signal_quality = event->signal.quality; input->signal_strength = event->signal.strength; - vlc_player_SendEvent(player, on_signal_changed, - input->signal_quality, input->signal_strength); + if (!input->gapless) + vlc_player_SendEvent(player, on_signal_changed, + input->signal_quality, + input->signal_strength); break; case INPUT_EVENT_CACHE: input->cache = event->cache; - vlc_player_SendEvent(player, on_buffering_changed, event->cache); + if (!input->gapless) + vlc_player_SendEvent(player, on_buffering_changed, event->cache); break; case INPUT_EVENT_VOUT: vlc_player_input_HandleVoutEvent(input, &event->vout); break; case INPUT_EVENT_ITEM_META: - vlc_player_SendEvent(player, on_media_meta_changed, - input_GetItem(input->thread)); + if (!input->gapless) + vlc_player_SendEvent(player, on_media_meta_changed, + input_GetItem(input->thread)); break; case INPUT_EVENT_ITEM_EPG: - vlc_player_SendEvent(player, on_media_epg_changed, - input_GetItem(input->thread)); + if (!input->gapless) + vlc_player_SendEvent(player, on_media_epg_changed, + input_GetItem(input->thread)); break; case INPUT_EVENT_SUBITEMS: - vlc_player_SendEvent(player, on_media_subitems_changed, - input_GetItem(input->thread), event->subitems); + if (!input->gapless) + vlc_player_SendEvent(player, on_media_subitems_changed, + input_GetItem(input->thread), event->subitems); input_item_node_Delete(event->subitems); break; case INPUT_EVENT_DEAD: @@ -2269,13 +2312,15 @@ input_thread_Events(input_thread_t *input_thread, break; case INPUT_EVENT_VBI_PAGE: input->teletext_page = event->vbi_page < 999 ? event->vbi_page : 100; - vlc_player_SendEvent(player, on_teletext_page_changed, - input->teletext_page); + if (!input->gapless) + vlc_player_SendEvent(player, on_teletext_page_changed, + input->teletext_page); break; case INPUT_EVENT_VBI_TRANSPARENCY: input->teletext_transparent = event->vbi_transparent; - vlc_player_SendEvent(player, on_teletext_transparency_changed, - input->teletext_transparent); + if (!input->gapless) + vlc_player_SendEvent(player, on_teletext_transparency_changed, + input->teletext_transparent); break; default: break; -- GitLab From 8373906fae7dcf72ebca21d88ef46f1fb60f9faa Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 30 Apr 2019 16:59:22 +0200 Subject: [PATCH 13/15] player: add gapless support If gapless mode is enabled, the player will start a new input in gapless mode, when the current input is almost finished (3 seconds before the end). --- include/vlc_player.h | 12 ++ src/input/player.c | 273 ++++++++++++++++++++++++++++++++++++++----- src/libvlccore.sym | 1 + 3 files changed, 259 insertions(+), 27 deletions(-) diff --git a/include/vlc_player.h b/include/vlc_player.h index e1010decd6..19c3ac48e1 100644 --- a/include/vlc_player.h +++ b/include/vlc_player.h @@ -1211,6 +1211,18 @@ VLC_API void vlc_player_SetMediaStoppedAction(vlc_player_t *player, enum vlc_player_media_stopped_action action); +/** + * Enable or disable gapless playback + * + * @warning Enabling gapless playback will cause the player to open 2 media + * simultaneously. + * + * @param player locked player instance + * @param enabled true to enable gapless playback + */ +VLC_API void +vlc_player_SetGaplessEnabled(vlc_player_t *player, bool enabled); + /** * Start the playback of the current media. * diff --git a/src/input/player.c b/src/input/player.c index fee953a9aa..86a2e5814d 100644 --- a/src/input/player.c +++ b/src/input/player.c @@ -43,6 +43,7 @@ #define RETRY_TIMEOUT_BASE VLC_TICK_FROM_MS(100) #define RETRY_TIMEOUT_MAX VLC_TICK_FROM_MS(3200) +#define OPEN_NEXT_DELAY AOUT_MAX_ADVANCE_TIME static_assert(VLC_PLAYER_CAP_SEEK == VLC_INPUT_CAPABILITIES_SEEKABLE && VLC_PLAYER_CAP_PAUSE == VLC_INPUT_CAPABILITIES_PAUSEABLE && @@ -104,6 +105,8 @@ struct vlc_player_input bool started; bool gapless; + input_item_node_t *subitems; /* subitems saved during gapless transition */ + enum vlc_player_state state; enum vlc_player_error error; float rate; @@ -164,6 +167,8 @@ struct vlc_player_t bool pause_on_cork; bool corked; + bool gapless_enabled; + bool gapless_failed; struct vlc_list listeners; struct vlc_list aout_listeners; @@ -174,6 +179,7 @@ struct vlc_player_t input_item_t *media; struct vlc_player_input *input; + struct vlc_player_input *next_input; bool releasing_media; bool next_media_requested; @@ -231,6 +237,10 @@ struct vlc_player_t for (struct vlc_player_input *it = player->input; it != NULL; it = NULL) static void +vlc_player_SendNewEvents(vlc_player_t *player, + struct vlc_player_input *input, + struct vlc_player_input *old_input); +static void input_thread_Events(input_thread_t *, const struct vlc_input_event *, void *); static void vlc_player_input_HandleState(struct vlc_player_input *, enum vlc_player_state); @@ -651,7 +661,7 @@ vlc_player_title_list_GetCount(struct vlc_player_title_list *titles) } static struct vlc_player_input * -vlc_player_input_New(vlc_player_t *player, input_item_t *item) +vlc_player_input_New(vlc_player_t *player, input_item_t *item, bool gapless) { struct vlc_player_input *input = malloc(sizeof(*input)); if (!input) @@ -659,7 +669,8 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item) input->player = player; input->started = false; - input->gapless = false; + input->gapless = gapless; + input->subitems = NULL; input->state = VLC_PLAYER_STATE_STOPPED; input->error = VLC_PLAYER_ERROR_NONE; @@ -689,9 +700,20 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item) input->abloop_state[0].set = input->abloop_state[1].set = false; + enum input_type input_type = gapless ? INPUT_TYPE_GAPLESS : INPUT_TYPE_NONE; input->thread = input_Create(player, input_thread_Events, input, item, - INPUT_TYPE_NONE, player->resource, - player->renderer); + input_type, player->resource, player->renderer); + if (gapless) + { + assert(player->input); + assert(!player->renderer); + if (input->thread) + { + int ret = input_Start(input->thread); + if (ret == VLC_SUCCESS) + input->started = true; + } + } if (!input->thread) { free(input); @@ -739,6 +761,9 @@ vlc_player_input_Delete(struct vlc_player_input *input) vlc_vector_destroy(&input->audio_track_vector); vlc_vector_destroy(&input->spu_track_vector); + if (input->subitems) + input_item_node_Delete(input->subitems); + input_Close(input->thread); free(input); } @@ -746,11 +771,22 @@ vlc_player_input_Delete(struct vlc_player_input *input) static int vlc_player_input_Start(struct vlc_player_input *input) { - int ret = input_Start(input->thread); - if (ret != VLC_SUCCESS) - return ret; - input->started = true; - return ret; + if (input->gapless) + { + assert(input->started); + input_WakeGapless(input->thread); + input->gapless = false; + return VLC_SUCCESS; + } + else + { + assert(!input->started); + int ret = input_Start(input->thread); + if (ret != VLC_SUCCESS) + return ret; + input->started = true; + return VLC_SUCCESS; + } } static void @@ -758,7 +794,7 @@ vlc_player_PrepareNextMedia(vlc_player_t *player) { vlc_player_assert_locked(player); - if (!player->media_provider + if (!player->media_provider || player->media_stopped_action != VLC_PLAYER_MEDIA_STOPPED_CONTINUE || player->next_media_requested) return; @@ -770,7 +806,7 @@ vlc_player_PrepareNextMedia(vlc_player_t *player) } static int -vlc_player_OpenNextMedia(vlc_player_t *player) +vlc_player_SwitchNextInput(vlc_player_t *player) { assert(player->input == NULL); @@ -794,8 +830,16 @@ vlc_player_OpenNextMedia(vlc_player_t *player) input_item_Release(player->media); player->media = player->next_media; player->next_media = NULL; + player->gapless_failed = false; + + if (player->next_input) + { + player->input = player->next_input; + player->next_input = NULL; + } + else + player->input = vlc_player_input_New(player, player->media, false); - player->input = vlc_player_input_New(player, player->media); if (!player->input) { input_item_Release(player->media); @@ -803,6 +847,7 @@ vlc_player_OpenNextMedia(vlc_player_t *player) ret = VLC_ENOMEM; } vlc_player_SendEvent(player, on_current_media_changed, player->media); + return ret; } @@ -983,7 +1028,6 @@ vlc_player_input_HandleState(struct vlc_player_input *input, { case VLC_PLAYER_STATE_STOPPED: assert(!input->started); - assert(input != player->input); if (input->titles) { @@ -993,6 +1037,11 @@ vlc_player_input_HandleState(struct vlc_player_input *input, vlc_player_SendEvent(player, on_titles_changed, NULL); } + if (input->gapless) + return; + + assert(input != player->input); + if (input->error != VLC_PLAYER_ERROR_NONE) player->error_count++; else @@ -1002,12 +1051,19 @@ vlc_player_input_HandleState(struct vlc_player_input *input, if (!player->deleting && player->media_stopped_action != VLC_PLAYER_MEDIA_STOPPED_STOP) - vlc_player_OpenNextMedia(player); + vlc_player_SwitchNextInput(player); if (player->input) { assert(player->started); - vlc_player_input_Start(player->input); + bool gapless = player->input->gapless; + int ret = vlc_player_input_Start(player->input); + if (gapless) + { + assert(ret == VLC_SUCCESS); + vlc_player_SendNewEvents(player, player->input, input); + } + vlc_player_PrepareNextMedia(player); } else { @@ -1020,17 +1076,24 @@ vlc_player_input_HandleState(struct vlc_player_input *input, break; case VLC_PLAYER_STATE_STOPPING: input->started = false; - if (input != player->input) - break; - player->input = NULL; + if (input == player->input) + { + player->input = NULL; - if (player->started) + if (player->started) + { + vlc_player_PrepareNextMedia(player); + if (!player->next_media) + player->started = false; + } + send_event = !player->started; + } + else if (input == player->next_input) { - vlc_player_PrepareNextMedia(player); - if (!player->next_media) - player->started = false; + player->next_input = NULL; + player->gapless_failed = true; } - send_event = !player->started; + break; case VLC_PLAYER_STATE_STARTED: case VLC_PLAYER_STATE_PLAYING: @@ -2192,6 +2255,21 @@ vlc_player_HandleAtoBLoop(vlc_player_t *player) vlc_player_SetPosition(player, input->abloop_state[0].pos); } +static void +vlc_player_StartNextGaplessIfNeeded(vlc_player_t *player) +{ + struct vlc_player_input *input = vlc_player_get_input_locked(player); + + /* Preload the next input, if any and in gapless mode, 3 seconds before the + * end of the current input */ + if (input && player->gapless_enabled && player->started + && !player->next_input && !player->gapless_failed && player->next_media + && !player->renderer && input->length != VLC_TICK_INVALID + && input->length - input->time <= OPEN_NEXT_DELAY) + player->next_input = + vlc_player_input_New(player, player->next_media, true); +} + static void input_thread_Events(input_thread_t *input_thread, const struct vlc_input_event *event, void *user_data) @@ -2230,6 +2308,8 @@ input_thread_Events(input_thread_t *input_thread, input->position = event->position.percentage; if (!input->gapless) { + vlc_player_StartNextGaplessIfNeeded(player); + vlc_player_SendEvent(player, on_position_changed, input->time, input->position); @@ -2245,8 +2325,12 @@ input_thread_Events(input_thread_t *input_thread, { input->length = event->length; if (!input->gapless) + { + vlc_player_StartNextGaplessIfNeeded(player); + vlc_player_SendEvent(player, on_length_changed, input->length); + } } break; case INPUT_EVENT_PROGRAM: @@ -2301,9 +2385,19 @@ input_thread_Events(input_thread_t *input_thread, break; case INPUT_EVENT_SUBITEMS: if (!input->gapless) + { vlc_player_SendEvent(player, on_media_subitems_changed, input_GetItem(input->thread), event->subitems); - input_item_node_Delete(event->subitems); + input_item_node_Delete(event->subitems); + } + else + { + /* Save subitems in order to send them when the input is + * current */ + if (input->subitems) + input_item_node_Delete(input->subitems); + input->subitems = event->subitems; + } break; case INPUT_EVENT_DEAD: if (input->started) /* Can happen with early input_thread fails */ @@ -2329,6 +2423,110 @@ input_thread_Events(input_thread_t *input_thread, vlc_mutex_unlock(&player->lock); } +static void +vlc_player_SendNewEvents(vlc_player_t *player, + struct vlc_player_input *input, + struct vlc_player_input *old_input) +{ + vlc_player_SendEvent(player, on_rate_changed, input->rate); + + vlc_player_SendEvent(player, on_capabilities_changed, + old_input->capabilities, input->capabilities); + + vlc_player_SendEvent(player, on_length_changed, + input->length); + + vlc_player_SendEvent(player, on_position_changed, input->time, + input->position); + + vlc_player_SendEvent(player, on_statistics_changed, + &input->stats); + + vlc_player_SendEvent(player, on_buffering_changed, 0.0f); + vlc_player_SendEvent(player, on_buffering_changed, input->cache); + + if (old_input->signal_quality != input->signal_quality + && old_input->signal_strength != input->signal_strength) + vlc_player_SendEvent(player, on_signal_changed, + input->signal_quality, + input->signal_strength); + + vlc_player_SendEvent(player, on_media_meta_changed, + input_GetItem(input->thread)); + + if (old_input->teletext_page != input->teletext_page) + vlc_player_SendEvent(player, on_teletext_page_changed, + input->teletext_page); + + if (old_input->teletext_transparent != input->teletext_transparent) + vlc_player_SendEvent(player, on_teletext_transparency_changed, + input->teletext_transparent); + + + { + vlc_player_program_vector *vec = &input->program_vector; + struct vlc_player_program *prgm; + vlc_vector_foreach(prgm, vec) + { + vlc_player_SendEvent(player, on_program_list_changed, + VLC_PLAYER_LIST_ADDED, prgm); + if (prgm->selected) + vlc_player_SendEvent(player, on_program_selection_changed, + -1, prgm->group_id); + } + } + + enum es_format_category_e cats[] = { VIDEO_ES, AUDIO_ES, SPU_ES }; + for (size_t i = 0; i < ARRAY_SIZE(cats); ++i) + { + enum es_format_category_e cat = cats[i]; + vlc_player_track_vector *vec = + vlc_player_input_GetTrackVector(input, cat); + struct vlc_player_track_priv *trackpriv; + vlc_vector_foreach(trackpriv, vec) + { + vlc_player_SendEvent(player, on_track_list_changed, + VLC_PLAYER_LIST_ADDED, &trackpriv->t); + if (trackpriv->t.selected) + vlc_player_SendEvent(player, on_track_selection_changed, + NULL, trackpriv->t.es_id); + } + } + + if (input->teletext_menu) + { + vlc_player_SendEvent(player, on_teletext_menu_changed, true); + if (input->teletext_enabled) + vlc_player_SendEvent(player, on_teletext_enabled_changed, + input->teletext_enabled); + } + + if (input->titles) + { + const struct vlc_player_title *title = + &input->titles->array[input->title_selected]; + const struct vlc_player_chapter *chapter = + &title->chapters[input->chapter_selected]; + + vlc_player_SendEvent(player, on_titles_changed, input->titles); + + vlc_player_SendEvent(player, on_title_selection_changed, title, + input->title_selected); + + vlc_player_SendEvent(player, on_chapter_selection_changed, title, + input->title_selected, chapter, + input->chapter_selected); + } + + if (input->subitems) + { + vlc_player_SendEvent(player, on_media_subitems_changed, + input_GetItem(input->thread), input->subitems); + input_item_node_Delete(input->subitems); + input->subitems = NULL; + } +} + void vlc_player_Lock(vlc_player_t *player) { @@ -2382,6 +2580,11 @@ static void vlc_player_ReleaseNextMedia(vlc_player_t *player) { vlc_player_assert_locked(player); + if (player->next_input) + { + vlc_player_destructor_AddInput(player, player->next_input); + player->next_input = NULL; + } if (player->next_media) { input_item_Release(player->next_media); @@ -2430,7 +2633,7 @@ vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media) } /* We can switch to the next media directly */ - return vlc_player_OpenNextMedia(player); + return vlc_player_SwitchNextInput(player); } input_item_t * @@ -2516,6 +2719,9 @@ vlc_player_InvalidateNextMedia(vlc_player_t *player) { vlc_player_ReleaseNextMedia(player); + vlc_player_PrepareNextMedia(player); + + vlc_player_StartNextGaplessIfNeeded(player); } int @@ -2545,7 +2751,7 @@ vlc_player_Start(vlc_player_t *player) if (!player->input) { /* Possible if the player was stopped by the user */ - player->input = vlc_player_input_New(player, player->media); + player->input = vlc_player_input_New(player, player->media, false); if (!player->input) return VLC_ENOMEM; @@ -2562,6 +2768,7 @@ vlc_player_Start(vlc_player_t *player) if (ret == VLC_SUCCESS) { player->started = true; + vlc_player_PrepareNextMedia(player); vlc_player_vout_OSDIcon(player, OSD_PLAY_ICON); } return ret; @@ -2595,6 +2802,13 @@ vlc_player_SetMediaStoppedAction(vlc_player_t *player, vlc_player_SendEvent(player, on_media_stopped_action_changed, action); } +void +vlc_player_SetGaplessEnabled(vlc_player_t *player, bool enabled) +{ + vlc_player_assert_locked(player); + player->gapless_enabled = enabled; +} + void vlc_player_SetStartPaused(vlc_player_t *player, bool start_paused) { @@ -3712,6 +3926,9 @@ vlc_player_Delete(vlc_player_t *player) if (player->input) vlc_player_destructor_AddInput(player, player->input); + if (player->next_input) + vlc_player_destructor_AddInput(player, player->next_input); + player->deleting = true; vlc_cond_signal(&player->destructor.wait); @@ -3765,13 +3982,15 @@ vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type, player->start_paused = false; player->pause_on_cork = false; player->corked = false; + player->gapless_enabled = false; player->renderer = NULL; player->media_provider = media_provider; player->media_provider_data = media_provider_data; player->media = NULL; - player->input = NULL; + player->input = player->next_input = NULL; player->global_state = VLC_PLAYER_STATE_STOPPED; player->started = false; + player->gapless_failed = false; player->error_count = 0; diff --git a/src/libvlccore.sym b/src/libvlccore.sym index c3f7dbbd1f..bea0205d1e 100644 --- a/src/libvlccore.sym +++ b/src/libvlccore.sym @@ -849,6 +849,7 @@ vlc_player_SetAtoBLoop vlc_player_SetCategoryDelay vlc_player_SetCurrentMedia vlc_player_SetEsIdDelay +vlc_player_SetGaplessEnabled vlc_player_SetMediaStoppedAction vlc_player_SetRecordingEnabled vlc_player_SetRenderer -- GitLab From e5edff13b1adbe6688f052aece6fa2057046b70a Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 16 May 2019 16:02:06 +0200 Subject: [PATCH 14/15] test: player: test gapless --- test/src/input/player.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/test/src/input/player.c b/test/src/input/player.c index de916e7a8a..ae00dab150 100644 --- a/test/src/input/player.c +++ b/test/src/input/player.c @@ -952,6 +952,7 @@ test_end(struct ctx *ctx) player_set_rate(ctx, 1.0f); vlc_player_SetStartPaused(player, false); + vlc_player_SetGaplessEnabled(player, false); ctx_reset(ctx); } @@ -1573,17 +1574,37 @@ test_seeks(struct ctx *ctx) free(media_name); \ } while(0) +#define TEST_NEXT_MEDIA_GAPLESS 0x1 +#define TEST_NEXT_MEDIA_WITH_ERRORS 0x2 + static void -test_next_media(struct ctx *ctx) +test_next_media(struct ctx *ctx, int flag) { - test_log("next_media\n"); - const char *media_names[] = { "media1", "media2", "media3" }; - const size_t media_count = ARRAY_SIZE(media_names); + const bool gapless = flag & TEST_NEXT_MEDIA_GAPLESS; + const bool with_errors = flag & TEST_NEXT_MEDIA_WITH_ERRORS; + + test_log("next_media%s%s\n", gapless ? " with gapless" : "", + with_errors ? " with errors" : ""); + + static const char *media_names_errors[] = + { "media1", "error", "media2", "error", "media3" }; + static const char *media_names_no_errors[] = + { "media1", "media2", "media3" }; + + const char *const *media_names = with_errors ? media_names_errors : + media_names_no_errors; + const size_t media_count = with_errors ? ARRAY_SIZE(media_names_errors) : + ARRAY_SIZE(media_names_no_errors); struct media_params params = DEFAULT_MEDIA_PARAMS(VLC_TICK_FROM_MS(100)); + vlc_player_SetGaplessEnabled(ctx->player, gapless); + for (size_t i = 0; i < media_count; ++i) + { + params.error = strcmp(media_names[i], "error") == 0; player_set_next_mock_media(ctx, media_names[i], ¶ms); + } player_set_rate(ctx, 4.f); player_start(ctx); @@ -1854,7 +1875,10 @@ main(void) test_outputs(&ctx); /* Must be the first test */ test_set_current_media(&ctx); - test_next_media(&ctx); + test_next_media(&ctx, 0); + test_next_media(&ctx, TEST_NEXT_MEDIA_GAPLESS); + test_next_media(&ctx, TEST_NEXT_MEDIA_WITH_ERRORS); + test_next_media(&ctx, TEST_NEXT_MEDIA_GAPLESS|TEST_NEXT_MEDIA_WITH_ERRORS); test_seeks(&ctx); test_pause(&ctx); test_capabilities_pause(&ctx); -- GitLab From f6640c6fba41885040705bae6cf9e5baae223d6e Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Thu, 16 May 2019 16:02:22 +0200 Subject: [PATCH 15/15] lib: add "gapless" option (enabled by default) --- src/interface/interface.c | 2 ++ src/libvlc-module.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/interface/interface.c b/src/interface/interface.c index ddb63e840f..e8be9d1fd2 100644 --- a/src/interface/interface.c +++ b/src/interface/interface.c @@ -79,6 +79,7 @@ PlaylistConfigureFromVariables(vlc_playlist_t *playlist, vlc_object_t *obj) bool start_paused = var_InheritBool(obj, "start-paused"); bool playlist_cork = var_InheritBool(obj, "playlist-cork"); + bool gapless = var_InheritBool(obj, "gapless"); vlc_playlist_Lock(playlist); vlc_playlist_SetPlaybackOrder(playlist, order); @@ -91,6 +92,7 @@ PlaylistConfigureFromVariables(vlc_playlist_t *playlist, vlc_object_t *obj) vlc_player_SetMediaStoppedAction(player, media_stopped_action); vlc_player_SetStartPaused(player, start_paused); vlc_player_SetPauseOnCork(player, playlist_cork); + vlc_player_SetGaplessEnabled(player, gapless); vlc_playlist_Unlock(playlist); } diff --git a/src/libvlc-module.c b/src/libvlc-module.c index f5de78bcae..d8e96b6302 100644 --- a/src/libvlc-module.c +++ b/src/libvlc-module.c @@ -1269,6 +1269,11 @@ static const char *const psz_recursive_list_text[] = { "If pending audio communication is detected, playback will be paused " \ "automatically." ) +#define GAPLESS_TEXT N_("Gapless playback") +#define GAPLESS_LONGTEXT N_( \ + "Preload the next media from the playlist in order to have uninterrupted " \ + "playback of consecutive audio tracks.") + #define ML_TEXT N_("Use media library") #define ML_LONGTEXT N_( \ "The media library is automatically saved and reloaded each time you " \ @@ -2185,6 +2190,7 @@ vlc_module_begin () add_bool( "playlist-autostart", true, AUTOSTART_TEXT, AUTOSTART_LONGTEXT, false ) add_bool( "playlist-cork", true, CORK_TEXT, CORK_LONGTEXT, false ) + add_bool( "gapless", true, GAPLESS_TEXT, GAPLESS_LONGTEXT, false ) #if defined(_WIN32) || defined(HAVE_DBUS) || defined(__OS2__) add_bool( "one-instance", 0, ONEINSTANCE_TEXT, ONEINSTANCE_LONGTEXT, true ) -- GitLab