qtdemux: Determine duration with reference to track header duration
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / qtdemux.c
index 5156873..ca78b20 100644 (file)
@@ -76,7 +76,7 @@
 #include <gst/math-compat.h>
 
 #ifdef HAVE_ZLIB
-# include <zlib.h>
+#include <zlib.h>
 #endif
 
 /* max. size considered 'sane' for non-mdat atoms */
 
 #define ABSDIFF(x, y) ( (x) > (y) ? ((x) - (y)) : ((y) - (x)) )
 
-#define QTDEMUX_FIRST_STREAM(demux) ((QtDemuxStream *)(demux)->active_streams \
-  ? (QtDemuxStream *)(demux)->active_streams->data : NULL)
 #define QTDEMUX_STREAM(s) ((QtDemuxStream *)(s))
+#define QTDEMUX_N_STREAMS(demux) ((demux)->active_streams->len)
+#define QTDEMUX_NTH_STREAM(demux,idx) \
+   QTDEMUX_STREAM(g_ptr_array_index((demux)->active_streams,idx))
+#define QTDEMUX_NTH_OLD_STREAM(demux,idx) \
+   QTDEMUX_STREAM(g_ptr_array_index((demux)->old_streams,idx))
 
 GST_DEBUG_CATEGORY (qtdemux_debug);
 #define GST_CAT_DEFAULT qtdemux_debug
@@ -119,6 +122,34 @@ struct _QtDemuxSample
   gboolean keyframe;            /* TRUE when this packet is a keyframe */
 };
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+typedef struct _QtDemuxSphericalMetadata QtDemuxSphericalMetadata;
+
+struct _QtDemuxSphericalMetadata
+{
+  gboolean is_spherical;
+  gboolean is_stitched;
+  char *stitching_software;
+  char *projection_type;
+  char *stereo_mode;
+  int source_count;
+  int init_view_heading;
+  int init_view_pitch;
+  int init_view_roll;
+  int timestamp;
+  int full_pano_width_pixels;
+  int full_pano_height_pixels;
+  int cropped_area_image_width;
+  int cropped_area_image_height;
+  int cropped_area_left;
+  int cropped_area_top;
+  QTDEMUX_AMBISONIC_TYPE ambisonic_type;
+  QTDEMUX_AMBISONIC_FORMAT ambisonic_format;
+  QTDEMUX_AMBISONIC_ORDER ambisonic_order;
+};
+
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
 /* Macros for converting to/from timescale */
 #define QTSTREAMTIME_TO_GSTTIME(stream, value) (gst_util_uint64_scale((value), GST_SECOND, (stream)->timescale))
 #define GSTTIME_TO_QTSTREAMTIME(stream, value) (gst_util_uint64_scale((value), (stream)->timescale, GST_SECOND))
@@ -299,6 +330,10 @@ struct _QtDemuxStream
   /* track id */
   guint track_id;
 
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  guint64 tkhd_duration;
+#endif
+
   /* duration/scale */
   guint64 duration;             /* in timescale units */
   guint32 timescale;
@@ -346,6 +381,8 @@ struct _QtDemuxStream
 
   /* buffer needs some custom processing, e.g. subtitles */
   gboolean need_process;
+  /* buffer needs potentially be split, e.g. CEA608 subtitles */
+  gboolean need_split;
 
   /* current position */
   guint32 segment_index;
@@ -446,6 +483,8 @@ struct _QtDemuxStream
   guint32 protection_scheme_version;
   gpointer protection_scheme_info;      /* specific to the protection scheme */
   GQueue protection_scheme_event_queue;
+
+  gint ref_count;               /* atomic */
 };
 
 /* Contains properties and cryptographic info for a set of samples from a
@@ -484,6 +523,8 @@ static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node,
 
 static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux);
 
+static void gst_qtdemux_check_send_pending_segment (GstQTDemux * demux);
+
 static GstStaticPadTemplate gst_qtdemux_sink_template =
     GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -539,6 +580,8 @@ static GstFlowReturn gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent,
     GstBuffer * inbuf);
 static gboolean gst_qtdemux_handle_sink_event (GstPad * pad, GstObject * parent,
     GstEvent * event);
+static gboolean gst_qtdemux_handle_sink_query (GstPad * pad, GstObject * parent,
+    GstQuery * query);
 static gboolean gst_qtdemux_setcaps (GstQTDemux * qtdemux, GstCaps * caps);
 static gboolean gst_qtdemux_configure_stream (GstQTDemux * qtdemux,
     QtDemuxStream * stream);
@@ -546,9 +589,8 @@ static void gst_qtdemux_stream_check_and_change_stsd_index (GstQTDemux * demux,
     QtDemuxStream * stream);
 static GstFlowReturn gst_qtdemux_process_adapter (GstQTDemux * demux,
     gboolean force);
-static GstClockTime gst_qtdemux_streams_get_first_sample_ts (GstQTDemux *
-    demux);
-static GstClockTime gst_qtdemux_streams_have_samples (GstQTDemux * demux);
+
+static void gst_qtdemux_check_seekability (GstQTDemux * demux);
 
 static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux,
     const guint8 * buffer, guint length);
@@ -577,13 +619,12 @@ static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux,
 static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux,
     QtDemuxStream * stream, guint32 n);
 static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux);
-static void gst_qtdemux_stream_free (QtDemuxStream * stream);
+static QtDemuxStream *gst_qtdemux_stream_ref (QtDemuxStream * stream);
+static void gst_qtdemux_stream_unref (QtDemuxStream * stream);
 static void gst_qtdemux_stream_clear (QtDemuxStream * stream);
-static void gst_qtdemux_remove_stream (GstQTDemux * qtdemux,
-    QtDemuxStream * stream);
 static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux);
-static void qtdemux_do_allocation (GstQTDemux * qtdemux,
-    QtDemuxStream * stream);
+static void qtdemux_do_allocation (QtDemuxStream * stream,
+    GstQTDemux * qtdemux);
 static gboolean gst_qtdemux_activate_segment (GstQTDemux * qtdemux,
     QtDemuxStream * stream, guint32 seg_idx, GstClockTime offset);
 static gboolean gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux,
@@ -604,6 +645,10 @@ static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
 static void qtdemux_gst_structure_free (GstStructure * gststructure);
 static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+static void gst_tag_register_spherical_tags (void);
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
 static void
 gst_qtdemux_class_init (GstQTDemuxClass * klass)
 {
@@ -626,6 +671,10 @@ gst_qtdemux_class_init (GstQTDemuxClass * klass)
 
   gst_tag_register_musicbrainz_tags ();
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  gst_tag_register_spherical_tags ();
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   gst_element_class_add_static_pad_template (gstelement_class,
       &gst_qtdemux_sink_template);
   gst_element_class_add_static_pad_template (gstelement_class,
@@ -653,6 +702,7 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
       qtdemux_sink_activate_mode);
   gst_pad_set_chain_function (qtdemux->sinkpad, gst_qtdemux_chain);
   gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event);
+  gst_pad_set_query_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_query);
   gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
 
   qtdemux->adapter = gst_adapter_new ();
@@ -660,6 +710,38 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
   qtdemux->flowcombiner = gst_flow_combiner_new ();
   g_mutex_init (&qtdemux->expose_lock);
 
+  qtdemux->active_streams = g_ptr_array_new_with_free_func
+      ((GDestroyNotify) gst_qtdemux_stream_unref);
+  qtdemux->old_streams = g_ptr_array_new_with_free_func
+      ((GDestroyNotify) gst_qtdemux_stream_unref);
+
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  qtdemux->spherical_metadata = (QtDemuxSphericalMetadata *)
+      malloc (sizeof (QtDemuxSphericalMetadata));
+
+  if (qtdemux->spherical_metadata) {
+    qtdemux->spherical_metadata->is_spherical = FALSE;
+    qtdemux->spherical_metadata->is_stitched = FALSE;
+    qtdemux->spherical_metadata->stitching_software = NULL;
+    qtdemux->spherical_metadata->projection_type = NULL;
+    qtdemux->spherical_metadata->stereo_mode = NULL;
+    qtdemux->spherical_metadata->source_count = 0;
+    qtdemux->spherical_metadata->init_view_heading = 0;
+    qtdemux->spherical_metadata->init_view_pitch = 0;
+    qtdemux->spherical_metadata->init_view_roll = 0;
+    qtdemux->spherical_metadata->timestamp = 0;
+    qtdemux->spherical_metadata->full_pano_width_pixels = 0;
+    qtdemux->spherical_metadata->full_pano_height_pixels = 0;
+    qtdemux->spherical_metadata->cropped_area_image_width = 0;
+    qtdemux->spherical_metadata->cropped_area_image_height = 0;
+    qtdemux->spherical_metadata->cropped_area_left = 0;
+    qtdemux->spherical_metadata->cropped_area_top = 0;
+    qtdemux->spherical_metadata->ambisonic_type = QTDEMUX_AMBISONIC_TYPE_UNKNOWN;
+    qtdemux->spherical_metadata->ambisonic_format = QTDEMUX_AMBISONIC_FORMAT_UNKNOWN;
+    qtdemux->spherical_metadata->ambisonic_order = QTDEMUX_AMBISONIC_ORDER_UNKNOWN;
+  }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE);
 
   gst_qtdemux_reset (qtdemux, TRUE);
@@ -670,6 +752,20 @@ gst_qtdemux_dispose (GObject * object)
 {
   GstQTDemux *qtdemux = GST_QTDEMUX (object);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  if (qtdemux->spherical_metadata) {
+    if (qtdemux->spherical_metadata->stitching_software)
+      free(qtdemux->spherical_metadata->stitching_software);
+    if (qtdemux->spherical_metadata->projection_type)
+      free(qtdemux->spherical_metadata->projection_type);
+    if (qtdemux->spherical_metadata->stereo_mode)
+      free(qtdemux->spherical_metadata->stereo_mode);
+
+    free(qtdemux->spherical_metadata);
+    qtdemux->spherical_metadata = NULL;
+  }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   if (qtdemux->adapter) {
     g_object_unref (G_OBJECT (qtdemux->adapter));
     qtdemux->adapter = NULL;
@@ -684,6 +780,9 @@ gst_qtdemux_dispose (GObject * object)
   qtdemux->cenc_aux_info_sizes = NULL;
   g_mutex_clear (&qtdemux->expose_lock);
 
+  g_ptr_array_free (qtdemux->active_streams, TRUE);
+  g_ptr_array_free (qtdemux->old_streams, TRUE);
+
   G_OBJECT_CLASS (parent_class)->dispose (object);
 }
 
@@ -983,6 +1082,12 @@ gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream)
           stream->stream_tags);
       gst_pad_push_event (stream->pad,
           gst_event_new_tag (gst_tag_list_ref (stream->stream_tags)));
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+      /* post message qtdemux tag (for early recive application) */
+      gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
+            gst_message_new_tag (GST_OBJECT_CAST (qtdemux),
+                  gst_tag_list_copy (stream->stream_tags)));
+#endif
     }
 
     if (G_UNLIKELY (stream->send_global_tags)) {
@@ -1001,14 +1106,14 @@ gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
 {
   gboolean has_valid_stream = FALSE;
   GstEventType etype = GST_EVENT_TYPE (event);
-  GList *iter;
+  guint i;
 
   GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads",
       GST_EVENT_TYPE_NAME (event));
 
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     GstPad *pad;
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     GST_DEBUG_OBJECT (qtdemux, "pushing on track-id %u", stream->track_id);
 
     if ((pad = stream->pad)) {
@@ -1033,34 +1138,6 @@ gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
   }
 }
 
-/* push a pending newsegment event, if any from the streaming thread */
-static void
-gst_qtdemux_push_pending_newsegment (GstQTDemux * qtdemux)
-{
-  if (G_UNLIKELY (qtdemux->need_segment)) {
-    GstClockTime min_ts;
-    GstEvent *newsegment;
-
-    if (!gst_qtdemux_streams_have_samples (qtdemux)) {
-      /* No samples yet, can't decide on segment.start */
-      GST_DEBUG_OBJECT (qtdemux, "No samples yet, postponing segment event");
-      return;
-    }
-
-    min_ts = gst_qtdemux_streams_get_first_sample_ts (qtdemux);
-
-    /* have_samples() above should guarantee we have a valid time */
-    g_assert (GST_CLOCK_TIME_IS_VALID (min_ts));
-
-    qtdemux->segment.start = min_ts;
-    newsegment = gst_event_new_segment (&qtdemux->segment);
-    if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID)
-      gst_event_set_seqnum (newsegment, qtdemux->segment_seqnum);
-    qtdemux->need_segment = FALSE;
-    gst_qtdemux_push_event (qtdemux, newsegment);
-  }
-}
-
 typedef struct
 {
   guint64 media_time;
@@ -1333,13 +1410,13 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time,
 {
   guint64 min_offset;
   gint64 min_byte_offset = -1;
-  GList *iter;
+  guint i;
 
   min_offset = desired_time;
 
   /* for each stream, find the index of the sample in the segment
    * and move back to the previous keyframe. */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     QtDemuxStream *str;
     guint32 index, kindex;
     guint32 seg_idx;
@@ -1349,7 +1426,7 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time,
     QtDemuxSegment *seg;
     gboolean empty_segment = FALSE;
 
-    str = QTDEMUX_STREAM (iter->data);
+    str = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     if (CUR_STREAM (str)->sparse && !use_sparse)
       continue;
@@ -1530,6 +1607,7 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
   }
   GST_OBJECT_UNLOCK (qtdemux);
 
+  qtdemux->segment_seqnum = seqnum;
   /* BYTE seek event */
   event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, byte_cur,
       stop_type, stop);
