qtdemux: Try to expose whenever got new moov or new stream-start
authorSeungha Yang <seungha.yang@navercorp.com>
Tue, 8 May 2018 11:26:41 +0000 (20:26 +0900)
committerEdward Hervey <bilboed@bilboed.com>
Thu, 10 May 2018 06:09:20 +0000 (08:09 +0200)
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
gst/isomp4/qtdemux.h

index e8d8ca8..dc153e9 100644 (file)
@@ -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;
index 67b0543..2615c10 100644 (file)
@@ -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