From d72a7c038c6a1be3f865afa9b50f2878d02d87ba Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Tue, 8 May 2018 20:26:41 +0900 Subject: [PATCH] qtdemux: Try to expose whenever got new moov or new stream-start Whenever got new moov or new stream-start, demux will try to expose new pad by following rule. Comparing stream-id in the current moov with previous one, then * If matched stream-id is found from previous one, reuse existing pad (most common case) * Otherwise, expose new pad with new stream-start * No more used stream will be freed https://bugzilla.gnome.org/show_bug.cgi?id=684790 --- gst/isomp4/qtdemux.c | 240 ++++++++++++++++++++++++++++++++++--------- gst/isomp4/qtdemux.h | 4 + 2 files changed, 198 insertions(+), 46 deletions(-) diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index e8d8ca8338..dc153e9ae1 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -2146,7 +2146,10 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) if (hard) { g_list_free_full (qtdemux->active_streams, (GDestroyNotify) gst_qtdemux_stream_free); + g_list_free_full (qtdemux->old_streams, + (GDestroyNotify) gst_qtdemux_stream_free); qtdemux->active_streams = NULL; + qtdemux->old_streams = NULL; qtdemux->n_streams = 0; qtdemux->n_video_streams = 0; qtdemux->n_audio_streams = 0; @@ -2164,6 +2167,9 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) g_ptr_array_free (qtdemux->protection_system_ids, TRUE); qtdemux->protection_system_ids = NULL; } + qtdemux->streams_aware = GST_OBJECT_PARENT (qtdemux) + && GST_OBJECT_FLAG_IS_SET (GST_OBJECT_PARENT (qtdemux), + GST_BIN_FLAG_STREAMS_AWARE); } else if (qtdemux->mss_mode) { gst_flow_combiner_reset (qtdemux->flowcombiner); g_list_foreach (qtdemux->active_streams, @@ -2439,6 +2445,15 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, { res = TRUE; gst_event_unref (event); + + /* Drain all the buffers */ + gst_qtdemux_process_adapter (demux, TRUE); + gst_qtdemux_reset (demux, FALSE); + /* We expect new moov box after new stream-start event */ + demux->old_streams = + g_list_concat (demux->old_streams, demux->active_streams); + demux->active_streams = NULL; + goto drop; } default: @@ -6791,20 +6806,16 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) demux->last_moov_offset = demux->offset; + /* Update streams with new moov */ + demux->old_streams = + g_list_concat (demux->old_streams, demux->active_streams); + demux->active_streams = NULL; + qtdemux_parse_moov (demux, data, demux->neededbytes); qtdemux_node_dump (demux, demux->moov_node); qtdemux_parse_tree (demux); qtdemux_prepare_streams (demux); - if (!demux->got_moov) - qtdemux_expose_streams (demux); - else { - - for (iter = demux->active_streams; iter; - iter = g_list_next (iter)) { - gst_qtdemux_configure_stream (demux, - QTDEMUX_STREAM (iter->data)); - } - } + qtdemux_expose_streams (demux); demux->got_moov = TRUE; gst_qtdemux_check_send_pending_segment (demux); @@ -12046,6 +12057,12 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux) GST_DEBUG_OBJECT (qtdemux, "no samples for stream; discarding"); gst_qtdemux_remove_stream (qtdemux, stream); continue; + } else if (stream->track_id == qtdemux->chapters_track_id && + (stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl)) { + /* TODO - parse chapters track and expose it as GstToc; For now just ignore it + so that it doesn't look like a subtitle track */ + gst_qtdemux_remove_stream (qtdemux, stream); + continue; } /* parse the initial sample for use in setting the frame rate cap */ @@ -12064,59 +12081,190 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux) return ret; } -static GstFlowReturn -qtdemux_expose_streams (GstQTDemux * qtdemux) +static GList * +_stream_in_list (GList * list, QtDemuxStream * stream) { - GSList *oldpads = NULL; - GSList *iter; - GList *walk, *next; + GList *iter; - GST_DEBUG_OBJECT (qtdemux, "exposing streams"); + for (iter = list; iter; iter = g_list_next (iter)) { + QtDemuxStream *tmp = QTDEMUX_STREAM (iter->data); + if (!g_strcmp0 (tmp->stream_id, stream->stream_id)) + return iter; + } - for (walk = qtdemux->active_streams; walk; walk = next) { - QtDemuxStream *stream = QTDEMUX_STREAM (walk->data); - GstPad *oldpad = stream->pad; - GstTagList *list; + return NULL; +} - next = walk->next; +static gboolean +qtdemux_is_streams_update (GstQTDemux * qtdemux) +{ + GList *new, *old; + + g_return_val_if_fail (qtdemux->active_streams != NULL, FALSE); + + /* streams in list are sorted in track-id order */ + for (new = qtdemux->active_streams, old = qtdemux->old_streams; new && old; + new = g_list_next (new), old = g_list_next (old)) { + + /* Different stream-id, updated */ + if (g_strcmp0 (QTDEMUX_STREAM (new->data)->stream_id, + QTDEMUX_STREAM (old->data)->stream_id)) + return TRUE; + } + + /* Different length, updated */ + if (new != NULL || old != NULL) + return TRUE; + + return FALSE; +} + +static gboolean +qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux, + QtDemuxStream * oldstream, QtDemuxStream * newstream) +{ + /* Connect old stream's srcpad to new stream */ + newstream->pad = oldstream->pad; + oldstream->pad = NULL; + + /* unset new_stream to prevent stream-start event */ + newstream->new_stream = FALSE; + + return gst_qtdemux_configure_stream (qtdemux, newstream); +} + +static gboolean +qtdemux_update_streams (GstQTDemux * qtdemux) +{ + GList *iter, *next; + + /* At below, figure out which stream in active_streams has identical stream-id + * with that of in old_streams. If there is matching stream-id, + * corresponding newstream will not be exposed again, + * but demux will reuse srcpad of matched old stream + * + * active_streams : newly created streams from the latest moov + * old_streams : existing streams (belong to previous moov) + */ + + /* Count n_streams again */ + qtdemux->n_streams = 0; + + for (iter = qtdemux->active_streams;iter; iter = next) { + GList *tmp; + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + + next = iter->next; GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc)); - if ((stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl) && - stream->track_id == qtdemux->chapters_track_id) { - /* TODO - parse chapters track and expose it as GstToc; For now just ignore it - so that it doesn't look like a subtitle track */ - gst_qtdemux_remove_stream (qtdemux, stream); - continue; + qtdemux->n_streams++; + + if (qtdemux->streams_aware + && (tmp = _stream_in_list (qtdemux->old_streams, stream)) != NULL + && QTDEMUX_STREAM (tmp->data)->pad) { + QtDemuxStream *oldstream = QTDEMUX_STREAM (tmp->data); + + GST_DEBUG_OBJECT (qtdemux, "Reuse track-id %d", oldstream->track_id); + + if (!qtdemux_reuse_and_configure_stream (qtdemux, oldstream, stream)) + return FALSE; + + qtdemux->old_streams = g_list_remove (qtdemux->old_streams, oldstream); + gst_qtdemux_stream_free (oldstream); + } else { + GstTagList *list; + + /* now we have all info and can expose */ + list = stream->stream_tags; + stream->stream_tags = NULL; + if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + return FALSE; + + /* New segment will be exposed at _update_segment in case of pull mode */ + if (!qtdemux->pending_newsegment && !qtdemux->pullbased) { + qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (qtdemux->pending_newsegment, + qtdemux->segment_seqnum); + } + } + } + + return TRUE; +} + +static GstFlowReturn +qtdemux_expose_streams (GstQTDemux * qtdemux) +{ + GList *iter, *next; + + GST_DEBUG_OBJECT (qtdemux, "exposing streams"); + + if (!qtdemux_is_streams_update (qtdemux)) { + GList *new, *old; + + GST_DEBUG_OBJECT (qtdemux, "Reuse all streams"); + for (new = qtdemux->active_streams, old = qtdemux->old_streams; new && old; + new = g_list_next (new), old = g_list_next (old)) { + if (!qtdemux_reuse_and_configure_stream (qtdemux, + QTDEMUX_STREAM (old->data), QTDEMUX_STREAM (new->data))) + return GST_FLOW_ERROR; } - /* now we have all info and can expose */ - list = stream->stream_tags; - stream->stream_tags = NULL; - if (oldpad) - oldpads = g_slist_prepend (oldpads, oldpad); - if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + g_list_free_full (qtdemux->old_streams, + (GDestroyNotify) gst_qtdemux_stream_free); + qtdemux->old_streams = NULL; + + return GST_FLOW_OK; + } + + if (qtdemux->streams_aware) { + if (!qtdemux_update_streams (qtdemux)) return GST_FLOW_ERROR; + } else { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + GstTagList *list; + + /* now we have all info and can expose */ + list = stream->stream_tags; + stream->stream_tags = NULL; + if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + return GST_FLOW_ERROR; + + /* New segment will be exposed at _update_segment in case of pull mode */ + if (!qtdemux->pending_newsegment && !qtdemux->pullbased) { + qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (qtdemux->pending_newsegment, + qtdemux->segment_seqnum); + } + } } gst_qtdemux_guess_bitrate (qtdemux); gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux)); - for (iter = oldpads; iter; iter = g_slist_next (iter)) { - GstPad *oldpad = iter->data; - GstEvent *event; + /* If we have still old_streams, it's no more used stream */ + for (iter = qtdemux->old_streams; iter; iter = next) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + next = g_list_next (iter); - event = gst_event_new_eos (); - if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) - gst_event_set_seqnum (event, qtdemux->segment_seqnum); + if (stream->pad) { + GstEvent *event; - gst_pad_push_event (oldpad, event); - gst_pad_set_active (oldpad, FALSE); - gst_element_remove_pad (GST_ELEMENT (qtdemux), oldpad); - gst_flow_combiner_remove_pad (qtdemux->flowcombiner, oldpad); - gst_object_unref (oldpad); + event = gst_event_new_eos (); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + + gst_pad_push_event (stream->pad, event); + } + + qtdemux->old_streams = g_list_remove (qtdemux->old_streams, stream); + gst_qtdemux_stream_free (stream); } /* check if we should post a redirect in case there is a single trak @@ -12135,8 +12283,8 @@ qtdemux_expose_streams (GstQTDemux * qtdemux) qtdemux->posted_redirect = TRUE; } - for (walk = qtdemux->active_streams; walk; walk = g_list_next (walk)) { - qtdemux_do_allocation (qtdemux, QTDEMUX_STREAM (walk->data)); + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + qtdemux_do_allocation (qtdemux, QTDEMUX_STREAM (iter->data)); } qtdemux->exposed = TRUE; diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h index 67b0543be4..2615c10ffd 100644 --- a/gst/isomp4/qtdemux.h +++ b/gst/isomp4/qtdemux.h @@ -73,6 +73,7 @@ struct _GstQTDemux { /* list of QtDemuxStream */ GList *active_streams; + GList *old_streams; gint n_streams; gint n_video_streams; @@ -152,6 +153,9 @@ struct _GstQTDemux { guint8 *cenc_aux_info_sizes; guint32 cenc_aux_sample_count; + /* Whether the parent bin is streams-aware, meaning we can + * add/remove streams at any point in time */ + gboolean streams_aware; /* * ALL VARIABLES BELOW ARE ONLY USED IN PUSH-BASED MODE