@@ -1578,7 +1656,7 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment,
     guint32 seqnum, GstSeekFlags flags)
 {
   gint64 desired_offset;
-  GList *iter;
+  guint i;
 
   desired_offset = segment->position;
 
@@ -1608,8 +1686,8 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment,
   /* and set all streams to the final position */
   gst_flow_combiner_reset (qtdemux->flowcombiner);
   qtdemux->segment_seqnum = seqnum;
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     stream->time_position = desired_offset;
     stream->accumulated_base = 0;
@@ -1753,13 +1831,13 @@ no_format:
 static gboolean
 qtdemux_ensure_index (GstQTDemux * qtdemux)
 {
-  GList *iter;
+  guint i;
 
   GST_DEBUG_OBJECT (qtdemux, "collecting all metadata for all streams");
 
   /* Build complete index */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) {
       GST_LOG_OBJECT (qtdemux,
@@ -1787,6 +1865,8 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent,
 #endif
       guint32 seqnum = gst_event_get_seqnum (event);
 
+      qtdemux->received_seek = TRUE;
+
       if (seqnum == qtdemux->segment_seqnum) {
         GST_LOG_OBJECT (pad,
             "Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum);
@@ -1817,7 +1897,8 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent,
       } else if (gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (event))) {
         GST_DEBUG_OBJECT (qtdemux, "Upstream successfully seeked");
         res = TRUE;
-      } else if (qtdemux->state == QTDEMUX_STATE_MOVIE && qtdemux->n_streams
+      } else if (qtdemux->state == QTDEMUX_STATE_MOVIE
+          && QTDEMUX_N_STREAMS (qtdemux)
           && !qtdemux->fragmented) {
         res = gst_qtdemux_do_push_seek (qtdemux, pad, event);
       } else {
@@ -1866,18 +1947,18 @@ gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw,
   gint i, index;
   gint64 time, min_time;
   QtDemuxStream *stream;
-  GList *iter;
+  gint iter;
 
   min_time = -1;
   stream = NULL;
   index = -1;
 
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (iter = 0; iter < QTDEMUX_N_STREAMS (qtdemux); iter++) {
     QtDemuxStream *str;
     gint inc;
     gboolean set_sample;
 
-    str = QTDEMUX_STREAM (iter->data);
+    str = QTDEMUX_NTH_STREAM (qtdemux, iter);
     set_sample = !set;
 
     if (fw) {
@@ -2014,6 +2095,9 @@ _create_stream (GstQTDemux * demux, guint32 track_id)
   stream->stream_tags = gst_tag_list_new_empty ();
   gst_tag_list_set_scope (stream->stream_tags, GST_TAG_SCOPE_STREAM);
   g_queue_init (&stream->protection_scheme_event_queue);
+  stream->ref_count = 1;
+  /* consistent default for push based mode */
+  gst_segment_init (&stream->segment, GST_FORMAT_TIME);
   return stream;
 }
 
@@ -2036,7 +2120,7 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps)
     demux->fragmented = TRUE;
     demux->mss_mode = TRUE;
 
-    if (demux->n_streams > 1) {
+    if (QTDEMUX_N_STREAMS (demux) > 1) {
       /* can't do this, we can only renegotiate for another mss format */
       return FALSE;
     }
@@ -2048,16 +2132,15 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps)
 
       /* TODO update when stream changes during playback */
 
-      if (demux->n_streams == 0) {
+      if (QTDEMUX_N_STREAMS (demux) == 0) {
         stream = _create_stream (demux, 1);
-        demux->active_streams = g_list_append (demux->active_streams, stream);
-        demux->n_streams = 1;
+        g_ptr_array_add (demux->active_streams, stream);
         /* mss has no stsd/stsd entry, use id 0 as default */
         stream->stsd_entries_length = 1;
         stream->stsd_sample_description_id = stream->cur_stsd_entry_index = 0;
         stream->stsd_entries = g_new0 (QtDemuxStreamStsdEntry, 1);
       } else {
-        stream = QTDEMUX_FIRST_STREAM (demux);
+        stream = QTDEMUX_NTH_STREAM (demux, 0);
       }
 
       timescale_v = gst_structure_get_value (structure, "timescale");
@@ -2106,7 +2189,7 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps)
 static void
 gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
 {
-  GList *iter;
+  gint i;
 
   GST_DEBUG_OBJECT (qtdemux, "Resetting demux");
   gst_pad_stop_task (qtdemux->sinkpad);
@@ -2167,21 +2250,19 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
         NULL);
     g_queue_clear (&qtdemux->protection_event_queue);
+
+    qtdemux->received_seek = FALSE;
+    qtdemux->first_moof_already_parsed = FALSE;
   }
   qtdemux->offset = 0;
   gst_adapter_clear (qtdemux->adapter);
   gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
-  qtdemux->segment_seqnum = GST_SEQNUM_INVALID;
   qtdemux->need_segment = TRUE;
 
   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->segment_seqnum = GST_SEQNUM_INVALID;
+    g_ptr_array_set_size (qtdemux->active_streams, 0);
+    g_ptr_array_set_size (qtdemux->old_streams, 0);
     qtdemux->n_video_streams = 0;
     qtdemux->n_audio_streams = 0;
     qtdemux->n_sub_streams = 0;
@@ -2208,12 +2289,12 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     }
   } else if (qtdemux->mss_mode) {
     gst_flow_combiner_reset (qtdemux->flowcombiner);
-    g_list_foreach (qtdemux->active_streams,
+    g_ptr_array_foreach (qtdemux->active_streams,
         (GFunc) gst_qtdemux_stream_clear, NULL);
   } else {
     gst_flow_combiner_reset (qtdemux->flowcombiner);
-    for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-      QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
       stream->sent_eos = FALSE;
       stream->time_position = 0;
       stream->accumulated_base = 0;
@@ -2232,11 +2313,10 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
 static void
 gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment)
 {
-  gint i;
-  GList *iter;
+  gint i, iter;
 
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+  for (iter = 0; iter < QTDEMUX_N_STREAMS (qtdemux); iter++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, iter);
 
     stream->time_position = segment->start;
 
@@ -2266,6 +2346,31 @@ gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment)
   }
 }
 
+static void
+gst_qtdemux_stream_concat (GstQTDemux * qtdemux, GPtrArray * dest,
+    GPtrArray * src)
+{
+  guint i;
+  guint len;
+
+  len = src->len;
+
+  if (len == 0)
+    return;
+
+  for (i = 0; i < len; i++) {
+    QtDemuxStream *stream = g_ptr_array_index (src, i);
+
+#ifndef GST_DISABLE_GST_DEBUG
+    GST_DEBUG_OBJECT (qtdemux, "Move stream %p (stream-id %s) to %p",
+        stream, GST_STR_NULL (stream->stream_id), dest);
+#endif
+    g_ptr_array_add (dest, gst_qtdemux_stream_ref (stream));
+  }
+
+  g_ptr_array_set_size (src, 0);
+}
+
 static gboolean
 gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
     GstEvent * event)
@@ -2290,12 +2395,13 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
 
       if (segment.format == GST_FORMAT_TIME) {
         demux->upstream_format_is_time = TRUE;
+        demux->segment_seqnum = gst_event_get_seqnum (event);
       } else {
         GST_DEBUG_OBJECT (demux, "Not storing upstream newsegment, "
             "not in time format");
 
         /* chain will send initial newsegment after pads have been added */
-        if (demux->state != QTDEMUX_STATE_MOVIE || !demux->n_streams) {
+        if (demux->state != QTDEMUX_STATE_MOVIE || !QTDEMUX_N_STREAMS (demux)) {
           GST_DEBUG_OBJECT (demux, "still starting, eating event");
           goto exit;
         }
@@ -2363,19 +2469,9 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment);
 
       /* map segment to internal qt segments and push on each stream */
-      if (demux->n_streams) {
-        if (demux->fragmented) {
-          GstEvent *segment_event = gst_event_new_segment (&segment);
-
-          gst_event_set_seqnum (segment_event, demux->segment_seqnum);
-          gst_qtdemux_push_event (demux, segment_event);
-        } else {
-          gst_qtdemux_map_and_push_segments (demux, &segment);
-        }
-        /* keep need-segment as is in case this is the segment before
-         * fragmented data, we might not have pads yet to push it */
-        if (demux->exposed)
-          demux->need_segment = FALSE;
+      if (QTDEMUX_N_STREAMS (demux)) {
+        demux->need_segment = TRUE;
+        gst_qtdemux_check_send_pending_segment (demux);
       }
 
       /* clear leftover in current segment, if any */
@@ -2434,10 +2530,10 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       /* If we are in push mode, and get an EOS before we've seen any streams,
        * then error out - we have nowhere to send the EOS */
       if (!demux->pullbased) {
-        GList *iter;
+        gint i;
         gboolean has_valid_stream = FALSE;
-        for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-          if (QTDEMUX_STREAM (iter->data)->pad != NULL) {
+        for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+          if (QTDEMUX_NTH_STREAM (demux, i)->pad != NULL) {
             has_valid_stream = TRUE;
             break;
           }
@@ -2486,9 +2582,10 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       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;
+      if (demux->exposed) {
+        gst_qtdemux_stream_concat (demux,
+            demux->old_streams, demux->active_streams);
+      }
 
       goto drop;
     }
@@ -2502,6 +2599,46 @@ drop:
   return res;
 }
 
+static gboolean
+gst_qtdemux_handle_sink_query (GstPad * pad, GstObject * parent,
+    GstQuery * query)
+{
+  GstQTDemux *demux = GST_QTDEMUX (parent);
+  gboolean res = FALSE;
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_BITRATE:
+    {
+      GstClockTime duration;
+
+      /* populate demux->upstream_size if not done yet */
+      gst_qtdemux_check_seekability (demux);
+
+      if (demux->upstream_size != -1
+          && gst_qtdemux_get_duration (demux, &duration)) {
+        guint bitrate =
+            gst_util_uint64_scale (8 * demux->upstream_size, GST_SECOND,
+            duration);
+
+        GST_LOG_OBJECT (demux, "bitrate query byte length: %" G_GUINT64_FORMAT
+            " duration %" GST_TIME_FORMAT " resulting a bitrate of %u",
+            demux->upstream_size, GST_TIME_ARGS (duration), bitrate);
+
+        /* TODO: better results based on ranges/index tables */
+        gst_query_set_bitrate (query, bitrate);
+        res = TRUE;
+      }
+      break;
+    }
+    default:
+      res = gst_pad_query_default (pad, (GstObject *) demux, query);
+      break;
+  }
+
+  return res;
+}
+
+
 #if 0
 static void
 gst_qtdemux_set_index (GstElement * element, GstIndex * index)
@@ -2657,27 +2794,28 @@ gst_qtdemux_stream_reset (QtDemuxStream * stream)
   stream->stsd_entries_length = 0;
 }
 
-
-static void
-gst_qtdemux_stream_free (QtDemuxStream * stream)
+static QtDemuxStream *
+gst_qtdemux_stream_ref (QtDemuxStream * stream)
 {
-  gst_qtdemux_stream_reset (stream);
-  gst_tag_list_unref (stream->stream_tags);
-  if (stream->pad) {
-    GstQTDemux *demux = stream->demux;
-    gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad);
-    gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad);
-  }
-  g_free (stream->stream_id);
-  g_free (stream);
+  g_atomic_int_add (&stream->ref_count, 1);
+
+  return stream;
 }
 
 static void
-gst_qtdemux_remove_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_stream_unref (QtDemuxStream * stream)
 {
-  qtdemux->active_streams = g_list_remove (qtdemux->active_streams, stream);
-  gst_qtdemux_stream_free (stream);
-  qtdemux->n_streams--;
+  if (g_atomic_int_dec_and_test (&stream->ref_count)) {
+    gst_qtdemux_stream_reset (stream);
+    gst_tag_list_unref (stream->stream_tags);
+    if (stream->pad) {
+      GstQTDemux *demux = stream->demux;
+      gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad);
+      gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad);
+    }
+    g_free (stream->stream_id);
+    g_free (stream);
+  }
 }
 
 static GstStateChangeReturn
@@ -2771,6 +2909,531 @@ qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist,
   }
 }
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+static void
+_get_int_value_from_xml_string (GstQTDemux * qtdemux,
+    const char *xml_str, const char *param_name, int *value)
+{
+  char *value_start, *value_end, *endptr;
+  const short value_length_max = 12;
+  char init_view_ret[12];
+  int value_length = 0;
+  int i = 0;
+
+  value_start = (xml_str && param_name) ? strstr (xml_str, param_name) : NULL;
+
+  if (!value_start) {
+    GST_WARNING_OBJECT (qtdemux, "error: parameter does not exist: %s\n",
+        param_name);
+    return;
+  }
+
+  value_start += strlen (param_name);
+  while ((value_start[0] == ' ') || (value_start[0] == '\t'))
+    value_start++;
+
+  value_end = strchr (value_start, '<');
+  if (!value_end) {
+    GST_ERROR_OBJECT (qtdemux, "error: incorrect XML\n");
+    return;
+  }
+
+  value_length = value_end - value_start;
+  while ((value_length >= 1) && ((value_start[value_length - 1] == ' ')
+          || (value_start[value_length - 1] == '\t')))
+    value_length--;
+
+  if (value_start[i] == '+' || value_start[i] == '-')
+    i++;
+  while (i < value_length) {
+    if (value_start[i] < '0' || value_start[i] > '9') {
+      GST_ERROR_OBJECT (qtdemux,
+          "error: incorrect value, integer was expected\n");
+      return;
+    }
+    i++;
+  }
+
+  if (value_length >= value_length_max || value_length < 1) {
+    GST_ERROR_OBJECT (qtdemux, "error: empty XML value or incorrect range\n");
+    return;
+  }
+
+  strncpy (init_view_ret, value_start, value_length_max);
+  init_view_ret[value_length] = '\0';
+
+  *value = strtol (init_view_ret, &endptr, 10);
+  if (endptr == init_view_ret) {
+    GST_ERROR_OBJECT (qtdemux, "error: no digits were found\n");
+    return;
+  }
+
+  return;
+}
+
+static void
+_get_string_value_from_xml_string (GstQTDemux * qtdemux,
+    const char *xml_str, const char *param_name, char **value)
+{
+  char *value_start, *value_end;
+  const short value_length_max = 256;
+  int value_length = 0;
+
+  value_start = (xml_str && param_name) ? strstr (xml_str, param_name) : NULL;
+
+  if (!value_start) {
+    GST_WARNING_OBJECT (qtdemux, "error: parameter does not exist: %s\n",
+        param_name);
+    return;
+  }
+
+  value_start += strlen (param_name);
+  while ((value_start[0] == ' ') || (value_start[0] == '\t'))
+    value_start++;
+
+  value_end = strchr (value_start, '<');
+  if (!value_end) {
+    GST_ERROR_OBJECT (qtdemux, "error: incorrect XML\n");
+    return;
+  }
+
+  value_length = value_end - value_start;
+  while ((value_length >= 1) && ((value_start[value_length - 1] == ' ')
+          || (value_start[value_length - 1] == '\t')))
+    value_length--;
+
+  if (value_length >= value_length_max || value_length < 1) {
+    GST_ERROR_OBJECT (qtdemux, "error: empty XML value or incorrect range\n");
+    return;
+  }
+
+  *value = strndup(value_start, value_length);
+
+  return;
+}
+
+static void
+_get_bool_value_from_xml_string (GstQTDemux * qtdemux,
+    const char *xml_str, const char *param_name, gboolean * value)
+{
+  char *value_start, *value_end;
+  int value_length = 0;
+
+  value_start = (xml_str && param_name) ? strstr (xml_str, param_name) : NULL;
+
+  if (!value_start) {
+    GST_WARNING_OBJECT (qtdemux, "error: parameter does not exist: %s\n",
+        param_name);
+    return;
+  }
+
+  value_start += strlen (param_name);
+  while ((value_start[0] == ' ') || (value_start[0] == '\t'))
+    value_start++;
+
+  value_end = strchr (value_start, '<');
+  if (!value_end) {
+    GST_ERROR_OBJECT (qtdemux, "error: incorrect XML\n");
+    return;
+  }
+
+  value_length = value_end - value_start;
+  while ((value_length >= 1) && ((value_start[value_length - 1] == ' ')
+          || (value_start[value_length - 1] == '\t')))
+    value_length--;
+
+  if (value_length < 1) {
+    GST_ERROR_OBJECT (qtdemux, "error: empty XML value or incorrect range\n");
+    return;
+  }
+
+  *value = g_strstr_len(value_start, value_length, "true") ? TRUE : FALSE;
+
+  return;
+}
+
+static void
+_parse_spatial_video_metadata_from_xml_string (GstQTDemux * qtdemux, const char *xmlStr)
+{
+  const char is_spherical_str[] = "<GSpherical:Spherical>";
+  const char is_stitched_str[] = "<GSpherical:Stitched>";
+  const char stitching_software_str[] = "<GSpherical:StitchingSoftware>";
+  const char projection_type_str[] = "<GSpherical:ProjectionType>";
+  const char stereo_mode_str[] = "<GSpherical:StereoMode>";
+  const char source_count_str[] = "<GSpherical:SourceCount>";
+  const char init_view_heading_str[] = "<GSpherical:InitialViewHeadingDegrees>";
+  const char init_view_pitch_str[] = "<GSpherical:InitialViewPitchDegrees>";
+  const char init_view_roll_str[] = "<GSpherical:InitialViewRollDegrees>";
+  const char timestamp_str[] = "<GSpherical:Timestamp>";
+  const char full_pano_width_str[] = "<GSpherical:FullPanoWidthPixels>";
+  const char full_pano_height_str[] = "<GSpherical:FullPanoHeightPixels>";
+  const char cropped_area_image_width_str[] =
+      "<GSpherical:CroppedAreaImageWidthPixels>";
+  const char cropped_area_image_height_str[] =
+      "<GSpherical:CroppedAreaImageHeightPixels>";
+  const char cropped_area_left_str[] = "<GSpherical:CroppedAreaLeftPixels>";
+  const char cropped_area_top_str[] = "<GSpherical:CroppedAreaTopPixels>";
+
+  QtDemuxSphericalMetadata * spherical_metadata = qtdemux->spherical_metadata;
+
+  _get_bool_value_from_xml_string (qtdemux, xmlStr, is_spherical_str,
+      (gboolean *) & spherical_metadata->is_spherical);
+  _get_bool_value_from_xml_string (qtdemux, xmlStr, is_stitched_str,
+      (gboolean *) & spherical_metadata->is_stitched);
+
+  if (spherical_metadata->is_spherical && spherical_metadata->is_stitched) {
+    _get_string_value_from_xml_string (qtdemux, xmlStr,
+        stitching_software_str, &spherical_metadata->stitching_software);
+    _get_string_value_from_xml_string (qtdemux, xmlStr,
+        projection_type_str, &spherical_metadata->projection_type);
+    _get_string_value_from_xml_string (qtdemux, xmlStr, stereo_mode_str,
+        &spherical_metadata->stereo_mode);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, source_count_str,
+        &spherical_metadata->source_count);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        init_view_heading_str, &spherical_metadata->init_view_heading);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, init_view_pitch_str,
+        &spherical_metadata->init_view_pitch);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, init_view_roll_str,
+        &spherical_metadata->init_view_roll);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, timestamp_str,
+        &spherical_metadata->timestamp);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, full_pano_width_str,
+        &spherical_metadata->full_pano_width_pixels);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        full_pano_height_str, &spherical_metadata->full_pano_height_pixels);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        cropped_area_image_width_str,
+        &spherical_metadata->cropped_area_image_width);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        cropped_area_image_height_str,
+        &spherical_metadata->cropped_area_image_height);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, cropped_area_left_str,
+        &spherical_metadata->cropped_area_left);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, cropped_area_top_str,
+        &spherical_metadata->cropped_area_top);
+  }
+
+  return;
+}
+
+static void
+gst_tag_register_spherical_tags (void) {
+  gst_tag_register ("is_spherical", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-spherical"),
+      _("Flag indicating if the video is a spherical video"),
+      NULL);
+  gst_tag_register ("is_stitched", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-stitched"),
+      _("Flag indicating if the video is stitched"),
+      NULL);
+  gst_tag_register ("stitching_software", GST_TAG_FLAG_META,
+      G_TYPE_STRING,
+      _("tag-stitching-software"),
+      _("Software used to stitch the spherical video"),
+      NULL);
+  gst_tag_register ("projection_type", GST_TAG_FLAG_META,
+      G_TYPE_STRING,
+      _("tag-projection-type"),
+      _("Projection type used in the video frames"),
+      NULL);
+  gst_tag_register ("stereo_mode", GST_TAG_FLAG_META,
+      G_TYPE_STRING,
+      _("tag-stereo-mode"),
+      _("Description of stereoscopic 3D layout"),
+      NULL);
+  gst_tag_register ("source_count", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-source-count"),
+      _("Number of cameras used to create the spherical video"),
+      NULL);
+  gst_tag_register ("init_view_heading", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-init-view-heading"),
+      _("The heading angle of the initial view in degrees"),
+      NULL);
+  gst_tag_register ("init_view_pitch", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-init-view-pitch"),
+      _("The pitch angle of the initial view in degrees"),
+      NULL);
+  gst_tag_register ("init_view_roll", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-init-view-roll"),
+      _("The roll angle of the initial view in degrees"),
+      NULL);
+  gst_tag_register ("timestamp", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-timestamp"),
+      _("Epoch timestamp of when the first frame in the video was recorded"),
+      NULL);
+  gst_tag_register ("full_pano_width_pixels", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-full-pano-width"),
+      _("Width of the encoded video frame in pixels"),
+      NULL);
+  gst_tag_register ("full_pano_height_pixels", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-full-pano-height"),
+      _("Height of the encoded video frame in pixels"),
+      NULL);
+  gst_tag_register ("cropped_area_image_width", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-image-width"),
+      _("Width of the video frame to display (e.g. cropping)"),
+      NULL);
+  gst_tag_register ("cropped_area_image_height", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-image-height"),
+      _("Height of the video frame to display (e.g. cropping)"),
+      NULL);
+  gst_tag_register ("cropped_area_left", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-left"),
+      _("Column where the left edge of the image was cropped from the"
+          " full sized panorama"),
+      NULL);
+  gst_tag_register ("cropped_area_top", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-top"),
+      _("Row where the top edge of the image was cropped from the"
+          " full sized panorama"),
+      NULL);
+  gst_tag_register ("ambisonic_type", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-ambisonic-type"),
+      _("Specifies the type of ambisonic audio represented"),
+      NULL);
+  gst_tag_register ("ambisonic_format", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-ambisonic-format"),
+      _("Specifies the ambisonic audio format"),
+      NULL);
+  gst_tag_register ("ambisonic_order", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-ambisonic-order"),
+      _("Specifies the ambisonic audio channel order"),
+      NULL);
+
+  return;
+}
+
+static void
+_send_spherical_metadata_msg_to_bus (GstQTDemux * qtdemux)
+{
+  GstTagList *taglist;
+  QtDemuxSphericalMetadata *spherical_metadata = qtdemux->spherical_metadata;
+
+  GST_DEBUG_OBJECT (qtdemux, "is_spherical = %d",
+      spherical_metadata->is_spherical);
+  GST_DEBUG_OBJECT (qtdemux, "is_stitched = %d",
+      spherical_metadata->is_stitched);
+  GST_DEBUG_OBJECT (qtdemux, "stitching_software = %s",
+      spherical_metadata->stitching_software);
+  GST_DEBUG_OBJECT (qtdemux, "projection_type = %s",
+      spherical_metadata->projection_type);
+  GST_DEBUG_OBJECT (qtdemux, "stereo_mode = %s",
+      spherical_metadata->stereo_mode);
+  GST_DEBUG_OBJECT (qtdemux, "source_count %d",
+      spherical_metadata->source_count);
+  GST_DEBUG_OBJECT (qtdemux, "init_view_heading = %d",
+      spherical_metadata->init_view_heading);
+  GST_DEBUG_OBJECT (qtdemux, "init_view_pitch = %d",
+      spherical_metadata->init_view_pitch);
+  GST_DEBUG_OBJECT (qtdemux, "init_view_roll = %d",
+      spherical_metadata->init_view_roll);
+  GST_DEBUG_OBJECT (qtdemux, "timestamp = %d", spherical_metadata->timestamp);
+  GST_DEBUG_OBJECT (qtdemux, "full_pano_width_pixels = %d",
+      spherical_metadata->full_pano_width_pixels);
+  GST_DEBUG_OBJECT (qtdemux, "full_pano_height_pixels = %d",
+      spherical_metadata->full_pano_height_pixels);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_image_width = %d",
+      spherical_metadata->cropped_area_image_width);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_image_height = %d",
+      spherical_metadata->cropped_area_image_height);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_left = %d",
+      spherical_metadata->cropped_area_left);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_top = %d",
+      spherical_metadata->cropped_area_top);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_type = %d",
+      spherical_metadata->ambisonic_type);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_order = %d",
+      spherical_metadata->ambisonic_order);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_format = %d",
+      spherical_metadata->ambisonic_format);
+
+  taglist = gst_tag_list_new_empty ();
+  gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+      "is_spherical", spherical_metadata->is_spherical,
+      "is_stitched", spherical_metadata->is_stitched,
+      "source_count", spherical_metadata->source_count,
+      "init_view_heading", spherical_metadata->init_view_heading,
+      "init_view_pitch", spherical_metadata->init_view_pitch,
+      "init_view_roll", spherical_metadata->init_view_roll,
+      "timestamp", spherical_metadata->timestamp,
+      "full_pano_width_pixels", spherical_metadata->full_pano_width_pixels,
+      "full_pano_height_pixels", spherical_metadata->full_pano_height_pixels,
+      "cropped_area_image_width", spherical_metadata->cropped_area_image_width,
+      "cropped_area_image_height", spherical_metadata->cropped_area_image_height,
+      "cropped_area_left", spherical_metadata->cropped_area_left,
+      "cropped_area_top", spherical_metadata->cropped_area_top,
+      "ambisonic_type", spherical_metadata->ambisonic_type,
+      "ambisonic_format", spherical_metadata->ambisonic_format,
+      "ambisonic_order", spherical_metadata->ambisonic_order,
+      NULL);
+
+  if (spherical_metadata->stitching_software)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        "stitching_software", spherical_metadata->stitching_software,
+        NULL);
+  if (spherical_metadata->projection_type)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        "projection_type", spherical_metadata->projection_type,
+        NULL);
+  if (spherical_metadata->stereo_mode)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        "stereo_mode", spherical_metadata->stereo_mode,
+        NULL);
+
+  gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
+          gst_message_new_tag (GST_OBJECT_CAST (qtdemux),
+                  gst_tag_list_copy (taglist)));
+
+  gst_tag_list_unref(taglist);
+
+  return;
+}
+
+static void
+qtdemux_parse_SA3D (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
+{
+  guint offset = 0;
+
+  guint8 version = 0;
+  guint8 ambisonic_type  = 0;
+  guint32 ambisonic_order = 0;
+  guint8 ambisonic_channel_ordering = 0;
+  guint8 ambisonic_normalization = 0;
+  guint32 num_channels = 0;
+  guint32 channel_map[49] = { 0 };      /* Up to 6th order */
+
+  int i;
+
+  GST_DEBUG_OBJECT (qtdemux, "qtdemux_parse_SA3D");
+
+  qtdemux->header_size += length;
+  offset = (QT_UINT32 (buffer) == 0) ? 16 : 8;
+
+  if (length <= offset + 16) {
+    GST_DEBUG_OBJECT (qtdemux, "SA3D atom is too short, skipping");
+    return;
+  }
+
+  version = QT_UINT8 (buffer + offset);
+  ambisonic_type = QT_UINT8 (buffer + offset + 1);
+  ambisonic_order = QT_UINT32 (buffer + offset + 2);
+  ambisonic_channel_ordering = QT_UINT8 (buffer + offset + 6);
+  ambisonic_normalization = QT_UINT8 (buffer + offset + 7);
+  num_channels = QT_UINT32 (buffer + offset + 8);
+  for (i = 0; i < num_channels; ++i)
+    channel_map[i] = QT_UINT32 (buffer + offset + 12 + i * 4);
+
+  GST_DEBUG_OBJECT (qtdemux, "version: %d", version);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_type: %d", ambisonic_type);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_order: %d", ambisonic_order);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_channel_ordering: %d",
+      ambisonic_channel_ordering);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_normalization: %d",
+      ambisonic_normalization);
+  GST_DEBUG_OBJECT (qtdemux, "num_channels: %d", num_channels);
+  for (i = 0; i < num_channels; ++i)
+    GST_DEBUG_OBJECT (qtdemux, "channel_map: %d", channel_map[i]);
+
+  if (version == RFC_AMBISONIC_SA3DBOX_VERSION_SUPPORTED) {
+    if (ambisonic_type == RFC_AMBISONIC_TYPE_PERIPHONIC)
+      qtdemux->spherical_metadata->ambisonic_type = QTDEMUX_AMBISONIC_TYPE_PERIPHONIC;
+
+    if (ambisonic_order == RFC_AMBISONIC_ORDER_FOA) {
+      if (num_channels == 4) {
+        qtdemux->spherical_metadata->ambisonic_order = QTDEMUX_AMBISONIC_ORDER_FOA;
+
+        if ((ambisonic_channel_ordering == RFC_AMBISONIC_CHANNEL_ORDERING_ACN)
+            && (ambisonic_normalization == RFC_AMBISONIC_NORMALIZATION_SN3D)
+            && (channel_map[0] == 0) && (channel_map[1] == 1)
+            && (channel_map[2] == 2) && (channel_map[3] == 3))
+          qtdemux->spherical_metadata->ambisonic_format = QTDEMUX_AMBISONIC_FORMAT_AMBIX;
+
+        if ((ambisonic_channel_ordering == RFC_AMBISONIC_CHANNEL_ORDERING_FUMA)
+            && (ambisonic_normalization == RFC_AMBISONIC_NORMALIZATION_FUMA)
+            && (channel_map[0] == 0) && (channel_map[1] == 3)
+            && (channel_map[2] == 1) && (channel_map[3] == 2))
+          qtdemux->spherical_metadata->ambisonic_format = QTDEMUX_AMBISONIC_FORMAT_AMB;
+      }
+    }
+  }
+
+  return;
+}
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
+static void
+qtdemux_update_default_sample_encryption_settings (GstQTDemux * qtdemux,
+    QtDemuxCencSampleSetInfo * info, guint32 is_encrypted, guint8 iv_size,
+    const guint8 * kid)
+{
+  GstBuffer *kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
+  gst_buffer_fill (kid_buf, 0, kid, 16);
+  if (info->default_properties)
+    gst_structure_free (info->default_properties);
+  info->default_properties =
+      gst_structure_new ("application/x-cenc",
+      "iv_size", G_TYPE_UINT, iv_size,
+      "encrypted", G_TYPE_BOOLEAN, (is_encrypted == 1),
+      "kid", GST_TYPE_BUFFER, kid_buf, NULL);
+  GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
+      "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
+  gst_buffer_unref (kid_buf);
+}
+
+static gboolean
+qtdemux_update_default_piff_encryption_settings (GstQTDemux * qtdemux,
+    QtDemuxCencSampleSetInfo * info, GstByteReader * br)
+{
+  guint32 algorithm_id = 0;
+  const guint8 *kid;
+  gboolean is_encrypted = TRUE;
+  guint8 iv_size = 8;
+
+  if (!gst_byte_reader_get_uint24_le (br, &algorithm_id)) {
+    GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
+    return FALSE;
+  }
+
+  algorithm_id >>= 8;
+  if (algorithm_id == 0) {
+    is_encrypted = FALSE;
+  } else if (algorithm_id == 1) {
+    GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
+  } else if (algorithm_id == 2) {
+    GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
+  }
+
+  if (!gst_byte_reader_get_uint8 (br, &iv_size))
+    return FALSE;
+
+  if (!gst_byte_reader_get_data (br, 16, &kid))
+    return FALSE;
+
+  qtdemux_update_default_sample_encryption_settings (qtdemux, info,
+      is_encrypted, iv_size, kid);
+  gst_structure_set (info->default_properties, "piff_algorithm_id",
+      G_TYPE_UINT, algorithm_id, NULL);
+  return TRUE;
+}
+
+
 static void
 qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     guint offset)
@@ -2779,7 +3442,7 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
   guint8 version;
   guint32 flags = 0;
   guint i;
-  guint8 iv_size = 8;
+  guint iv_size = 8;
   QtDemuxStream *stream;
   GstStructure *structure;
   QtDemuxCencSampleSetInfo *ss_info = NULL;
@@ -2787,10 +3450,11 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
   gboolean uses_sub_sample_encryption = FALSE;
   guint32 sample_count;
 
-  stream = QTDEMUX_FIRST_STREAM (qtdemux);
-  if (!stream)
+  if (QTDEMUX_N_STREAMS (qtdemux) == 0)
     return;
 
+  stream = QTDEMUX_NTH_STREAM (qtdemux, 0);
+
   structure = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
   if (!gst_structure_has_name (structure, "application/x-cenc")) {
     GST_WARNING_OBJECT (qtdemux,
@@ -2809,13 +3473,13 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     stream->protection_scheme_info = g_new0 (QtDemuxCencSampleSetInfo, 1);
 
   ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+  if (!ss_info->default_properties) {
+    ss_info->default_properties =
+        gst_structure_new ("application/x-cenc",
+        "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE,
+        NULL);
 
-  if (ss_info->default_properties)
-    gst_structure_free (ss_info->default_properties);
-
-  ss_info->default_properties =
-      gst_structure_new ("application/x-cenc",
-      "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE, NULL);
+  }
 
   if (ss_info->crypto_info) {
     GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info");
@@ -2836,50 +3500,20 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     return;
   }
 
-  if ((flags & 0x000001)) {
-    guint32 algorithm_id = 0;
-    const guint8 *kid;
-    GstBuffer *kid_buf;
-    gboolean is_encrypted = TRUE;
-
-    if (!gst_byte_reader_get_uint24_le (&br, &algorithm_id)) {
-      GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
-      return;
-    }
-
-    algorithm_id >>= 8;
-    if (algorithm_id == 0) {
-      is_encrypted = FALSE;
-    } else if (algorithm_id == 1) {
-      /* FIXME: maybe store this in properties? */
-      GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
-    } else if (algorithm_id == 2) {
-      /* FIXME: maybe store this in properties? */
-      GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
-    }
-
-    if (!gst_byte_reader_get_uint8 (&br, &iv_size))
-      return;
-
-    if (!gst_byte_reader_get_data (&br, 16, &kid))
+  if ((flags & 0x000001)) {
+    if (!qtdemux_update_default_piff_encryption_settings (qtdemux, ss_info,
+            &br))
       return;
-
-    kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
-    gst_buffer_fill (kid_buf, 0, kid, 16);
-    if (ss_info->default_properties)
-      gst_structure_free (ss_info->default_properties);
-    ss_info->default_properties =
-        gst_structure_new ("application/x-cenc",
-        "iv_size", G_TYPE_UINT, iv_size,
-        "encrypted", G_TYPE_BOOLEAN, is_encrypted,
-        "kid", GST_TYPE_BUFFER, kid_buf, NULL);
-    GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
-        "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
-    gst_buffer_unref (kid_buf);
   } else if ((flags & 0x000002)) {
     uses_sub_sample_encryption = TRUE;
   }
 
+  if (!gst_structure_get_uint (ss_info->default_properties, "iv_size",
+          &iv_size)) {
+    GST_ERROR_OBJECT (qtdemux, "Error getting encryption IV size field");
+    return;
+  }
+
   if (!gst_byte_reader_get_uint32_be (&br, &sample_count)) {
     GST_ERROR_OBJECT (qtdemux, "Error getting box's sample count field");
     return;
@@ -2913,6 +3547,7 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
 
     if (uses_sub_sample_encryption) {
       guint16 n_subsamples;
+      const GValue *kid_buf_value;
 
       if (!gst_byte_reader_get_uint16_be (&br, &n_subsamples)
           || n_subsamples == 0) {
@@ -2931,9 +3566,14 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
         return;
       }
       buf = gst_buffer_new_wrapped (data, n_subsamples * 6);
+
+      kid_buf_value =
+          gst_structure_get_value (ss_info->default_properties, "kid");
+
       gst_structure_set (properties,
           "subsample_count", G_TYPE_UINT, n_subsamples,
           "subsamples", GST_TYPE_BUFFER, buf, NULL);
+      gst_structure_set_value (properties, "kid", kid_buf_value);
       gst_buffer_unref (buf);
     } else {
       gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL);
@@ -2963,6 +3603,13 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
     0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4
   };
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  static const guint8 spherical_uuid[] = {
+    0xff, 0xcc, 0x82, 0x63, 0xf8, 0x55, 0x4a, 0x93,
+    0x88, 0x14, 0x58, 0x7a, 0x02, 0x52, 0x1f, 0xdd
+  };
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   guint offset;
 
   /* counts as header data */
@@ -2975,6 +3622,21 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
     return;
   }
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  if (memcmp (buffer + offset, spherical_uuid, 16) == 0) {
+    const char *contents;
+
+    GST_DEBUG_OBJECT (qtdemux, "spherical uuid was found");
+    contents = (char *) (buffer + offset + 16);
+    GST_DEBUG_OBJECT (qtdemux, "contents: %s\n", contents);
+
+    if (qtdemux->spherical_metadata)
+      _parse_spatial_video_metadata_from_xml_string (qtdemux, contents);
+
+    return;
+  }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   if (memcmp (buffer + offset, xmp_uuid, 16) == 0) {
     GstBuffer *buf;
     GstTagList *taglist;
@@ -3167,7 +3829,6 @@ check_update_duration (GstQTDemux * qtdemux, GstClockTime duration)
   guint i;
   guint64 movdur;
   GstClockTime prevdur;
-  GList *iter;
 
   movdur = GSTTIME_TO_QTTIME (qtdemux, duration);
 
@@ -3192,9 +3853,9 @@ check_update_duration (GstQTDemux * qtdemux, GstClockTime duration)
       qtdemux->segment.stop = fixeddur;
     }
   }
-  for (iter = qtdemux->active_streams, i = 0; iter;
-      iter = g_list_next (iter), i++) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration);
     if (movdur > stream->duration) {
@@ -3513,7 +4174,7 @@ static inline QtDemuxStream *
 qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id)
 {
   QtDemuxStream *stream;
-  GList *iter;
+  gint i;
 
   /* check */
   if (G_UNLIKELY (!id)) {
@@ -3521,14 +4182,14 @@ qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id)
     return NULL;
   }
 
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     if (stream->track_id == id)
       return stream;
   }
   if (qtdemux->mss_mode) {
     /* mss should have only 1 stream anyway */
-    return QTDEMUX_FIRST_STREAM (qtdemux);
+    return QTDEMUX_NTH_STREAM (qtdemux, 0);
   }
 
   return NULL;
@@ -3941,7 +4602,7 @@ qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node)
   GstBuffer *pssh = NULL;
   GstEvent *event = NULL;
   guint32 parent_box_type;
-  GList *iter;
+  gint i;
 
   if (G_UNLIKELY (pssh_size < 32U)) {
     GST_ERROR_OBJECT (qtdemux, "invalid box size");
@@ -3962,8 +4623,8 @@ qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node)
   /* Push an event containing the pssh box onto the queues of all streams. */
   event = gst_event_new_protection (sysid_string, pssh,
       (parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof");
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     GST_TRACE_OBJECT (qtdemux,
         "adding protection event for stream %s and system %s",
         stream->stream_id, sysid_string);
@@ -3988,6 +4649,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
   guint32 ds_size = 0, ds_duration = 0, ds_flags = 0;
   gint64 base_offset, running_offset;
   guint32 frag_num;
+  GstClockTime min_dts = GST_CLOCK_TIME_NONE;
 
   /* NOTE @stream ignored */
 
@@ -4108,8 +4770,19 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
     if (G_UNLIKELY (base_offset < -1))
       goto lost_offset;
 
-    if (qtdemux->upstream_format_is_time)
+    min_dts = MIN (min_dts, QTSTREAMTIME_TO_GSTTIME (stream, decode_time));
+
+    if (!qtdemux->pullbased) {
+      /* Sample tables can grow enough to be problematic if the system memory
+       * is very low (e.g. embedded devices) and the videos very long
+       * (~8 MiB/hour for 25-30 fps video + typical AAC audio frames).
+       * Fortunately, we can easily discard them for each new fragment when
+       * we know qtdemux will not receive seeks outside of the current fragment.
+       * adaptivedemux honors this assumption.
+       * This optimization is also useful for applications that use qtdemux as
+       * a push-based simple demuxer, like Media Source Extensions. */
       gst_qtdemux_stream_flush_samples_data (stream);
+    }
 
     /* initialise moof sample data */
     stream->n_samples_moof = 0;
@@ -4158,6 +4831,38 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
     pssh_node = qtdemux_tree_get_sibling_by_type (pssh_node, FOURCC_pssh);
   }
 
+  if (!qtdemux->upstream_format_is_time && !qtdemux->first_moof_already_parsed
+      && !qtdemux->received_seek && GST_CLOCK_TIME_IS_VALID (min_dts)
+      && min_dts != 0) {
+    /* Unless the user has explictly requested another seek, perform an
+     * internal seek to the time specified in the tfdt.
+     *
+     * This way if the user opens a file where the first tfdt is 1 hour
+     * into the presentation, they will not have to wait 1 hour for run
+     * time to catch up and actual playback to start. */
+    gint i;
+
+    GST_DEBUG_OBJECT (qtdemux, "First fragment has a non-zero tfdt, "
+        "performing an internal seek to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (min_dts));
+
+    qtdemux->segment.start = min_dts;
+    qtdemux->segment.time = qtdemux->segment.position = min_dts;
+
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+      stream->time_position = min_dts;
+    }
+
+    /* Before this code was run a segment was already sent when the moov was
+     * parsed... which is OK -- some apps (mostly tests) expect a segment to
+     * be emitted after a moov, and we can emit a second segment anyway for
+     * special cases like this. */
+    qtdemux->need_segment = TRUE;
+  }
+
+  qtdemux->first_moof_already_parsed = TRUE;
+
   g_node_destroy (moof_node);
   return TRUE;
 
@@ -4526,11 +5231,6 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
       gst_buffer_unmap (moov, &map);
       gst_buffer_unref (moov);
       qtdemux->got_moov = TRUE;
-      if (!qtdemux->fragmented && !qtdemux->upstream_format_is_time) {
-        /* in this case, parsing the edts entries will give us segments
-           already */
-        qtdemux->need_segment = FALSE;
-      }
 
       break;
     }
@@ -4600,6 +5300,10 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
 beach:
   if (ret == GST_FLOW_EOS && (qtdemux->got_moov || qtdemux->media_caps)) {
     /* digested all data, show what we have */
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+    if (qtdemux->spherical_metadata)
+      _send_spherical_metadata_msg_to_bus (qtdemux);
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
     qtdemux_prepare_streams (qtdemux);
     QTDEMUX_EXPOSE_LOCK (qtdemux);
     ret = qtdemux_expose_streams (qtdemux);
@@ -4627,13 +5331,13 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
   QtDemuxStream *ref_str = NULL;
   guint64 seg_media_start_mov;  /* segment media start time in mov format */
   guint64 target_ts;
-  GList *iter;
+  gint i;
 
   /* Now we choose an arbitrary stream, get the previous keyframe timestamp
    * and finally align all the other streams on that timestamp with their
    * respective keyframes */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *str = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     /* No candidate yet, take the first stream */
     if (!ref_str) {
@@ -4681,7 +5385,7 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
   seg_media_start_mov = seg->trak_media_start;
 
   GST_LOG_OBJECT (qtdemux, "keyframe index %u ts %" G_GUINT64_FORMAT
-      " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT "\n",
+      " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT,
       k_index, target_ts, seg_media_start_mov,
       GST_TIME_ARGS (seg->media_start));
 
@@ -4728,10 +5432,10 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
   ref_k_index = k_index;
 
   /* Align them all on this */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     guint32 index = 0;
     GstClockTime seg_time = 0;
-    QtDemuxStream *str = QTDEMUX_STREAM (iter->data);
+    QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     /* aligning reference stream again might lead to backing up to yet another
      * keyframe (due to timestamp rounding issues),
@@ -4868,7 +5572,6 @@ gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
 {
   QtDemuxSegment *segment;
   GstClockTime start = 0, stop = GST_CLOCK_TIME_NONE, time = 0;
-  GstClockTime min_ts;
   gdouble rate;
   GstEvent *event;
 
@@ -4907,22 +5610,17 @@ gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
   /* Copy flags from main segment */
   stream->segment.flags = qtdemux->segment.flags;
 
-  /* need to offset with the start time of the first sample */
-  min_ts = gst_qtdemux_streams_get_first_sample_ts (qtdemux);
-
   /* update the segment values used for clipping */
   stream->segment.offset = qtdemux->segment.offset;
   stream->segment.base = qtdemux->segment.base + stream->accumulated_base;
   stream->segment.applied_rate = qtdemux->segment.applied_rate;
   stream->segment.rate = rate;
   stream->segment.start = start + QTSTREAMTIME_TO_GSTTIME (stream,
-      stream->cslg_shift) + min_ts;
-  if (GST_CLOCK_TIME_IS_VALID (stop)) {
-    stream->segment.stop = stop + QTSTREAMTIME_TO_GSTTIME (stream,
-        stream->cslg_shift) + min_ts;
-  }
-  stream->segment.time = time + min_ts;
-  stream->segment.position = stream->segment.start + min_ts;
+      stream->cslg_shift);
+  stream->segment.stop = stop + QTSTREAMTIME_TO_GSTTIME (stream,
+      stream->cslg_shift);
+  stream->segment.time = time;
+  stream->segment.position = stream->segment.start;
 
   GST_DEBUG_OBJECT (stream->pad, "New segment: %" GST_SEGMENT_FORMAT,
       &stream->segment);
@@ -5098,8 +5796,8 @@ gst_qtdemux_prepare_current_sample (GstQTDemux * qtdemux,
   if (G_UNLIKELY (stream->segment_index != seg_idx))
     gst_qtdemux_activate_segment (qtdemux, stream, seg_idx, time_position);
 
-  if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream->segments[stream->
-                  segment_index]))) {
+  if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream->
+              segments[stream->segment_index]))) {
     QtDemuxSegment *seg = &stream->segments[stream->segment_index];
 
     GST_LOG_OBJECT (qtdemux, "Empty segment activated,"
@@ -5251,16 +5949,16 @@ next_segment:
 static void
 gst_qtdemux_sync_streams (GstQTDemux * demux)
 {
-  GList *iter;
+  gint i;
 
-  if (demux->n_streams <= 1)
+  if (QTDEMUX_N_STREAMS (demux) <= 1)
     return;
 
-  for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
     QtDemuxStream *stream;
     GstClockTime end_time;
 
-    stream = QTDEMUX_STREAM (iter->data);
+    stream = QTDEMUX_NTH_STREAM (demux, i);
 
     if (!stream->pad)
       continue;
@@ -5490,7 +6188,7 @@ gst_qtdemux_align_buffer (GstQTDemux * demux,
 }
 
 static guint8 *
-convert_to_ccdata (const guint8 * ccpair, guint8 ccpair_size, guint field,
+convert_to_s334_1a (const guint8 * ccpair, guint8 ccpair_size, guint field,
     gsize * res)
 {
   guint8 *storage;
@@ -5500,10 +6198,11 @@ convert_to_ccdata (const guint8 * ccpair, guint8 ccpair_size, guint field,
   *res = ccpair_size / 2 * 3;
   storage = g_malloc (*res);
   for (i = 0; i * 2 < ccpair_size; i += 1) {
+    /* FIXME: Use line offset 0 as we simply can't know here */
     if (field == 1)
-      storage[i * 3] = 0xfc;
+      storage[i * 3] = 0x80 | 0x00;
     else
-      storage[i * 3] = 0xfd;
+      storage[i * 3] = 0x00 | 0x00;
     storage[i * 3 + 1] = ccpair[i * 2];
     storage[i * 3 + 2] = ccpair[i * 2 + 1];
   }
@@ -5547,11 +6246,11 @@ extract_cc_from_data (QtDemuxStream * stream, const guint8 * data, gsize size,
         goto invalid_cdat;
       }
 
-      /* Convert to cc_data triplet */
+      /* Convert to S334-1 Annex A byte triplet */
       if (fourcc == FOURCC_cdat)
-        cdat = convert_to_ccdata (data + 8, atom_length - 8, 1, &cdat_size);
+        cdat = convert_to_s334_1a (data + 8, atom_length - 8, 1, &cdat_size);
       else
-        cdt2 = convert_to_ccdata (data + 8, atom_length - 8, 2, &cdt2_size);
+        cdt2 = convert_to_s334_1a (data + 8, atom_length - 8, 2, &cdt2_size);
       GST_DEBUG_OBJECT (stream->pad, "size:%" G_GSIZE_FORMAT " atom_length:%u",
           size, atom_length);
 
@@ -5563,7 +6262,7 @@ extract_cc_from_data (QtDemuxStream * stream, const guint8 * data, gsize size,
           if (fourcc == FOURCC_cdat) {
             if (cdat == NULL)
               cdat =
-                  convert_to_ccdata (data + atom_length + 8,
+                  convert_to_s334_1a (data + atom_length + 8,
                   new_atom_length - 8, 1, &cdat_size);
             else
               GST_WARNING_OBJECT (stream->pad,
@@ -5571,7 +6270,7 @@ extract_cc_from_data (QtDemuxStream * stream, const guint8 * data, gsize size,
           } else {
             if (cdt2 == NULL)
               cdt2 =
-                  convert_to_ccdata (data + atom_length + 8,
+                  convert_to_s334_1a (data + atom_length + 8,
                   new_atom_length - 8, 2, &cdt2_size);
             else
               GST_WARNING_OBJECT (stream->pad,
@@ -5694,6 +6393,210 @@ gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
   return buf;
 }
 
+static GstFlowReturn
+gst_qtdemux_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  GstClockTime pts, duration;
+
+  if (stream->need_clip)
+    buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf);
+
+  if (G_UNLIKELY (buf == NULL))
+    goto exit;
+
+  if (G_UNLIKELY (stream->discont)) {
+    GST_LOG_OBJECT (qtdemux, "marking discont buffer");
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
+    stream->discont = FALSE;
+  } else {
+    GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
+  }
+
+  GST_LOG_OBJECT (qtdemux,
+      "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
+      ", duration %" GST_TIME_FORMAT " on pad %s",
+      GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
+      GST_TIME_ARGS (GST_BUFFER_PTS (buf)),
+      GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_PAD_NAME (stream->pad));
+
+  if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
+    GstStructure *crypto_info;
+    QtDemuxCencSampleSetInfo *info =
+        (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+    gint index;
+    GstEvent *event;
+
+    while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
+      GST_TRACE_OBJECT (stream->pad, "pushing protection event: %"
+          GST_PTR_FORMAT, event);
+      gst_pad_push_event (stream->pad, event);
+    }
+
+    if (info->crypto_info == NULL) {
+      GST_DEBUG_OBJECT (qtdemux,
+          "cenc metadata hasn't been parsed yet, pushing buffer as if it wasn't encrypted");
+    } else {
+      /* The end of the crypto_info array matches our n_samples position,
+       * so count backward from there */
+      index = stream->sample_index - stream->n_samples + info->crypto_info->len;
+      if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
+        /* steal structure from array */
+        crypto_info = g_ptr_array_index (info->crypto_info, index);
+        g_ptr_array_index (info->crypto_info, index) = NULL;
+        GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u/%u]", index,
+            info->crypto_info->len);
+        if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
+          GST_ERROR_OBJECT (qtdemux,
+              "failed to attach cenc metadata to buffer");
+      } else {
+        GST_INFO_OBJECT (qtdemux, "No crypto info with index %d and sample %d",
+            index, stream->sample_index);
+      }
+    }
+  }
+
+  if (stream->alignment > 1)
+    buf = gst_qtdemux_align_buffer (qtdemux, buf, stream->alignment);
+
+  pts = GST_BUFFER_PTS (buf);
+  duration = GST_BUFFER_DURATION (buf);
+
+  ret = gst_pad_push (stream->pad, buf);
+
+  if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
+    /* mark position in stream, we'll need this to know when to send GAP event */
+    stream->segment.position = pts + duration;
+  }
+
+exit:
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_qtdemux_split_and_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  if (stream->subtype == FOURCC_clcp
+      && CUR_STREAM (stream)->fourcc == FOURCC_c608 && stream->need_split) {
+    GstMapInfo map;
+    guint n_output_buffers, n_field1 = 0, n_field2 = 0;
+    guint n_triplets, i;
+    guint field1_off = 0, field2_off = 0;
+
+    /* We have to split CEA608 buffers so that each outgoing buffer contains
+     * one byte pair per field according to the framerate of the video track.
+     *
+     * If there is only a single byte pair per field we don't have to do
+     * anything
+     */
+
+    gst_buffer_map (buf, &map, GST_MAP_READ);
+
+    n_triplets = map.size / 3;
+    for (i = 0; i < n_triplets; i++) {
+      if (map.data[3 * i] & 0x80)
+        n_field1++;
+      else
+        n_field2++;
+    }
+
+    g_assert (n_field1 || n_field2);
+
+    /* If there's more than 1 frame we have to split, otherwise we can just
+     * pass through */
+    if (n_field1 > 1 || n_field2 > 1) {
+      n_output_buffers =
+          gst_util_uint64_scale (GST_BUFFER_DURATION (buf),
+          CUR_STREAM (stream)->fps_n, GST_SECOND * CUR_STREAM (stream)->fps_d);
+
+      for (i = 0; i < n_output_buffers; i++) {
+        GstBuffer *outbuf =
+            gst_buffer_new_and_alloc ((n_field1 ? 3 : 0) + (n_field2 ? 3 : 0));
+        GstMapInfo outmap;
+        guint8 *outptr;
+
+        gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE);
+        outptr = outmap.data;
+
+        if (n_field1) {
+          gboolean found = FALSE;
+
+          while (map.data + field1_off < map.data + map.size) {
+            if (map.data[field1_off] & 0x80) {
+              memcpy (outptr, &map.data[field1_off], 3);
+              field1_off += 3;
+              found = TRUE;
+              break;
+            }
+            field1_off += 3;
+          }
+
+          if (!found) {
+            const guint8 empty[] = { 0x80, 0x80, 0x80 };
+
+            memcpy (outptr, empty, 3);
+          }
+
+          outptr += 3;
+        }
+
+        if (n_field2) {
+          gboolean found = FALSE;
+
+          while (map.data + field2_off < map.data + map.size) {
+            if ((map.data[field2_off] & 0x80) == 0) {
+              memcpy (outptr, &map.data[field2_off], 3);
+              field2_off += 3;
+              found = TRUE;
+              break;
+            }
+            field2_off += 3;
+          }
+
+          if (!found) {
+            const guint8 empty[] = { 0x00, 0x80, 0x80 };
+
+            memcpy (outptr, empty, 3);
+          }
+
+          outptr += 3;
+        }
+
+        gst_buffer_unmap (outbuf, &outmap);
+
+        GST_BUFFER_PTS (outbuf) =
+            GST_BUFFER_PTS (buf) + gst_util_uint64_scale (i,
+            GST_SECOND * CUR_STREAM (stream)->fps_d,
+            CUR_STREAM (stream)->fps_n);
+        GST_BUFFER_DURATION (outbuf) =
+            gst_util_uint64_scale (GST_SECOND, CUR_STREAM (stream)->fps_d,
+            CUR_STREAM (stream)->fps_n);
+        GST_BUFFER_OFFSET (outbuf) = -1;
+        GST_BUFFER_OFFSET_END (outbuf) = -1;
+
+        ret = gst_qtdemux_push_buffer (qtdemux, stream, outbuf);
+
+        if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)
+          break;
+      }
+      gst_buffer_unmap (buf, &map);
+      gst_buffer_unref (buf);
+    } else {
+      gst_buffer_unmap (buf, &map);
+      ret = gst_qtdemux_push_buffer (qtdemux, stream, buf);
+    }
+  } else {
+    ret = gst_qtdemux_push_buffer (qtdemux, stream, buf);
+  }
+
+  return ret;
+}
+
 /* Sets a buffer's attributes properly and pushes it downstream.
  * Also checks for additional actions and custom processing that may
  * need to be done first.
@@ -5769,113 +6672,48 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
   if (!buf) {
     goto exit;
   }
-
-  GST_BUFFER_DTS (buf) = dts;
-  GST_BUFFER_PTS (buf) = pts;
-  GST_BUFFER_DURATION (buf) = duration;
-  GST_BUFFER_OFFSET (buf) = -1;
-  GST_BUFFER_OFFSET_END (buf) = -1;
-
-  if (G_UNLIKELY (CUR_STREAM (stream)->rgb8_palette))
-    gst_buffer_append_memory (buf,
-        gst_memory_ref (CUR_STREAM (stream)->rgb8_palette));
-
-  if (G_UNLIKELY (CUR_STREAM (stream)->padding)) {
-    gst_buffer_resize (buf, CUR_STREAM (stream)->padding, -1);
-  }
-#if 0
-  if (G_UNLIKELY (qtdemux->element_index)) {
-    GstClockTime stream_time;
-
-    stream_time =
-        gst_segment_to_stream_time (&stream->segment, GST_FORMAT_TIME,
-        timestamp);
-    if (GST_CLOCK_TIME_IS_VALID (stream_time)) {
-      GST_LOG_OBJECT (qtdemux,
-          "adding association %" GST_TIME_FORMAT "-> %"
-          G_GUINT64_FORMAT, GST_TIME_ARGS (stream_time), byte_position);
-      gst_index_add_association (qtdemux->element_index,
-          qtdemux->index_id,
-          keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT :
-          GST_ASSOCIATION_FLAG_DELTA_UNIT, GST_FORMAT_TIME, stream_time,
-          GST_FORMAT_BYTES, byte_position, NULL);
-    }
-  }
-#endif
-
-  if (stream->need_clip)
-    buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf);
-
-  if (G_UNLIKELY (buf == NULL))
-    goto exit;
-
-  if (G_UNLIKELY (stream->discont)) {
-    GST_LOG_OBJECT (qtdemux, "marking discont buffer");
-    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
-    stream->discont = FALSE;
-  } else {
-    GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
-  }
-
-  if (!keyframe) {
-    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
-    stream->on_keyframe = FALSE;
-  } else {
-    stream->on_keyframe = TRUE;
-  }
-
-
-  GST_LOG_OBJECT (qtdemux,
-      "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
-      ", duration %" GST_TIME_FORMAT " on pad %s", GST_TIME_ARGS (dts),
-      GST_TIME_ARGS (pts), GST_TIME_ARGS (duration),
-      GST_PAD_NAME (stream->pad));
-
-  if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
-    GstStructure *crypto_info;
-    QtDemuxCencSampleSetInfo *info =
-        (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
-    gint index;
-    GstEvent *event;
-
-    while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
-      GST_TRACE_OBJECT (stream->pad, "pushing protection event: %"
-          GST_PTR_FORMAT, event);
-      gst_pad_push_event (stream->pad, event);
-    }
-
-    if (info->crypto_info == NULL) {
-      GST_DEBUG_OBJECT (qtdemux,
-          "cenc metadata hasn't been parsed yet, pushing buffer as if it wasn't encrypted");
-    } else {
-      /* The end of the crypto_info array matches our n_samples position,
-       * so count backward from there */
-      index = stream->sample_index - stream->n_samples + info->crypto_info->len;
-      if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
-        /* steal structure from array */
-        crypto_info = g_ptr_array_index (info->crypto_info, index);
-        g_ptr_array_index (info->crypto_info, index) = NULL;
-        GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u/%u]", index,
-            info->crypto_info->len);
-        if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
-          GST_ERROR_OBJECT (qtdemux,
-              "failed to attach cenc metadata to buffer");
-      } else {
-        GST_INFO_OBJECT (qtdemux, "No crypto info with index %d and sample %d",
-            index, stream->sample_index);
-      }
-    }
+
+  GST_BUFFER_DTS (buf) = dts;
+  GST_BUFFER_PTS (buf) = pts;
+  GST_BUFFER_DURATION (buf) = duration;
+  GST_BUFFER_OFFSET (buf) = -1;
+  GST_BUFFER_OFFSET_END (buf) = -1;
+
+  if (!keyframe) {
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
+    stream->on_keyframe = FALSE;
+  } else {
+    stream->on_keyframe = TRUE;
   }
 
-  if (stream->alignment > 1)
-    buf = gst_qtdemux_align_buffer (qtdemux, buf, stream->alignment);
+  if (G_UNLIKELY (CUR_STREAM (stream)->rgb8_palette))
+    gst_buffer_append_memory (buf,
+        gst_memory_ref (CUR_STREAM (stream)->rgb8_palette));
 
-  ret = gst_pad_push (stream->pad, buf);
+  if (G_UNLIKELY (CUR_STREAM (stream)->padding)) {
+    gst_buffer_resize (buf, CUR_STREAM (stream)->padding, -1);
+  }
+#if 0
+  if (G_UNLIKELY (qtdemux->element_index)) {
+    GstClockTime stream_time;
 
-  if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
-    /* mark position in stream, we'll need this to know when to send GAP event */
-    stream->segment.position = pts + duration;
+    stream_time =
+        gst_segment_to_stream_time (&stream->segment, GST_FORMAT_TIME,
+        timestamp);
+    if (GST_CLOCK_TIME_IS_VALID (stream_time)) {
+      GST_LOG_OBJECT (qtdemux,
+          "adding association %" GST_TIME_FORMAT "-> %"
+          G_GUINT64_FORMAT, GST_TIME_ARGS (stream_time), byte_position);
+      gst_index_add_association (qtdemux->element_index,
+          qtdemux->index_id,
+          keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT :
+          GST_ASSOCIATION_FLAG_DELTA_UNIT, GST_FORMAT_TIME, stream_time,
+          GST_FORMAT_BYTES, byte_position, NULL);
+    }
   }
+#endif
+
+  ret = gst_qtdemux_split_and_push_buffer (qtdemux, stream, buf);
 
 exit:
   return ret;
@@ -5910,20 +6748,20 @@ static gboolean
 gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux)
 {
   const QtDemuxRandomAccessEntry *best_entry = NULL;
-  GList *iter;
+  gint i;
 
   GST_OBJECT_LOCK (qtdemux);
 
-  g_assert (qtdemux->n_streams > 0);
+  g_assert (QTDEMUX_N_STREAMS (qtdemux) > 0);
 
   /* first see if we can determine where to go to using mfra,
    * before we start clearing things */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     const QtDemuxRandomAccessEntry *entry;
     QtDemuxStream *stream;
     gboolean is_audio_or_video;
 
-    stream = QTDEMUX_STREAM (iter->data);
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     if (stream->ra_entries == NULL)
       continue;
@@ -5957,10 +6795,10 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux)
   }
 
   /* ok, now we can prepare for processing as of located moof */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     QtDemuxStream *stream;
 
-    stream = QTDEMUX_STREAM (iter->data);
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     g_free (stream->samples);
     stream->samples = NULL;
@@ -5983,7 +6821,7 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux)
 
   GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment "
       "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (QTDEMUX_FIRST_STREAM (qtdemux)->time_position),
+      GST_TIME_ARGS (QTDEMUX_NTH_STREAM (qtdemux, 0)->time_position),
       best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts));
 
   qtdemux->moof_offset = best_entry->moof_offset;
@@ -6009,9 +6847,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
   guint sample_size = 0;
   gboolean empty = 0;
   guint size;
-  GList *iter;
-
-  gst_qtdemux_push_pending_newsegment (qtdemux);
+  gint i;
 
   if (qtdemux->fragmented_seek_pending) {
     GST_INFO_OBJECT (qtdemux, "pending fragmented seek");
@@ -6026,10 +6862,10 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
   /* Figure out the next stream sample to output, min_time is expressed in
    * global time and runs over the edit list segments. */
   min_time = G_MAXUINT64;
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     GstClockTime position;
 
-    stream = QTDEMUX_STREAM (iter->data);
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     position = stream->time_position;
 
     /* position of -1 is EOS */
@@ -6056,8 +6892,8 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
   }
 
   /* gap events for subtitle streams */
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     if (stream->pad && (stream->subtype == FOURCC_subp
             || stream->subtype == FOURCC_text
             || stream->subtype == FOURCC_sbtl)) {
@@ -6083,12 +6919,12 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
   gst_qtdemux_stream_check_and_change_stsd_index (qtdemux, stream);
   if (stream->new_caps) {
     gst_qtdemux_configure_stream (qtdemux, stream);
-    qtdemux_do_allocation (qtdemux, stream);
+    qtdemux_do_allocation (stream, qtdemux);
   }
 
   /* If we're doing a keyframe-only trickmode, only push keyframes on video streams */
-  if (G_UNLIKELY (qtdemux->
-          segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
+  if (G_UNLIKELY (qtdemux->segment.
+          flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
     if (stream->subtype == FOURCC_vide && !keyframe) {
       GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on track-id %u",
           stream->track_id);
@@ -6297,7 +7133,7 @@ pause:
     /* fatal errors need special actions */
     /* check EOS */
     if (ret == GST_FLOW_EOS) {
-      if (qtdemux->n_streams == 0) {
+      if (QTDEMUX_N_STREAMS (qtdemux) == 0) {
         /* we have no streams, post an error */
         gst_qtdemux_post_no_playable_stream_error (qtdemux);
       }
@@ -6364,12 +7200,12 @@ static gboolean
 has_next_entry (GstQTDemux * demux)
 {
   QtDemuxStream *stream;
-  GList *iter;
+  gint i;
 
   GST_DEBUG_OBJECT (demux, "Checking if there are samples not played yet");
 
-  for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-    stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+    stream = QTDEMUX_NTH_STREAM (demux, i);
 
     if (stream->sample_index == -1) {
       stream->sample_index = 0;
@@ -6400,13 +7236,13 @@ next_entry_size (GstQTDemux * demux)
   QtDemuxStream *stream, *target_stream = NULL;
   guint64 smalloffs = (guint64) - 1;
   QtDemuxSample *sample;
-  GList *iter;
+  gint i;
 
   GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT,
       demux->offset);
 
-  for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-    stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+    stream = QTDEMUX_NTH_STREAM (demux, i);
 
     if (stream->sample_index == -1) {
       stream->sample_index = 0;
@@ -6545,18 +7381,28 @@ gst_qtdemux_drop_data (GstQTDemux * demux, gint bytes)
   demux->todrop -= bytes;
 }
 
+/* PUSH-MODE only: Send a segment, if not done already. */
 static void
 gst_qtdemux_check_send_pending_segment (GstQTDemux * demux)
 {
   if (G_UNLIKELY (demux->need_segment)) {
     gint i;
-    GList *iter;
 
-    gst_qtdemux_push_pending_newsegment (demux);
+    if (!demux->upstream_format_is_time) {
+      gst_qtdemux_map_and_push_segments (demux, &demux->segment);
+    } else {
+      GstEvent *segment_event;
+      segment_event = gst_event_new_segment (&demux->segment);
+      if (demux->segment_seqnum != GST_SEQNUM_INVALID)
+        gst_event_set_seqnum (segment_event, demux->segment_seqnum);
+      gst_qtdemux_push_event (demux, segment_event);
+    }
+
+    demux->need_segment = FALSE;
+
     /* clear to send tags on all streams */
-    for (iter = demux->active_streams, i = 0; iter;
-        iter = g_list_next (iter), i++) {
-      QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+    for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (demux, i);
       gst_qtdemux_push_tags (demux, stream);
       if (CUR_STREAM (stream)->sparse) {
         GST_INFO_OBJECT (demux, "Sending gap event on stream %d", i);
@@ -6593,35 +7439,6 @@ gst_qtdemux_send_gap_for_segment (GstQTDemux * demux,
   }
 }
 
-static GstClockTime
-gst_qtdemux_streams_get_first_sample_ts (GstQTDemux * demux)
-{
-  GstClockTime res = GST_CLOCK_TIME_NONE;
-  GList *iter;
-
-  for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
-    if (stream->n_samples) {
-      res = MIN (QTSAMPLE_PTS (stream, &stream->samples[0]), res);
-      res = MIN (QTSAMPLE_DTS (stream, &stream->samples[0]), res);
-    }
-  }
-  return res;
-}
-
-static GstClockTime
-gst_qtdemux_streams_have_samples (GstQTDemux * demux)
-{
-  GList *iter;
-
-  for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
-    if (stream->n_samples)
-      return TRUE;
-  }
-  return FALSE;
-}
-
 static GstFlowReturn
 gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
 {
@@ -6638,12 +7455,12 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
 
   if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT)) {
     gboolean is_gap_input = FALSE;
-    GList *iter;
+    gint i;
 
     GST_DEBUG_OBJECT (demux, "Got DISCONT, marking all streams as DISCONT");
 
-    for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-      QTDEMUX_STREAM (iter->data)->discont = TRUE;
+    for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+      QTDEMUX_NTH_STREAM (demux, i)->discont = TRUE;
     }
 
     /* Check if we can land back on our feet in the case where upstream is
@@ -6651,9 +7468,9 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
      * in the case of trick-mode DASH for example) */
     if (demux->upstream_format_is_time
         && GST_BUFFER_OFFSET (inbuf) != GST_BUFFER_OFFSET_NONE) {
-      for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
+      for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
         guint32 res;
-        QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+        QtDemuxStream *stream = QTDEMUX_NTH_STREAM (demux, i);
         GST_LOG_OBJECT (demux,
             "track-id #%u , checking if offset %" G_GUINT64_FORMAT
             " is a sample start", stream->track_id, GST_BUFFER_OFFSET (inbuf));
@@ -6701,7 +7518,7 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
      * previously received one. */
     if (!is_gap_input && demux->fragmented && demux->segment.rate < 0) {
       gst_qtdemux_process_adapter (demux, TRUE);
-      g_list_foreach (demux->active_streams,
+      g_ptr_array_foreach (demux->active_streams,
           (GFunc) gst_qtdemux_stream_flush_samples_data, NULL);
     }
   }
@@ -6771,7 +7588,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         }
         if (fourcc == FOURCC_mdat) {
           gint next_entry = next_entry_size (demux);
-          if (demux->n_streams > 0 && (next_entry != -1 || !demux->fragmented)) {
+          if (QTDEMUX_N_STREAMS (demux) > 0 && (next_entry != -1
+                  || !demux->fragmented)) {
             /* we have the headers, start playback */
             demux->state = QTDEMUX_STATE_MOVIE;
             demux->neededbytes = next_entry;
@@ -6861,7 +7679,7 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         if (fourcc == FOURCC_moov) {
           /* in usual fragmented setup we could try to scan for more
            * and end up at the the moov (after mdat) again */
-          if (demux->got_moov && demux->n_streams > 0 &&
+          if (demux->got_moov && QTDEMUX_N_STREAMS (demux) > 0 &&
               (!demux->fragmented
                   || demux->last_moov_offset == demux->offset)) {
             GST_DEBUG_OBJECT (demux,
@@ -6886,9 +7704,8 @@ 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;
+            gst_qtdemux_stream_concat (demux,
+                demux->old_streams, demux->active_streams);
 
             qtdemux_parse_moov (demux, data, demux->neededbytes);
             qtdemux_node_dump (demux, demux->moov_node);
@@ -6899,10 +7716,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
             QTDEMUX_EXPOSE_UNLOCK (demux);
 
             demux->got_moov = TRUE;
-            demux->need_segment = TRUE;
-            gst_qtdemux_map_and_push_segments (demux, &demux->segment);
-            if (demux->exposed)
-              demux->need_segment = FALSE;
+
+            gst_qtdemux_check_send_pending_segment (demux);
 
             if (demux->moov_node_compressed) {
               g_node_destroy (demux->moov_node_compressed);
@@ -7029,12 +7844,12 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         gst_adapter_unmap (demux->adapter);
         data = NULL;
 
-        if (demux->mdatbuffer && demux->n_streams) {
+        if (demux->mdatbuffer && QTDEMUX_N_STREAMS (demux)) {
           gsize remaining_data_size = 0;
 
           /* the mdat was before the header */
           GST_DEBUG_OBJECT (demux, "We have n_streams:%d and mdatbuffer:%p",
-              demux->n_streams, demux->mdatbuffer);
+              QTDEMUX_N_STREAMS (demux), demux->mdatbuffer);
           /* restore our adapter/offset view of things with upstream;
            * put preceding buffered data ahead of current moov data.
            * This should also handle evil mdat, moov, mdat cases and alike */
@@ -7111,7 +7926,7 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         QtDemuxSample *sample;
         GstClockTime dts, pts, duration;
         gboolean keyframe;
-        GList *iter;
+        gint i;
 
         GST_DEBUG_OBJECT (demux,
             "BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset);
@@ -7162,7 +7977,7 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
             data = gst_adapter_map (demux->adapter, demux->todrop);
             gst_byte_reader_init (&br, data + 8, demux->todrop);
             if (!qtdemux_parse_cenc_aux_info (demux,
-                    QTDEMUX_FIRST_STREAM (demux), &br,
+                    QTDEMUX_NTH_STREAM (demux, 0), &br,
                     demux->cenc_aux_info_sizes, demux->cenc_aux_sample_count)) {
               GST_ERROR_OBJECT (demux, "failed to parse cenc auxiliary info");
               ret = GST_FLOW_ERROR;
@@ -7185,8 +8000,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         gst_qtdemux_check_send_pending_segment (demux);
 
         /* Figure out which stream this packet belongs to */
-        for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-          stream = QTDEMUX_STREAM (iter->data);
+        for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+          stream = QTDEMUX_NTH_STREAM (demux, i);
           if (stream->sample_index >= stream->n_samples) {
             /* reset to be checked below G_UNLIKELY (stream == NULL) */
             stream = NULL;
@@ -7226,7 +8041,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
 
           /* check for segment end */
           if (G_UNLIKELY (demux->segment.stop != -1
-                  && demux->segment.stop <= pts && stream->on_keyframe)) {
+                  && demux->segment.stop <= pts && stream->on_keyframe)
+              && !(demux->upstream_format_is_time && demux->segment.rate < 0)) {
             GST_DEBUG_OBJECT (demux, "we reached the end of our segment.");
             stream->time_position = GST_CLOCK_TIME_NONE;        /* this means EOS */
 
@@ -7236,8 +8052,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
 
             /* check if all streams are eos */
             ret = GST_FLOW_EOS;
-            for (iter = demux->active_streams; iter; iter = g_list_next (iter)) {
-              if (!STREAM_IS_EOS (QTDEMUX_STREAM (iter->data))) {
+            for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+              if (!STREAM_IS_EOS (QTDEMUX_NTH_STREAM (demux, i))) {
                 ret = GST_FLOW_OK;
                 break;
               }
@@ -7878,6 +8694,13 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
         qtdemux_parse_container (qtdemux, node, buffer + 36, end);
         break;
       }
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+      case FOURCC_SA3D:
+      {
+        qtdemux_parse_SA3D (qtdemux, buffer, end - buffer);
+        break;
+      }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
       default:
         if (!strcmp (type->name, "unknown"))
           GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4);
@@ -7994,7 +8817,7 @@ qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc)
 }
 
 static void
-qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream)
+qtdemux_do_allocation (QtDemuxStream * stream, GstQTDemux * qtdemux)
 {
 /* FIXME: This can only reliably work if demuxers have a
  * separate streaming thread per srcpad. This should be
@@ -8100,7 +8923,9 @@ gst_qtdemux_request_protection_context (GstQTDemux * qtdemux,
       qtdemux->protection_system_ids->len - 1);
   GST_TRACE_OBJECT (qtdemux, "detected %u protection systems, we have "
       "decryptors for %u of them, running context request",
-      qtdemux->protection_system_ids->len, g_strv_length (filtered_sys_ids));
+      qtdemux->protection_system_ids->len,
+      filtered_sys_ids ? g_strv_length (filtered_sys_ids) : 0);
+
 
   if (stream->protection_scheme_event_queue.length) {
     GST_TRACE_OBJECT (qtdemux, "using stream event queue, length %u",
@@ -8177,7 +9002,9 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
       FALSE);
 
   if (stream->protection_scheme_type != FOURCC_cenc) {
-    GST_ERROR_OBJECT (qtdemux, "unsupported protection scheme");
+    GST_ERROR_OBJECT (qtdemux,
+        "unsupported protection scheme: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (stream->protection_scheme_type));
     return FALSE;
   }
   if (qtdemux->protection_system_ids == NULL) {
@@ -8232,74 +9059,91 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
 }
 
 static gboolean
-gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_guess_framerate (GstQTDemux * qtdemux, QtDemuxStream * stream)
 {
-  if (stream->subtype == FOURCC_vide) {
-    /* fps is calculated base on the duration of the average framerate since
-     * qt does not have a fixed framerate. */
-    gboolean fps_available = TRUE;
-    guint32 first_duration = 0;
-
-    if (stream->n_samples > 0)
-      first_duration = stream->samples[0].duration;
-
-    if ((stream->n_samples == 1 && first_duration == 0)
-        || (qtdemux->fragmented && stream->n_samples_moof == 1)) {
-      /* still frame */
-      CUR_STREAM (stream)->fps_n = 0;
+  /* fps is calculated base on the duration of the average framerate since
+   * qt does not have a fixed framerate. */
+  gboolean fps_available = TRUE;
+  guint32 first_duration = 0;
+
+  if (stream->n_samples > 0)
+    first_duration = stream->samples[0].duration;
+
+  if ((stream->n_samples == 1 && first_duration == 0)
+      || (qtdemux->fragmented && stream->n_samples_moof == 1)) {
+    /* still frame */
+    CUR_STREAM (stream)->fps_n = 0;
+    CUR_STREAM (stream)->fps_d = 1;
+  } else {
+    if (stream->duration == 0 || stream->n_samples < 2) {
+      CUR_STREAM (stream)->fps_n = stream->timescale;
       CUR_STREAM (stream)->fps_d = 1;
+      fps_available = FALSE;
     } else {
-      if (stream->duration == 0 || stream->n_samples < 2) {
-        CUR_STREAM (stream)->fps_n = stream->timescale;
-        CUR_STREAM (stream)->fps_d = 1;
-        fps_available = FALSE;
+      GstClockTime avg_duration;
+      guint64 duration;
+      guint32 n_samples;
+
+      /* duration and n_samples can be updated for fragmented format
+       * so, framerate of fragmented format is calculated using data in a moof */
+      if (qtdemux->fragmented && stream->n_samples_moof > 0
+          && stream->duration_moof > 0) {
+        n_samples = stream->n_samples_moof;
+        duration = stream->duration_moof;
       } else {
-        GstClockTime avg_duration;
-        guint64 duration;
-        guint32 n_samples;
-
-        /* duration and n_samples can be updated for fragmented format
-         * so, framerate of fragmented format is calculated using data in a moof */
-        if (qtdemux->fragmented && stream->n_samples_moof > 0
-            && stream->duration_moof > 0) {
-          n_samples = stream->n_samples_moof;
-          duration = stream->duration_moof;
-        } else {
-          n_samples = stream->n_samples;
-          duration = stream->duration;
-        }
-
-        /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
-        /* stream->duration is guint64, timescale, n_samples are guint32 */
-        avg_duration =
-            gst_util_uint64_scale_round (duration -
-            first_duration, GST_SECOND,
-            (guint64) (stream->timescale) * (n_samples - 1));
+        n_samples = stream->n_samples;
+        duration = stream->duration;
+      }
 
-        GST_LOG_OBJECT (qtdemux,
-            "Calculating avg sample duration based on stream (or moof) duration %"
-            G_GUINT64_FORMAT
-            " minus first sample %u, leaving %d samples gives %"
-            GST_TIME_FORMAT, duration, first_duration,
-            n_samples - 1, GST_TIME_ARGS (avg_duration));
+      /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
+      /* stream->duration is guint64, timescale, n_samples are guint32 */
+      avg_duration =
+          gst_util_uint64_scale_round (duration -
+          first_duration, GST_SECOND,
+          (guint64) (stream->timescale) * (n_samples - 1));
 
-        gst_video_guess_framerate (avg_duration, &CUR_STREAM (stream)->fps_n,
-            &CUR_STREAM (stream)->fps_d);
+      GST_LOG_OBJECT (qtdemux,
+          "Calculating avg sample duration based on stream (or moof) duration %"
+          G_GUINT64_FORMAT
+          " minus first sample %u, leaving %d samples gives %"
+          GST_TIME_FORMAT, duration, first_duration,
+          n_samples - 1, GST_TIME_ARGS (avg_duration));
+
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+      gst_video_guess_framerate (avg_duration,
+        &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d);
+      if (CUR_STREAM (stream)->fps_d == 0)
+        fps_available = FALSE;
+#else
+      fps_available =
+          gst_video_guess_framerate (avg_duration,
+          &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d);
+#endif
 
-        GST_DEBUG_OBJECT (qtdemux,
-            "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
-            stream->timescale, CUR_STREAM (stream)->fps_n,
-            CUR_STREAM (stream)->fps_d);
-      }
+      GST_DEBUG_OBJECT (qtdemux,
+          "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
+          stream->timescale, CUR_STREAM (stream)->fps_n,
+          CUR_STREAM (stream)->fps_d);
     }
+  }
+
+  return fps_available;
+}
+
+static gboolean
+gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+{
+  if (stream->subtype == FOURCC_vide) {
+    gboolean fps_available = gst_qtdemux_guess_framerate (qtdemux, stream);
 
     if (CUR_STREAM (stream)->caps) {
       CUR_STREAM (stream)->caps =
           gst_caps_make_writable (CUR_STREAM (stream)->caps);
 
-      gst_caps_set_simple (CUR_STREAM (stream)->caps,
-          "width", G_TYPE_INT, CUR_STREAM (stream)->width,
-          "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL);
+      if (CUR_STREAM (stream)->width && CUR_STREAM (stream)->height)
+        gst_caps_set_simple (CUR_STREAM (stream)->caps,
+            "width", G_TYPE_INT, CUR_STREAM (stream)->width,
+            "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL);
 
       /* set framerate if calculated framerate is reliable */
       if (fps_available) {
@@ -8410,6 +9254,56 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
     }
   }
 
+  else if (stream->subtype == FOURCC_clcp && CUR_STREAM (stream)->caps) {
+    const GstStructure *s;
+    QtDemuxStream *fps_stream = NULL;
+    gboolean fps_available = FALSE;
+
+    /* CEA608 closed caption tracks are a bit special in that each sample
+     * can contain CCs for multiple frames, and CCs can be omitted and have to
+     * be inferred from the duration of the sample then.
+     *
+     * As such we take the framerate from the (first) video track here for
+     * CEA608 as there must be one CC byte pair for every video frame
+     * according to the spec.
+     *
+     * For CEA708 all is fine and there is one sample per frame.
+     */
+
+    s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
+    if (gst_structure_has_name (s, "closedcaption/x-cea-608")) {
+      gint i;
+
+      for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+        QtDemuxStream *tmp = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+        if (tmp->subtype == FOURCC_vide) {
+          fps_stream = tmp;
+          break;
+        }
+      }
+
+      if (fps_stream) {
+        fps_available = gst_qtdemux_guess_framerate (qtdemux, fps_stream);
+        CUR_STREAM (stream)->fps_n = CUR_STREAM (fps_stream)->fps_n;
+        CUR_STREAM (stream)->fps_d = CUR_STREAM (fps_stream)->fps_d;
+      }
+    } else {
+      fps_available = gst_qtdemux_guess_framerate (qtdemux, stream);
+      fps_stream = stream;
+    }
+
+    CUR_STREAM (stream)->caps =
+        gst_caps_make_writable (CUR_STREAM (stream)->caps);
+
+    /* set framerate if calculated framerate is reliable */
+    if (fps_available) {
+      gst_caps_set_simple (CUR_STREAM (stream)->caps,
+          "framerate", GST_TYPE_FRACTION, CUR_STREAM (stream)->fps_n,
+          CUR_STREAM (stream)->fps_d, NULL);
+    }
+  }
+
   if (stream->pad) {
     GstCaps *prev_caps = NULL;
 
@@ -8512,8 +9406,6 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
     QtDemuxStream * stream, GstTagList * list)
 {
   gboolean ret = TRUE;
-  /* consistent default for push based mode */
-  gst_segment_init (&stream->segment, GST_FORMAT_TIME);
 
   if (stream->subtype == FOURCC_vide) {
     gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams);
@@ -8546,7 +9438,8 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
   } else if (stream->subtype == FOURCC_strm) {
     GST_DEBUG_OBJECT (qtdemux, "stream type, not creating pad");
   } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text
-      || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt) {
+      || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt
+      || stream->subtype == FOURCC_clcp) {
     gchar *name = g_strdup_printf ("subtitle_%u", qtdemux->n_sub_streams);
 
     stream->pad =
@@ -8946,6 +9839,18 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl)
 
         gst_byte_reader_skip_unchecked (&stream->ctts, 4);
         offset = gst_byte_reader_get_int32_be_unchecked (&stream->ctts);
+        /* HACK: if sample_offset is larger than 2 * duration, ignore the box.
+         * slightly inaccurate PTS could be more usable than corrupted one */
+        if (G_UNLIKELY ((ABS (offset) / 2) > stream->duration)) {
+          GST_WARNING_OBJECT (qtdemux,
+              "Ignore corrupted ctts, sample_offset %" G_GINT32_FORMAT
+              " larger than duration %" G_GUINT64_FORMAT,
+              offset, stream->duration);
+
+          stream->cslg_shift = 0;
+          stream->ctts_present = FALSE;
+          return TRUE;
+        }
 
         if (offset < cslg_least)
           cslg_least = offset;
@@ -9551,14 +10456,15 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream,
           GST_TIME_ARGS (segment->media_stop),
           GST_TIME_ARGS (segment->stop_time), segment->rate, rate_int,
           stream->timescale);
-      if (segment->stop_time > qtdemux->segment.stop) {
+      if (segment->stop_time > qtdemux->segment.stop &&
+          !qtdemux->upstream_format_is_time) {
         GST_WARNING_OBJECT (qtdemux, "Segment %d "
             " extends to %" GST_TIME_FORMAT
-            " past the end of the file duration %" GST_TIME_FORMAT
-            " it will be truncated", segment_number,
+            " past the end of the declared movie duration %" GST_TIME_FORMAT
+            " movie segment will be extended", segment_number,
             GST_TIME_ARGS (segment->stop_time),
             GST_TIME_ARGS (qtdemux->segment.stop));
-        qtdemux->segment.stop = segment->stop_time;
+        qtdemux->segment.stop = qtdemux->segment.duration = segment->stop_time;
       }
 
       buffer += entry_size;
@@ -9957,6 +10863,9 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
   GNode *frma;
   GNode *schm;
   GNode *schi;
+  QtDemuxCencSampleSetInfo *info;
+  GNode *tenc;
+  const guint8 *tenc_data;
 
   g_return_val_if_fail (qtdemux != NULL, FALSE);
   g_return_val_if_fail (stream != NULL, FALSE);
@@ -10003,20 +10912,24 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
     GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schi box");
     return FALSE;
   }
+  if (stream->protection_scheme_type != FOURCC_cenc &&
+      stream->protection_scheme_type != FOURCC_piff) {
+    GST_ERROR_OBJECT (qtdemux,
+        "Invalid protection_scheme_type: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (stream->protection_scheme_type));
+    return FALSE;
+  }
+
+  if (G_UNLIKELY (!stream->protection_scheme_info))
+    stream->protection_scheme_info =
+        g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
+
+  info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+
   if (stream->protection_scheme_type == FOURCC_cenc) {
-    QtDemuxCencSampleSetInfo *info;
-    GNode *tenc;
-    const guint8 *tenc_data;
-    guint32 isEncrypted;
+    guint32 is_encrypted;
     guint8 iv_size;
     const guint8 *default_kid;
-    GstBuffer *kid_buf;
-
-    if (G_UNLIKELY (!stream->protection_scheme_info))
-      stream->protection_scheme_info =
-          g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
-
-    info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
 
     tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_tenc);
     if (!tenc) {
@@ -10025,29 +10938,50 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
       return FALSE;
     }
     tenc_data = (const guint8 *) tenc->data + 12;
-    isEncrypted = QT_UINT24 (tenc_data);
+    is_encrypted = QT_UINT24 (tenc_data);
     iv_size = QT_UINT8 (tenc_data + 3);
     default_kid = (tenc_data + 4);
-    kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
-    gst_buffer_fill (kid_buf, 0, default_kid, 16);
-    if (info->default_properties)
-      gst_structure_free (info->default_properties);
-    info->default_properties =
-        gst_structure_new ("application/x-cenc",
-        "iv_size", G_TYPE_UINT, iv_size,
-        "encrypted", G_TYPE_BOOLEAN, (isEncrypted == 1),
-        "kid", GST_TYPE_BUFFER, kid_buf, NULL);
-    GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
-        "is_encrypted=%u, iv_size=%u", isEncrypted, iv_size);
-    gst_buffer_unref (kid_buf);
+    qtdemux_update_default_sample_encryption_settings (qtdemux, info,
+        is_encrypted, iv_size, default_kid);
+  } else if (stream->protection_scheme_type == FOURCC_piff) {
+    GstByteReader br;
+    static const guint8 piff_track_encryption_uuid[] = {
+      0x89, 0x74, 0xdb, 0xce, 0x7b, 0xe7, 0x4c, 0x51,
+      0x84, 0xf9, 0x71, 0x48, 0xf9, 0x88, 0x25, 0x54
+    };
+
+    tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_uuid);
+    if (!tenc) {
+      GST_ERROR_OBJECT (qtdemux, "schi box does not contain tenc box, "
+          "which is mandatory for Common Encryption");
+      return FALSE;
+    }
+
+    tenc_data = (const guint8 *) tenc->data + 8;
+    if (memcmp (tenc_data, piff_track_encryption_uuid, 16) != 0) {
+      gchar *box_uuid = qtdemux_uuid_bytes_to_string (tenc_data);
+      GST_ERROR_OBJECT (qtdemux,
+          "Unsupported track encryption box with uuid: %s", box_uuid);
+      g_free (box_uuid);
+      return FALSE;
+    }
+    tenc_data = (const guint8 *) tenc->data + 16 + 12;
+    gst_byte_reader_init (&br, tenc_data, 20);
+    if (!qtdemux_update_default_piff_encryption_settings (qtdemux, info, &br)) {
+      GST_ERROR_OBJECT (qtdemux, "PIFF track box parsing error");
+      return FALSE;
+    }
+    stream->protection_scheme_type = FOURCC_cenc;
   }
+
   return TRUE;
 }
 
 static gint
-qtdemux_track_id_compare_func (QtDemuxStream * stream1, QtDemuxStream * stream2)
+qtdemux_track_id_compare_func (QtDemuxStream ** stream1,
+    QtDemuxStream ** stream2)
 {
-  return (gint) stream1->track_id - (gint) stream2->track_id;
+  return (gint) (*stream1)->track_id - (gint) (*stream2)->track_id;
 }
 
 /* parse the traks.
@@ -10108,6 +11042,22 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   stream = _create_stream (qtdemux, track_id);
   stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  if (!gst_byte_reader_skip (&tkhd, 4))
+    goto corrupt_file;
+
+  if (tkhd_version == 1) {
+    if (!gst_byte_reader_get_uint64_be (&tkhd, &stream->tkhd_duration))
+      goto corrupt_file;
+  } else {
+    guint32 dur = 0;
+    if (!gst_byte_reader_get_uint32_be (&tkhd, &dur))
+      goto corrupt_file;
+    stream->tkhd_duration = dur;
+  }
+  GST_INFO_OBJECT (qtdemux, "tkhd duration: %" G_GUINT64_FORMAT,
+      stream->tkhd_duration);
+#endif
   /* need defaults for fragments */
   qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy);
 
@@ -10131,11 +11081,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   version = QT_UINT32 ((guint8 *) mdhd->data + 8);
   GST_LOG_OBJECT (qtdemux, "track version/flags: %08x", version);
   if (version == 0x01000000) {
-    if (len < 38)
+    if (len < 42)
       goto corrupt_file;
     stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 28);
     stream->duration = QT_UINT64 ((guint8 *) mdhd->data + 32);
-    lang_code = QT_UINT16 ((guint8 *) mdhd->data + 36);
+    lang_code = QT_UINT16 ((guint8 *) mdhd->data + 40);
   } else {
     if (len < 30)
       goto corrupt_file;
@@ -10200,7 +11150,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           "found, assuming preview image or something; skipping track",
           stream->duration, stream->timescale, qtdemux->duration,
           qtdemux->timescale);
-      gst_qtdemux_stream_free (stream);
+      gst_qtdemux_stream_unref (stream);
       return TRUE;
     }
   }
@@ -10273,7 +11223,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
     guint32 matrix[9];
 
     /* version 1 uses some 64-bit ints */
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+    if (!gst_byte_reader_skip (&tkhd, 16))
+#else
     if (!gst_byte_reader_skip (&tkhd, 20 + value_size))
+#endif
       goto corrupt_file;
 
     if (!qtdemux_parse_transformation_matrix (qtdemux, &tkhd, matrix, "tkhd"))
@@ -10297,7 +11251,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   if (stsd_len < 24) {
     /* .. but skip stream with empty stsd produced by some Vivotek cameras */
     if (stream->subtype == FOURCC_vivo) {
-      gst_qtdemux_stream_free (stream);
+      gst_qtdemux_stream_unref (stream);
       return TRUE;
     } else {
       goto corrupt_file;
@@ -10513,14 +11467,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       fiel = NULL;
       /* pick 'the' stsd child */
       mp4v = qtdemux_tree_get_child_by_index (stsd, stsd_index);
-      if (!stream->protected) {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) {
+      // We should skip parsing the stsd for non-protected streams if
+      // the entry doesn't match the fourcc, since they don't change
+      // format. However, for protected streams we can have partial
+      // encryption, where parts of the stream are encrypted and parts
+      // not. For both parts of such streams, we should ensure the
+      // esds overrides are parsed for both from the stsd.
+      if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) {
+        if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv)
           mp4v = NULL;
-        }
-      } else {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) {
+        else if (!stream->protected)
           mp4v = NULL;
-        }
       }
 
       if (mp4v) {
@@ -11214,6 +12171,81 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             }
             break;
           }
+          case FOURCC_av01:
+          {
+            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            const guint8 *av1_data = stsd_entry_data + 0x56;
+
+            /* find av1C */
+            while (len >= 0x8) {
+              gint size;
+
+              if (QT_UINT32 (av1_data) <= len)
+                size = QT_UINT32 (av1_data) - 0x8;
+              else
+                size = len - 0x8;
+
+              if (size < 1)
+                /* No real data, so break out */
+                break;
+
+              switch (QT_FOURCC (av1_data + 0x4)) {
+                case FOURCC_av1C:
+                {
+                  /* parse, if found */
+                  GstBuffer *buf;
+                  guint8 pres_delay_field;
+
+                  GST_DEBUG_OBJECT (qtdemux,
+                      "found av1C codec_data in stsd of size %d", size);
+
+                  /* not enough data, just ignore and hope for the best */
+                  if (size < 5)
+                    break;
+
+                  /* Content is:
+                   * 4 bytes: atom length
+                   * 4 bytes: fourcc
+                   * 1 byte: version
+                   * 3 bytes: flags
+                   * 3 bits: reserved
+                   * 1 bits:  initial_presentation_delay_present
+                   * 4 bits: initial_presentation_delay (if present else reserved
+                   * rest: OBUs.
+                   */
+
+                  if (av1_data[9] != 0) {
+                    GST_WARNING ("Unknown version %d of av1C box", av1_data[9]);
+                    break;
+                  }
+
+                  /* We skip initial_presentation_delay* for now */
+                  pres_delay_field = *(av1_data + 12);
+                  if (pres_delay_field & (1 << 5)) {
+                    gst_caps_set_simple (entry->caps,
+                        "presentation-delay", G_TYPE_INT,
+                        (gint) (pres_delay_field & 0x0F) + 1, NULL);
+                  }
+                  if (size > 5) {
+                    buf = gst_buffer_new_and_alloc (size - 5);
+                    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
+                    gst_buffer_fill (buf, 0, av1_data + 13, size - 5);
+                    gst_caps_set_simple (entry->caps,
+                        "codec_data", GST_TYPE_BUFFER, buf, NULL);
+                    gst_buffer_unref (buf);
+                  }
+                  break;
+                }
+                default:
+                  break;
+              }
+
+              len -= size + 8;
+              av1_data += size + 8;
+            }
+
+            break;
+          }
           default:
             break;
         }
@@ -11629,20 +12661,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       }
 
       mp4a = qtdemux_tree_get_child_by_index (stsd, stsd_index);
-      if (!stream->protected) {
-      } else {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) {
-          mp4v = NULL;
-        }
-      }
-      if (stream->protected && fourcc == FOURCC_mp4a) {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca) {
+      if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != fourcc) {
+        if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca)
           mp4a = NULL;
-        }
-      } else {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_mp4a) {
+        else if (!stream->protected)
           mp4a = NULL;
-        }
       }
 
       wave = NULL;
@@ -12099,11 +13122,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
 
   /* Insert and sort new stream in track-id order.
    * This will help in comparing old/new streams during stream update check */
-  qtdemux->active_streams =
-      g_list_insert_sorted (qtdemux->active_streams, stream,
+  g_ptr_array_add (qtdemux->active_streams, stream);
+  g_ptr_array_sort (qtdemux->active_streams,
       (GCompareFunc) qtdemux_track_id_compare_func);
-  qtdemux->n_streams++;
-  GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams);
+  GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d",
+      QTDEMUX_N_STREAMS (qtdemux));
 
   return TRUE;
 
@@ -12113,13 +13136,13 @@ corrupt_file:
     GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
         (_("This file is corrupt and cannot be played.")), (NULL));
     if (stream)
-      gst_qtdemux_stream_free (stream);
+      gst_qtdemux_stream_unref (stream);
     return FALSE;
   }
 error_encrypted:
   {
     GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (NULL), (NULL));
-    gst_qtdemux_stream_free (stream);
+    gst_qtdemux_stream_unref (stream);
     return FALSE;
   }
 samples_failed:
@@ -12128,7 +13151,7 @@ segments_failed:
     /* we posted an error already */
     /* free stbl sub-atoms */
     gst_qtdemux_stbl_free (stream);
-    gst_qtdemux_stream_free (stream);
+    gst_qtdemux_stream_unref (stream);
     return FALSE;
   }
 existing_stream:
@@ -12141,7 +13164,7 @@ unknown_stream:
   {
     GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT,
         GST_FOURCC_ARGS (stream->subtype));
-    gst_qtdemux_stream_free (stream);
+    gst_qtdemux_stream_unref (stream);
     return TRUE;
   }
 }
@@ -12158,7 +13181,7 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux)
   gint64 size, sys_bitrate, sum_bitrate = 0;
   GstClockTime duration;
   guint bitrate;
-  GList *iter;
+  gint i;
 
   if (qtdemux->fragmented)
     return;
@@ -12186,8 +13209,8 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux)
     return;
   }
 
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    QtDemuxStream *str = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
     switch (str->subtype) {
       case FOURCC_soun:
       case FOURCC_vide:
@@ -12255,16 +13278,17 @@ static GstFlowReturn
 qtdemux_prepare_streams (GstQTDemux * qtdemux)
 {
   GstFlowReturn ret = GST_FLOW_OK;
-  GList *iter, *next;
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  guint64 tkhd_max_duration = 0;
+#endif
+  gint i;
 
   GST_DEBUG_OBJECT (qtdemux, "prepare streams");
 
-  for (iter = qtdemux->active_streams; ret == GST_FLOW_OK && iter; iter = next) {
-    QtDemuxStream *stream = QTDEMUX_STREAM (iter->data);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     guint32 sample_num = 0;
 
-    next = iter->next;
-
     GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT,
         stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
 
@@ -12278,6 +13302,10 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
     } else {
       /* discard any stray moof */
       qtdemux->moof_offset = 0;
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+      if (tkhd_max_duration < stream->tkhd_duration)
+        tkhd_max_duration = stream->tkhd_duration;
+#endif
     }
 
     /* prepare braking */
@@ -12289,13 +13317,15 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
      * in push mode, we'll just have to deal with it */
     if (G_UNLIKELY (qtdemux->pullbased && !stream->n_samples)) {
       GST_DEBUG_OBJECT (qtdemux, "no samples for stream; discarding");
-      gst_qtdemux_remove_stream (qtdemux, stream);
+      g_ptr_array_remove_index (qtdemux->active_streams, i);
+      i--;
       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);
+      g_ptr_array_remove_index (qtdemux->active_streams, i);
+      i--;
       continue;
     }
 
@@ -12307,44 +13337,41 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
     }
   }
 
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  if (!qtdemux->fragmented && (qtdemux->duration > tkhd_max_duration)) {
+    GST_INFO_OBJECT (qtdemux,
+        "Update duration: %" G_GUINT64_FORMAT " -> %" G_GUINT64_FORMAT,
+        qtdemux->duration, tkhd_max_duration);
+    qtdemux->duration = tkhd_max_duration;
+  }
+#endif
+
   return ret;
 }
 
-static GList *
-_stream_in_list (GList * list, QtDemuxStream * stream)
+static gboolean
+_stream_equal_func (const QtDemuxStream * stream, const gchar * stream_id)
 {
-  GList *iter;
-
-  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;
-  }
-
-  return NULL;
+  return g_strcmp0 (stream->stream_id, stream_id) == 0;
 }
 
 static gboolean
 qtdemux_is_streams_update (GstQTDemux * qtdemux)
 {
-  GList *new, *old;
+  gint i;
 
-  g_return_val_if_fail (qtdemux->active_streams != NULL, FALSE);
+  /* Different length, updated */
+  if (QTDEMUX_N_STREAMS (qtdemux) != qtdemux->old_streams->len)
+    return TRUE;
 
   /* 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)) {
-
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     /* Different stream-id, updated */
-    if (g_strcmp0 (QTDEMUX_STREAM (new->data)->stream_id,
-            QTDEMUX_STREAM (old->data)->stream_id))
+    if (g_strcmp0 (QTDEMUX_NTH_STREAM (qtdemux, i)->stream_id,
+            QTDEMUX_NTH_OLD_STREAM (qtdemux, i)->stream_id))
       return TRUE;
   }
 
-  /* Different length, updated */
-  if (new != NULL || old != NULL)
-    return TRUE;
-
   return FALSE;
 }
 
@@ -12362,10 +13389,38 @@ qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux,
   return gst_qtdemux_configure_stream (qtdemux, newstream);
 }
 
+/* g_ptr_array_find_with_equal_func is available since 2.54,
+ * replacement until we can depend unconditionally on the real one in GLib */
+#if !GLIB_CHECK_VERSION(2,54,0)
+#define g_ptr_array_find_with_equal_func qtdemux_ptr_array_find_with_equal_func
+static gboolean
+qtdemux_ptr_array_find_with_equal_func (GPtrArray * haystack,
+    gconstpointer needle, GEqualFunc equal_func, guint * index_)
+{
+  guint i;
+
+  g_return_val_if_fail (haystack != NULL, FALSE);
+
+  if (equal_func == NULL)
+    equal_func = g_direct_equal;
+
+  for (i = 0; i < haystack->len; i++) {
+    if (equal_func (g_ptr_array_index (haystack, i), needle)) {
+      if (index_ != NULL)
+        *index_ = i;
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+#endif
+
 static gboolean
 qtdemux_update_streams (GstQTDemux * qtdemux)
 {
-  GList *iter, *next;
+  gint i;
+  g_assert (qtdemux->streams_aware);
 
   /* 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,
@@ -12376,32 +13431,31 @@ qtdemux_update_streams (GstQTDemux * qtdemux)
    * 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;
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+    QtDemuxStream *oldstream = NULL;
+    guint target;
 
     GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT,
         stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
 
-    qtdemux->n_streams++;
+    if (g_ptr_array_find_with_equal_func (qtdemux->old_streams,
+            stream->stream_id, (GEqualFunc) _stream_equal_func, &target)) {
+      oldstream = QTDEMUX_NTH_OLD_STREAM (qtdemux, target);
 
-    if (qtdemux->streams_aware
-        && (tmp = _stream_in_list (qtdemux->old_streams, stream)) != NULL
-        && QTDEMUX_STREAM (tmp->data)->pad) {
-      QtDemuxStream *oldstream = QTDEMUX_STREAM (tmp->data);
+      /* null pad stream cannot be reused */
+      if (oldstream->pad == NULL)
+        oldstream = NULL;
+    }
 
+    if (oldstream) {
       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);
+      /* we don't need to preserve order of old streams */
+      g_ptr_array_remove_fast (qtdemux->old_streams, oldstream);
     } else {
       GstTagList *list;
 
@@ -12420,24 +13474,21 @@ qtdemux_update_streams (GstQTDemux * qtdemux)
 static GstFlowReturn
 qtdemux_expose_streams (GstQTDemux * qtdemux)
 {
-  GList *iter, *next;
+  gint i;
 
   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)))
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *new_stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+      QtDemuxStream *old_stream = QTDEMUX_NTH_OLD_STREAM (qtdemux, i);
+      if (!qtdemux_reuse_and_configure_stream (qtdemux, old_stream, new_stream))
         return GST_FLOW_ERROR;
     }
 
-    g_list_free_full (qtdemux->old_streams,
-        (GDestroyNotify) gst_qtdemux_stream_free);
-    qtdemux->old_streams = NULL;
+    g_ptr_array_set_size (qtdemux->old_streams, 0);
+    qtdemux->need_segment = TRUE;
 
     return GST_FLOW_OK;
   }
@@ -12446,8 +13497,8 @@ qtdemux_expose_streams (GstQTDemux * qtdemux)
     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);
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
       GstTagList *list;
 
       /* now we have all info and can expose */
@@ -12464,9 +13515,8 @@ qtdemux_expose_streams (GstQTDemux * qtdemux)
   gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux));
 
   /* 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);
+  for (i = 0; i < qtdemux->old_streams->len; i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_OLD_STREAM (qtdemux, i);
 
     if (stream->pad) {
       GstEvent *event;
@@ -12477,15 +13527,14 @@ qtdemux_expose_streams (GstQTDemux * qtdemux)
 
       gst_pad_push_event (stream->pad, event);
     }
-
-    qtdemux->old_streams = g_list_remove (qtdemux->old_streams, stream);
-    gst_qtdemux_stream_free (stream);
   }
 
+  g_ptr_array_set_size (qtdemux->old_streams, 0);
+
   /* check if we should post a redirect in case there is a single trak
    * and it is a redirecting trak */
-  if (qtdemux->n_streams == 1 &&
-      QTDEMUX_FIRST_STREAM (qtdemux)->redirect_uri != NULL) {
+  if (QTDEMUX_N_STREAMS (qtdemux) == 1 &&
+      QTDEMUX_NTH_STREAM (qtdemux, 0)->redirect_uri != NULL) {
     GstMessage *m;
 
     GST_INFO_OBJECT (qtdemux, "Issuing a redirect due to a single track with "
@@ -12493,14 +13542,15 @@ qtdemux_expose_streams (GstQTDemux * qtdemux)
     m = gst_message_new_element (GST_OBJECT_CAST (qtdemux),
         gst_structure_new ("redirect",
             "new-location", G_TYPE_STRING,
-            QTDEMUX_FIRST_STREAM (qtdemux)->redirect_uri, NULL));
+            QTDEMUX_NTH_STREAM (qtdemux, 0)->redirect_uri, NULL));
     gst_element_post_message (GST_ELEMENT_CAST (qtdemux), m);
     qtdemux->posted_redirect = TRUE;
   }
 
-  for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) {
-    qtdemux_do_allocation (qtdemux, QTDEMUX_STREAM (iter->data));
-  }
+  g_ptr_array_foreach (qtdemux->active_streams,
+      (GFunc) qtdemux_do_allocation, qtdemux);
+
+  qtdemux->need_segment = TRUE;
 
   qtdemux->exposed = TRUE;
   return GST_FLOW_OK;
@@ -13690,7 +14740,6 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
   GNode *trak;
   GNode *udta;
   GNode *mvex;
-  GstClockTime duration;
   GNode *pssh;
   guint64 creation_time;
   GstDateTime *datetime = NULL;
@@ -13770,9 +14819,13 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
       qtdemux_parse_mehd (qtdemux, &mehd_data);
   }
 
-  /* set duration in the segment info */
-  gst_qtdemux_get_duration (qtdemux, &duration);
-  if (duration) {
+  /* Update the movie segment duration, unless it was directly given to us
+   * by upstream. Otherwise let it as is, as we don't want to mangle the
+   * duration provided by upstream that may come e.g. from a MPD file. */
+  if (!qtdemux->upstream_format_is_time) {
+    GstClockTime duration;
+    /* set duration in the segment info */
+    gst_qtdemux_get_duration (qtdemux, &duration);
     qtdemux->segment.duration = duration;
     /* also do not exceed duration; stop is set that way post seek anyway,
      * and segment activation falls back to duration,
@@ -14673,6 +15726,10 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       caps = gst_caps_new_simple ("video/x-wmv",
           "wmvversion", G_TYPE_INT, 3, "format", G_TYPE_STRING, "WVC1", NULL);
       break;
+    case FOURCC_av01:
+      _codec ("AV1");
+      caps = gst_caps_new_empty_simple ("video/x-av1");
+      break;
     case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'):
     default:
     {
@@ -15071,8 +16128,9 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       _codec ("CEA 608 Closed Caption");
       caps =
           gst_caps_new_simple ("closedcaption/x-cea-608", "format",
-          G_TYPE_STRING, "cc_data", NULL);
+          G_TYPE_STRING, "s334-1a", NULL);
       stream->need_process = TRUE;
+      stream->need_split = TRUE;
       break;
     case FOURCC_c708:
       _codec ("CEA 708 Closed Caption");