qtdemux: Fix guint vs gsize type confusion
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / gst / isomp4 / qtdemux.c
index a1a2b91..b20c255 100644 (file)
@@ -50,7 +50,7 @@
 #include "config.h"
 #endif
 
-#include "gst/gst-i18n-plugin.h"
+#include <glib/gi18n-lib.h>
 
 #include <glib/gprintf.h>
 #include <gst/base/base.h>
@@ -70,6 +70,7 @@
 #include "qtpalette.h"
 #include "qtdemux_tags.h"
 #include "qtdemux_tree.h"
+#include "qtdemux-webvtt.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -288,6 +289,12 @@ GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
     GST_PAD_SOMETIMES,
     GST_STATIC_CAPS_ANY);
 
+static GstStaticPadTemplate gst_qtdemux_metasrc_template =
+GST_STATIC_PAD_TEMPLATE ("meta_%u",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+
 #define gst_qtdemux_parent_class parent_class
 G_DEFINE_TYPE (GstQTDemux, gst_qtdemux, GST_TYPE_ELEMENT);
 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qtdemux, "qtdemux",
@@ -350,6 +357,9 @@ static GstCaps *qtdemux_audio_caps (GstQTDemux * qtdemux,
 static GstCaps *qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
     QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * data,
     gchar ** codec_name);
+static GstCaps *qtdemux_meta_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * data,
+    gchar ** codec_name);
 static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux,
     QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc,
     const guint8 * stsd_entry_data, gchar ** codec_name);
@@ -382,6 +392,8 @@ static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
     const gchar * id);
 static void qtdemux_gst_structure_free (GstStructure * gststructure);
 static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard);
+static void qtdemux_clear_protection_events_on_all_streams (GstQTDemux *
+    qtdemux);
 
 static void
 gst_qtdemux_class_init (GstQTDemuxClass * klass)
@@ -472,9 +484,8 @@ gst_qtdemux_dispose (GObject * object)
   }
   gst_tag_list_unref (qtdemux->tag_list);
   gst_flow_combiner_free (qtdemux->flowcombiner);
-  g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
-      NULL);
-  g_queue_clear (&qtdemux->protection_event_queue);
+  g_queue_clear_full (&qtdemux->protection_event_queue,
+      (GDestroyNotify) gst_event_unref);
 
   g_free (qtdemux->cenc_aux_info_sizes);
   qtdemux->cenc_aux_info_sizes = NULL;
@@ -1849,7 +1860,7 @@ _create_stream (GstQTDemux * demux, guint32 track_id)
   stream->discont = TRUE;
   /* we enable clipping for raw audio/video streams */
   stream->need_clip = FALSE;
-  stream->need_process = FALSE;
+  stream->process_func = NULL;
   stream->segment_index = -1;
   stream->time_position = 0;
   stream->sample_index = -1;
@@ -1975,7 +1986,6 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
   gint i;
 
   GST_DEBUG_OBJECT (qtdemux, "Resetting demux");
-  gst_pad_stop_task (qtdemux->sinkpad);
 
   if (hard || qtdemux->upstream_format_is_time) {
     qtdemux->state = QTDEMUX_STATE_INITIAL;
@@ -2030,9 +2040,8 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     qtdemux->have_group_id = FALSE;
     qtdemux->group_id = G_MAXUINT;
 
-    g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
-        NULL);
-    g_queue_clear (&qtdemux->protection_event_queue);
+    g_queue_clear_full (&qtdemux->protection_event_queue,
+        (GDestroyNotify) gst_event_unref);
 
     qtdemux->received_seek = FALSE;
     qtdemux->first_moof_already_parsed = FALSE;
@@ -2050,13 +2059,16 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     qtdemux->n_video_streams = 0;
     qtdemux->n_audio_streams = 0;
     qtdemux->n_sub_streams = 0;
+    qtdemux->n_meta_streams = 0;
     qtdemux->exposed = FALSE;
     qtdemux->fragmented = FALSE;
     qtdemux->mss_mode = FALSE;
     gst_caps_replace (&qtdemux->media_caps, NULL);
     qtdemux->timescale = 0;
     qtdemux->got_moov = FALSE;
+    qtdemux->start_utc_time = GST_CLOCK_TIME_NONE;
     qtdemux->cenc_aux_info_offset = 0;
+    g_free (qtdemux->cenc_aux_info_sizes);
     qtdemux->cenc_aux_info_sizes = NULL;
     qtdemux->cenc_aux_sample_count = 0;
     if (qtdemux->protection_system_ids) {
@@ -2087,6 +2099,15 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
   }
 }
 
+static void
+qtdemux_clear_protection_events_on_all_streams (GstQTDemux * qtdemux)
+{
+  for (unsigned i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+    g_queue_clear_full (&stream->protection_scheme_event_queue,
+        (GDestroyNotify) gst_event_unref);
+  }
+}
 
 /* Maps the @segment to the qt edts internal segments and pushes
  * the corresponding segment event.
@@ -2562,9 +2583,8 @@ gst_qtdemux_stream_clear (QtDemuxStream * stream)
   }
   stream->protection_scheme_type = 0;
   stream->protection_scheme_version = 0;
-  g_queue_foreach (&stream->protection_scheme_event_queue,
-      (GFunc) gst_event_unref, NULL);
-  g_queue_clear (&stream->protection_scheme_event_queue);
+  g_queue_clear_full (&stream->protection_scheme_event_queue,
+      (GDestroyNotify) gst_event_unref);
   gst_qtdemux_stream_flush_segments_data (stream);
   gst_qtdemux_stream_flush_samples_data (stream);
 }
@@ -2671,14 +2691,56 @@ qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
   /* only consider at least a sufficiently complete ftyp atom */
   if (length >= 20) {
     GstBuffer *buf;
+    guint32 minor_version;
+    const guint8 *p;
 
     qtdemux->major_brand = QT_FOURCC (buffer + 8);
-    GST_DEBUG_OBJECT (qtdemux, "major brand: %" GST_FOURCC_FORMAT,
+    GST_DEBUG_OBJECT (qtdemux, "ftyp major brand: %" GST_FOURCC_FORMAT,
         GST_FOURCC_ARGS (qtdemux->major_brand));
+    minor_version = QT_UINT32 (buffer + 12);
+    GST_DEBUG_OBJECT (qtdemux, "ftyp minor version: %u", minor_version);
     if (qtdemux->comp_brands)
       gst_buffer_unref (qtdemux->comp_brands);
     buf = qtdemux->comp_brands = gst_buffer_new_and_alloc (length - 16);
     gst_buffer_fill (buf, 0, buffer + 16, length - 16);
+
+    p = buffer + 16;
+    length = length - 16;
+    while (length > 0) {
+      GST_DEBUG_OBJECT (qtdemux, "ftyp compatible brand: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (QT_FOURCC (p)));
+      length -= 4;
+      p += 4;
+    }
+  }
+}
+
+static void
+qtdemux_parse_styp (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
+{
+  /* only consider at least a sufficiently complete styp atom */
+  if (length >= 20) {
+    GstBuffer *buf;
+    guint32 major_brand;
+    guint32 minor_version;
+    const guint8 *p;
+
+    major_brand = QT_FOURCC (buffer + 8);
+    GST_DEBUG_OBJECT (qtdemux, "styp major brand: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (major_brand));
+    minor_version = QT_UINT32 (buffer + 12);
+    GST_DEBUG_OBJECT (qtdemux, "styp minor version: %u", minor_version);
+    buf = qtdemux->comp_brands = gst_buffer_new_and_alloc (length - 16);
+    gst_buffer_fill (buf, 0, buffer + 16, length - 16);
+
+    p = buffer + 16;
+    length = length - 16;
+    while (length > 0) {
+      GST_DEBUG_OBJECT (qtdemux, "styp compatible brand: %" GST_FOURCC_FORMAT,
+          GST_FOURCC_ARGS (QT_FOURCC (p)));
+      length -= 4;
+      p += 4;
+    }
   }
 }
 
@@ -2789,8 +2851,13 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     return;
   }
 
-  gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD,
-      G_TYPE_STRING, &system_id, NULL);
+  if (!gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD,
+          G_TYPE_STRING, &system_id, NULL)) {
+    GST_WARNING_OBJECT (qtdemux, "%s field not present in caps",
+        GST_PROTECTION_SYSTEM_ID_CAPS_FIELD);
+    return;
+  }
+
   gst_qtdemux_append_protection_system_id (qtdemux, system_id);
 
   stream->protected = TRUE;
@@ -3001,9 +3068,60 @@ qtdemux_parse_sidx (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
   gst_isoff_qt_sidx_parser_clear (&sidx_parser);
 }
 
+static void
+qtdemux_parse_cstb (GstQTDemux * qtdemux, GstByteReader * data)
+{
+  guint64 start_time;
+  guint32 entry_count;
+
+  GST_DEBUG_OBJECT (qtdemux, "Parsing CorrectStartTime box");
+
+  qtdemux->start_utc_time = GST_CLOCK_TIME_NONE;
+
+  if (gst_byte_reader_get_remaining (data) < 4) {
+    GST_WARNING_OBJECT (qtdemux, "Too small CorrectStartTime box");
+    return;
+  }
+
+  entry_count = gst_byte_reader_get_uint32_be_unchecked (data);
+  if (entry_count == 0)
+    return;
+
+  /* XXX: We assume that all start times are the same as different start times
+   * would violate the MP4 synchronization model, so we just take the first
+   * one here and apply it to all tracks.
+   */
+
+  if (gst_byte_reader_get_remaining (data) < entry_count * 12) {
+    GST_WARNING_OBJECT (qtdemux, "Too small CorrectStartTime box");
+    return;
+  }
+
+  /* Skip track id */
+  gst_byte_reader_skip_unchecked (data, 4);
+
+  /* In 100ns intervals */
+  start_time = gst_byte_reader_get_uint64_be_unchecked (data);
+
+  /* Convert from Jan 1 1601 to Jan 1 1970 */
+  if (start_time < 11644473600 * G_GUINT64_CONSTANT (10000000)) {
+    GST_WARNING_OBJECT (qtdemux, "Start UTC time before UNIX epoch");
+    return;
+  }
+  start_time -= 11644473600 * G_GUINT64_CONSTANT (10000000);
+
+  /* Convert to GstClockTime */
+  start_time *= 100;
+
+  GST_DEBUG_OBJECT (qtdemux, "Start UTC time: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (start_time));
+
+  qtdemux->start_utc_time = start_time;
+}
+
 /* caller verifies at least 8 bytes in buf */
 static void
-extract_initial_length_and_fourcc (const guint8 * data, guint size,
+extract_initial_length_and_fourcc (const guint8 * data, gsize size,
     guint64 * plength, guint32 * pfourcc)
 {
   guint64 length;
@@ -3209,6 +3327,7 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
   GstClockTime gst_ts = GST_CLOCK_TIME_NONE;
   guint64 timestamp;
   gint32 data_offset = 0;
+  guint8 version;
   guint32 flags = 0, first_flags = 0, samples_count = 0;
   gint i;
   guint8 *data;
@@ -3216,6 +3335,7 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
   QtDemuxSample *sample;
   gboolean ismv = FALSE;
   gint64 initial_offset;
+  gint32 min_ct = 0;
 
   GST_LOG_OBJECT (qtdemux, "parsing trun track-id %d; "
       "default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT ", "
@@ -3235,7 +3355,7 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
     stream->all_keyframe = TRUE;
   }
 
-  if (!gst_byte_reader_skip (trun, 1) ||
+  if (!gst_byte_reader_get_uint8 (trun, &version) ||
       !gst_byte_reader_get_uint24_be (trun, &flags))
     goto fail;
 
@@ -3357,33 +3477,22 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
       GST_INFO_OBJECT (stream->pad, "first sample ts %" GST_TIME_FORMAT,
           GST_TIME_ARGS (gst_ts));
     } else {
-      /* subsequent fragments extend stream */
-      timestamp =
-          stream->samples[stream->n_samples - 1].timestamp +
-          stream->samples[stream->n_samples - 1].duration;
-
-      /* If this is a GST_FORMAT_BYTES stream and there's a significant
-       * difference (1 sec.) between decode_ts and timestamp, prefer the
-       * former */
-      if (has_tfdt && !qtdemux->upstream_format_is_time
-          && ABSDIFF (decode_ts, timestamp) >
-          MAX (stream->duration_last_moof / 2,
-              GSTTIME_TO_QTSTREAMTIME (stream, GST_SECOND))) {
-        GST_INFO_OBJECT (qtdemux,
-            "decode_ts (%" GST_TIME_FORMAT ") and timestamp (%" GST_TIME_FORMAT
-            ") are significantly different (more than %" GST_TIME_FORMAT
-            "), using decode_ts",
-            GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, decode_ts)),
-            GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, timestamp)),
-            GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream,
-                    MAX (stream->duration_last_moof / 2,
-                        GSTTIME_TO_QTSTREAMTIME (stream, GST_SECOND)))));
+      /* If this is a GST_FORMAT_BYTES stream and we have a tfdt then use it
+       * instead of the sum of sample durations */
+      if (has_tfdt && !qtdemux->upstream_format_is_time) {
         timestamp = decode_ts;
+        gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp);
+        GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT
+            " (using tfdt)", GST_TIME_ARGS (gst_ts));
+      } else {
+        /* subsequent fragments extend stream */
+        timestamp =
+            stream->samples[stream->n_samples - 1].timestamp +
+            stream->samples[stream->n_samples - 1].duration;
+        gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp);
+        GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT
+            " (extends previous samples)", GST_TIME_ARGS (gst_ts));
       }
-
-      gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp);
-      GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT
-          " (extends previous samples)", GST_TIME_ARGS (gst_ts));
     }
   }
 
@@ -3391,7 +3500,8 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
 
   sample = stream->samples + stream->n_samples;
   for (i = 0; i < samples_count; i++) {
-    guint32 dur, size, sflags, ct;
+    guint32 dur, size, sflags;
+    gint32 ct;
 
     /* first read sample data */
     if (flags & TR_SAMPLE_DURATION) {
@@ -3415,8 +3525,17 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
     } else {
       sflags = d_sample_flags;
     }
+
     if (flags & TR_COMPOSITION_TIME_OFFSETS) {
+      /* Read offsets as signed numbers regardless of trun version as very
+       * high offsets are unlikely and there are files out there that use
+       * version=0 truns with negative offsets */
       ct = QT_UINT32 (data + ct_offset);
+
+      /* FIXME: Set offset to 0 for "no decode samples". This needs
+       * to be handled in a codec specific manner ideally. */
+      if (ct == G_MININT32)
+        ct = 0;
     } else {
       ct = 0;
     }
@@ -3436,8 +3555,23 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
     timestamp += dur;
     stream->duration_moof += dur;
     sample++;
+
+    if (ct < min_ct)
+      min_ct = ct;
   }
 
+  /* Shift PTS/DTS to allow for negative composition offsets while keeping
+   * A/V sync in place. This is similar to the code handling ctts/cslg in the
+   * non-fragmented case.
+   */
+  if (min_ct < 0)
+    stream->cslg_shift = -min_ct;
+  else
+    stream->cslg_shift = 0;
+
+  GST_DEBUG_OBJECT (qtdemux, "Using clsg_shift %" G_GUINT64_FORMAT,
+      stream->cslg_shift);
+
   /* Update total duration if needed */
   check_update_duration (qtdemux, QTSTREAMTIME_TO_GSTTIME (stream, timestamp));
 
@@ -3547,11 +3681,13 @@ qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd,
       goto invalid_track;
 
   /* obtain stream defaults */
-  qtdemux_parse_trex (qtdemux, *stream,
-      default_sample_duration, default_sample_size, default_sample_flags);
+  if (qtdemux_parse_trex (qtdemux, *stream,
+          default_sample_duration, default_sample_size, default_sample_flags)) {
 
-  (*stream)->stsd_sample_description_id =
-      (*stream)->def_sample_description_index - 1;
+    /* Default sample description index is only valid if trex parsing succeeded */
+    (*stream)->stsd_sample_description_id =
+        (*stream)->def_sample_description_index - 1;
+  }
 
   if (flags & TF_SAMPLE_DESCRIPTION_INDEX) {
     guint32 sample_description_index;
@@ -4126,6 +4262,8 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
       /* iterate all siblings */
       trun_node = qtdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun,
           &trun_data);
+      /* don't use tfdt for subsequent trun as it only refers to the first */
+      tfdt_node = NULL;
     }
 
     uuid_node = qtdemux_tree_get_child_by_type (traf_node, FOURCC_uuid);
@@ -4151,6 +4289,10 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
 
   /* parse any protection system info */
   pssh_node = qtdemux_tree_get_child_by_type (moof_node, FOURCC_pssh);
+  if (pssh_node) {
+    /* Unref old protection events if we are going to receive new ones. */
+    qtdemux_clear_protection_events_on_all_streams (qtdemux);
+  }
   while (pssh_node) {
     GST_LOG_OBJECT (qtdemux, "Parsing pssh box.");
     qtdemux_parse_pssh (qtdemux, pssh_node);
@@ -4534,8 +4676,8 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
         GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
             (_("This file is incomplete and cannot be played.")),
             ("We got less than expected (received %" G_GSIZE_FORMAT
-                ", wanted %u, offset %" G_GUINT64_FORMAT ")", map.size,
-                (guint) length, cur_offset));
+                ", wanted %" G_GUINT64_FORMAT ", offset %" G_GUINT64_FORMAT ")",
+                map.size, length, cur_offset));
         gst_buffer_unmap (moov, &map);
         gst_buffer_unref (moov);
         ret = GST_FLOW_ERROR;
@@ -4575,6 +4717,20 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
       gst_buffer_unref (ftyp);
       break;
     }
+    case FOURCC_styp:
+    {
+      GstBuffer *styp = NULL;
+
+      ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &styp);
+      if (ret != GST_FLOW_OK)
+        goto beach;
+      qtdemux->offset += length;
+      gst_buffer_map (styp, &map, GST_MAP_READ);
+      qtdemux_parse_styp (qtdemux, map.data, map.size);
+      gst_buffer_unmap (styp, &map);
+      gst_buffer_unref (styp);
+      break;
+    }
     case FOURCC_uuid:
     {
       GstBuffer *uuid = NULL;
@@ -4603,6 +4759,34 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
       gst_buffer_unref (sidx);
       break;
     }
+    case FOURCC_meta:
+    {
+      GstBuffer *meta = NULL;
+      GNode *node, *child;
+      GstByteReader child_data;
+      ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &meta);
+      if (ret != GST_FLOW_OK)
+        goto beach;
+      qtdemux->offset += length;
+      gst_buffer_map (meta, &map, GST_MAP_READ);
+
+      node = g_node_new (map.data);
+
+      qtdemux_parse_node (qtdemux, node, map.data, map.size);
+
+      /* Parse ONVIF Export File Format CorrectStartTime box if available */
+      if ((child =
+              qtdemux_tree_get_child_by_type_full (node, FOURCC_cstb,
+                  &child_data))) {
+        qtdemux_parse_cstb (qtdemux, &child_data);
+      }
+
+      g_node_destroy (node);
+
+      gst_buffer_unmap (meta, &map);
+      gst_buffer_unref (meta);
+      break;
+    }
     default:
     {
       GstBuffer *unknown = NULL;
@@ -4939,8 +5123,11 @@ gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
   stream->segment.rate = rate;
   stream->segment.start = start + QTSTREAMTIME_TO_GSTTIME (stream,
       stream->cslg_shift);
-  stream->segment.stop = stop + QTSTREAMTIME_TO_GSTTIME (stream,
-      stream->cslg_shift);
+  if (stop != -1)
+    stream->segment.stop = stop + QTSTREAMTIME_TO_GSTTIME (stream,
+        stream->cslg_shift);
+  else
+    stream->segment.stop = stop;
   stream->segment.time = time;
   stream->segment.position = stream->segment.start;
 
@@ -5669,59 +5856,89 @@ invalid_cdat:
   return NULL;
 }
 
-/* the input buffer metadata must be writable,
+/* Handle Closed Caption sample buffers.
+ * The input buffer metadata must be writable,
  * but time/duration etc not yet set and need not be preserved */
 static GstBuffer *
-gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+gst_qtdemux_process_buffer_clcp (GstQTDemux * qtdemux, QtDemuxStream * stream,
     GstBuffer * buf)
 {
+  GstBuffer *outbuf = NULL;
   GstMapInfo map;
-  guint nsize = 0;
-  gchar *str;
+  guint8 *cc;
+  gsize cclen = 0;
 
-  /* not many cases for now */
-  if (G_UNLIKELY (CUR_STREAM (stream)->fourcc == FOURCC_mp4s)) {
-    /* send a one time dvd clut event */
-    if (stream->pending_event && stream->pad)
-      gst_pad_push_event (stream->pad, stream->pending_event);
-    stream->pending_event = NULL;
+  gst_buffer_map (buf, &map, GST_MAP_READ);
+
+  /* empty buffer is sent to terminate previous subtitle */
+  if (map.size <= 2) {
+    gst_buffer_unmap (buf, &map);
+    gst_buffer_unref (buf);
+    return NULL;
   }
 
-  if (G_UNLIKELY (stream->subtype != FOURCC_text
-          && stream->subtype != FOURCC_sbtl &&
-          stream->subtype != FOURCC_subp && stream->subtype != FOURCC_clcp)) {
-    return buf;
+  /* For closed caption, we need to extract the information from the
+   * [cdat],[cdt2] or [ccdp] atom */
+  cc = extract_cc_from_data (stream, map.data, map.size, &cclen);
+  gst_buffer_unmap (buf, &map);
+  if (cc) {
+    outbuf = _gst_buffer_new_wrapped (cc, cclen, g_free);
+    gst_buffer_copy_into (outbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1);
+  } else {
+    /* Conversion failed or there's nothing */
   }
+  gst_buffer_unref (buf);
 
-  gst_buffer_map (buf, &map, GST_MAP_READ);
+  return outbuf;
+}
+
+/* DVD subpicture specific sample handling.
+ * the input buffer metadata must be writable,
+ * but time/duration etc not yet set and need not be preserved */
+static GstBuffer *
+gst_qtdemux_process_buffer_dvd (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  /* send a one time dvd clut event */
+  if (stream->pending_event && stream->pad)
+    gst_pad_push_event (stream->pad, stream->pending_event);
+  stream->pending_event = NULL;
 
   /* empty buffer is sent to terminate previous subtitle */
-  if (map.size <= 2) {
-    gst_buffer_unmap (buf, &map);
+  if (gst_buffer_get_size (buf) <= 2) {
     gst_buffer_unref (buf);
     return NULL;
   }
-  if (stream->subtype == FOURCC_subp) {
-    /* That's all the processing needed for subpictures */
-    gst_buffer_unmap (buf, &map);
+
+  /* That's all the processing needed for subpictures */
+  return buf;
+}
+
+/* Timed text formats
+ * the input buffer metadata must be writable,
+ * but time/duration etc not yet set and need not be preserved */
+static GstBuffer *
+gst_qtdemux_process_buffer_text (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstBuffer *outbuf = NULL;
+  GstMapInfo map;
+  guint nsize = 0;
+  gchar *str;
+
+  /* not many cases for now */
+  if (G_UNLIKELY (stream->subtype != FOURCC_text &&
+          stream->subtype != FOURCC_sbtl)) {
     return buf;
   }
 
-  if (stream->subtype == FOURCC_clcp) {
-    guint8 *cc;
-    gsize cclen = 0;
-    /* For closed caption, we need to extract the information from the
-     * [cdat],[cdt2] or [ccdp] atom */
-    cc = extract_cc_from_data (stream, map.data, map.size, &cclen);
+  gst_buffer_map (buf, &map, GST_MAP_READ);
+
+  /* empty buffer is sent to terminate previous subtitle */
+  if (map.size <= 2) {
     gst_buffer_unmap (buf, &map);
     gst_buffer_unref (buf);
-    if (cc) {
-      buf = _gst_buffer_new_wrapped (cc, cclen, g_free);
-    } else {
-      /* Conversion failed or there's nothing */
-      buf = NULL;
-    }
-    return buf;
+    return NULL;
   }
 
   nsize = GST_READ_UINT16_BE (map.data);
@@ -5734,18 +5951,53 @@ gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
    * no other encoding expected */
   str = gst_tag_freeform_string_to_utf8 ((gchar *) map.data + 2, nsize, NULL);
   gst_buffer_unmap (buf, &map);
+
   if (str) {
-    gst_buffer_unref (buf);
-    buf = _gst_buffer_new_wrapped (str, strlen (str), g_free);
+    outbuf = _gst_buffer_new_wrapped (str, strlen (str), g_free);
+    gst_buffer_copy_into (outbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1);
   } else {
     /* this should not really happen unless the subtitle is corrupted */
-    gst_buffer_unref (buf);
-    buf = NULL;
   }
+  gst_buffer_unref (buf);
 
   /* FIXME ? convert optional subsequent style info to markup */
 
-  return buf;
+  return outbuf;
+}
+
+/* WebVTT sample handling according to 14496-30 */
+static GstBuffer *
+gst_qtdemux_process_buffer_wvtt (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstBuffer *outbuf = NULL;
+  GstMapInfo map;
+
+  if (!gst_buffer_map (buf, &map, GST_MAP_READ)) {
+    g_assert_not_reached ();    /* The buffer must be mappable */
+  }
+
+  if (qtdemux_webvtt_is_empty (qtdemux, map.data, map.size)) {
+    GstEvent *gap = NULL;
+    /* Push a gap event */
+    stream->segment.position = GST_BUFFER_PTS (buf);
+    gap =
+        gst_event_new_gap (stream->segment.position, GST_BUFFER_DURATION (buf));
+    gst_pad_push_event (stream->pad, gap);
+
+    if (GST_BUFFER_DURATION_IS_VALID (buf))
+      stream->segment.position += GST_BUFFER_DURATION (buf);
+  } else {
+    outbuf =
+        qtdemux_webvtt_decode (qtdemux, GST_BUFFER_PTS (buf),
+        GST_BUFFER_DURATION (buf), map.data, map.size);
+    gst_buffer_copy_into (outbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1);
+  }
+
+  gst_buffer_unmap (buf, &map);
+  gst_buffer_unref (buf);
+
+  return outbuf;
 }
 
 static GstFlowReturn
@@ -6044,11 +6296,13 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
   /* we're going to modify the metadata */
   buf = gst_buffer_make_writable (buf);
 
-  if (G_UNLIKELY (stream->need_process))
-    buf = gst_qtdemux_process_buffer (qtdemux, stream, buf);
-
-  if (!buf) {
-    goto exit;
+  if (qtdemux->start_utc_time != GST_CLOCK_TIME_NONE) {
+    static GstStaticCaps unix_caps = GST_STATIC_CAPS ("timestamp/x-unix");
+    GstCaps *caps = gst_static_caps_get (&unix_caps);
+    gst_buffer_add_reference_timestamp_meta (buf, caps,
+        pts + qtdemux->start_utc_time - stream->cslg_shift,
+        GST_CLOCK_TIME_NONE);
+    gst_caps_unref (caps);
   }
 
   GST_BUFFER_DTS (buf) = dts;
@@ -6057,6 +6311,13 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
   GST_BUFFER_OFFSET (buf) = -1;
   GST_BUFFER_OFFSET_END (buf) = -1;
 
+  if (G_UNLIKELY (stream->process_func))
+    buf = stream->process_func (qtdemux, stream, buf);
+
+  if (!buf) {
+    goto exit;
+  }
+
   if (!keyframe) {
     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
     stream->on_keyframe = FALSE;
@@ -6277,37 +6538,63 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
     goto eos_stream;
   }
 
-  /* gap events for subtitle streams */
-  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+  /* fetch info for the current sample of this stream */
+  if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, target_stream,
+              &empty, &offset, &sample_size, &dts, &pts, &duration, &keyframe)))
+    goto eos_stream;
+
+  /* Send catche-up GAP event for each other stream if required.
+   * This logic will be applied only for positive rate */
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux) &&
+      qtdemux->segment.rate >= 0; i++) {
     stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+    if (stream == target_stream ||
+        !GST_CLOCK_TIME_IS_VALID (stream->segment.stop) ||
+        !GST_CLOCK_TIME_IS_VALID (stream->segment.position))
+      continue;
+
     if (stream->pad) {
       GstClockTime gap_threshold;
+      /* kind of running time with offset segment.base and segment.start */
+      GstClockTime pseudo_target_time = target_stream->segment.base;
+      GstClockTime pseudo_cur_time = stream->segment.base;
+
+      /* make sure positive offset, segment.position can be smallr than
+       * segment.start for some reasons */
+      if (target_stream->segment.position >= target_stream->segment.start) {
+        pseudo_target_time +=
+            (target_stream->segment.position - target_stream->segment.start);
+      }
+
+      if (stream->segment.position >= stream->segment.start)
+        pseudo_cur_time += (stream->segment.position - stream->segment.start);
 
       /* Only send gap events on non-subtitle streams if lagging way behind. */
       if (stream->subtype == FOURCC_subp
-          || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl)
+          || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl ||
+          stream->subtype == FOURCC_wvtt)
         gap_threshold = 1 * GST_SECOND;
       else
         gap_threshold = 3 * GST_SECOND;
 
       /* send gap events until the stream catches up */
       /* gaps can only be sent after segment is activated (segment.stop is no longer -1) */
-      while (GST_CLOCK_TIME_IS_VALID (stream->segment.stop) &&
-          GST_CLOCK_TIME_IS_VALID (stream->segment.position) &&
-          stream->segment.position + gap_threshold < min_time) {
+      while (GST_CLOCK_TIME_IS_VALID (stream->segment.position) &&
+          pseudo_cur_time < (G_MAXUINT64 - gap_threshold) &&
+          pseudo_cur_time + gap_threshold < pseudo_target_time) {
         GstEvent *gap =
             gst_event_new_gap (stream->segment.position, gap_threshold);
+        GST_LOG_OBJECT (stream->pad, "Sending %" GST_PTR_FORMAT, gap);
+
         gst_pad_push_event (stream->pad, gap);
         stream->segment.position += gap_threshold;
+        pseudo_cur_time += gap_threshold;
       }
     }
   }
 
   stream = target_stream;
-  /* fetch info for the current sample of this stream */
-  if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, stream, &empty,
-              &offset, &sample_size, &dts, &pts, &duration, &keyframe)))
-    goto eos_stream;
 
   gst_qtdemux_stream_check_and_change_stsd_index (qtdemux, stream);
   if (stream->new_caps) {
@@ -7162,6 +7449,7 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
               if (demux->moov_node)
                 g_node_destroy (demux->moov_node);
               demux->moov_node = NULL;
+              demux->start_utc_time = GST_CLOCK_TIME_NONE;
             }
 
             demux->last_moov_offset = demux->offset;
@@ -7284,6 +7572,21 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         } else if (fourcc == FOURCC_sidx) {
           GST_DEBUG_OBJECT (demux, "Parsing [sidx]");
           qtdemux_parse_sidx (demux, data, demux->neededbytes);
+        } else if (fourcc == FOURCC_meta) {
+          GNode *node, *child;
+          GstByteReader child_data;
+
+          node = g_node_new ((gpointer) data);
+          qtdemux_parse_node (demux, node, data, demux->neededbytes);
+
+          /* Parse ONVIF Export File Format CorrectStartTime box if available */
+          if ((child =
+                  qtdemux_tree_get_child_by_type_full (node, FOURCC_cstb,
+                      &child_data))) {
+            qtdemux_parse_cstb (demux, &child_data);
+          }
+
+          g_node_destroy (node);
         } else {
           switch (fourcc) {
             case FOURCC_styp:
@@ -7716,10 +8019,16 @@ qtdemux_inflate (void *z_buffer, guint z_length, guint * length)
       break;
     }
 
+    if (*length > G_MAXUINT - 4096 || *length > QTDEMUX_MAX_SAMPLE_INDEX_SIZE) {
+      GST_WARNING ("too big decompressed data");
+      ret = Z_MEM_ERROR;
+      break;
+    }
+
     *length += 4096;
     buffer = (guint8 *) g_realloc (buffer, *length);
     z.next_out = (Bytef *) (buffer + z.total_out);
-    z.avail_out += 4096;
+    z.avail_out += *length - z.total_out;
   } while (z.avail_in > 0);
 
   if (ret != Z_STREAM_END) {
@@ -8314,10 +8623,10 @@ gst_qtdemux_request_protection_context (GstQTDemux * qtdemux,
 
   g_value_init (&event_list, GST_TYPE_LIST);
   for (; walk; walk = g_list_previous (walk)) {
-    GValue *event_value = g_new0 (GValue, 1);
-    g_value_init (event_value, GST_TYPE_EVENT);
-    g_value_set_boxed (event_value, walk->data);
-    gst_value_list_append_and_take_value (&event_list, event_value);
+    GValue event_value = G_VALUE_INIT;
+    g_value_init (&event_value, GST_TYPE_EVENT);
+    g_value_set_boxed (&event_value, walk->data);
+    gst_value_list_append_and_take_value (&event_list, &event_value);
   }
 
   /*  2a) Query downstream with GST_QUERY_CONTEXT for the context and
@@ -8693,6 +9002,7 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
   }
 
   if (stream->pad) {
+    gboolean forward_collection = FALSE;
     GstCaps *prev_caps = NULL;
 
     GST_PAD_ELEMENT_PRIVATE (stream->pad) = stream;
@@ -8710,8 +9020,6 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
       }
     }
 
-    GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT,
-        CUR_STREAM (stream)->caps);
     if (stream->new_stream) {
       GstEvent *event;
       GstStreamFlags stream_flags = GST_STREAM_FLAG_NONE;
@@ -8744,6 +9052,8 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
       }
       gst_event_set_stream_flags (event, stream_flags);
       gst_pad_push_event (stream->pad, event);
+
+      forward_collection = TRUE;
     }
 
     prev_caps = gst_pad_get_current_caps (stream->pad);
@@ -8764,6 +9074,14 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
     if (prev_caps)
       gst_caps_unref (prev_caps);
     stream->new_caps = FALSE;
+
+    if (forward_collection) {
+      /* Forward upstream collection and selection if any */
+      GstEvent *upstream_event = gst_pad_get_sticky_event (qtdemux->sinkpad,
+          GST_EVENT_STREAM_COLLECTION, 0);
+      if (upstream_event)
+        gst_pad_push_event (stream->pad, upstream_event);
+    }
   }
   return TRUE;
 }
@@ -8827,7 +9145,7 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
     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_clcp) {
+      || stream->subtype == FOURCC_clcp || stream->subtype == FOURCC_wvtt) {
     gchar *name = g_strdup_printf ("subtitle_%u", qtdemux->n_sub_streams);
 
     stream->pad =
@@ -8840,6 +9158,19 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
       goto done;
     }
     qtdemux->n_sub_streams++;
+  } else if (stream->subtype == FOURCC_meta) {
+    gchar *name = g_strdup_printf ("meta_%u", qtdemux->n_meta_streams);
+
+    stream->pad =
+        gst_pad_new_from_static_template (&gst_qtdemux_metasrc_template, name);
+    g_free (name);
+    if (!gst_qtdemux_configure_stream (qtdemux, stream)) {
+      gst_object_unref (stream->pad);
+      stream->pad = NULL;
+      ret = FALSE;
+      goto done;
+    }
+    qtdemux->n_meta_streams++;
   } else if (CUR_STREAM (stream)->caps) {
     gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams);
 
@@ -9301,12 +9632,18 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl)
           ! !qtdemux_tree_get_child_by_type_full (stbl, FOURCC_ctts,
               &stream->ctts) ? TRUE : FALSE) == TRUE) {
     GstByteReader cslg = GST_BYTE_READER_INIT (NULL, 0);
+    guint8 ctts_version;
+    gboolean checked_ctts = FALSE;
 
     /* copy atom data into a new buffer for later use */
     stream->ctts.data = g_memdup2 (stream->ctts.data, stream->ctts.size);
 
-    /* skip version + flags */
-    if (!gst_byte_reader_skip (&stream->ctts, 1 + 3)
+    /* version 1 has signed offsets */
+    if (!gst_byte_reader_get_uint8 (&stream->ctts, &ctts_version))
+      goto corrupt_file;
+
+    /* flags */
+    if (!gst_byte_reader_skip (&stream->ctts, 3)
         || !gst_byte_reader_get_uint32_be (&stream->ctts,
             &stream->n_composition_times))
       goto corrupt_file;
@@ -9318,10 +9655,30 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl)
 
     /* This is optional, if missing we iterate the ctts */
     if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_cslg, &cslg)) {
-      if (!gst_byte_reader_skip (&cslg, 1 + 3)
-          || !gst_byte_reader_get_uint32_be (&cslg, &stream->cslg_shift)) {
-        g_free ((gpointer) cslg.data);
+      guint8 cslg_version;
+
+      /* cslg version 1 has 64 bit fields */
+      if (!gst_byte_reader_get_uint8 (&cslg, &cslg_version))
         goto corrupt_file;
+
+      /* skip flags */
+      if (!gst_byte_reader_skip (&cslg, 3))
+        goto corrupt_file;
+
+      if (cslg_version == 0) {
+        gint32 composition_to_dts_shift;
+
+        if (!gst_byte_reader_get_int32_be (&cslg, &composition_to_dts_shift))
+          goto corrupt_file;
+
+        stream->cslg_shift = MAX (0, composition_to_dts_shift);
+      } else {
+        gint64 composition_to_dts_shift;
+
+        if (!gst_byte_reader_get_int64_be (&cslg, &composition_to_dts_shift))
+          goto corrupt_file;
+
+        stream->cslg_shift = MAX (0, composition_to_dts_shift);
       }
     } else {
       gint32 cslg_least = 0;
@@ -9331,6 +9688,8 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl)
       pos = gst_byte_reader_get_pos (&stream->ctts);
       num_entries = stream->n_composition_times;
 
+      checked_ctts = TRUE;
+
       stream->cslg_shift = 0;
 
       for (i = 0; i < num_entries; i++) {
@@ -9340,35 +9699,73 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl)
         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)) {
+        if (G_UNLIKELY ((ctts_version == 0 || offset != G_MININT32)
+                && 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);
+              " larger than duration %" G_GUINT64_FORMAT, offset,
+              stream->duration);
 
           stream->cslg_shift = 0;
           stream->ctts_present = FALSE;
           goto done;
         }
 
-        if (offset < cslg_least)
+        /* Don't consider "no decode samples" with offset G_MININT32
+         * for the DTS/PTS shift */
+        if (offset != G_MININT32 && offset < cslg_least)
           cslg_least = offset;
       }
 
       if (cslg_least < 0)
-        stream->cslg_shift = ABS (cslg_least);
+        stream->cslg_shift = -cslg_least;
       else
         stream->cslg_shift = 0;
 
       /* reset the reader so we can generate sample table */
       gst_byte_reader_set_pos (&stream->ctts, pos);
     }
+
+    /* Check if ctts values are looking reasonable if that didn't happen above */
+    if (!checked_ctts) {
+      guint num_entries, pos;
+      gint i;
+
+      pos = gst_byte_reader_get_pos (&stream->ctts);
+      num_entries = stream->n_composition_times;
+
+      for (i = 0; i < num_entries; i++) {
+        gint32 offset;
+
+        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 ((ctts_version == 0 || offset != G_MININT32)
+                && 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;
+          goto done;
+        }
+      }
+
+      /* reset the reader so we can generate sample table */
+      gst_byte_reader_set_pos (&stream->ctts, pos);
+    }
   } else {
     /* Ensure the cslg_shift value is consistent so we can use it
      * unconditionally to produce TS and Segment */
     stream->cslg_shift = 0;
   }
 
+  GST_DEBUG_OBJECT (qtdemux, "Using clsg_shift %" G_GUINT64_FORMAT,
+      stream->cslg_shift);
+
   /* For raw audio streams especially we might want to merge the samples
    * to not output one audio sample per buffer. We're doing this here
    * before allocating the sample tables so that from this point onwards
@@ -9790,6 +10187,11 @@ ctts:
       ctts_count = stream->ctts_count;
       ctts_soffset = stream->ctts_soffset;
 
+      /* FIXME: Set offset to 0 for "no decode samples". This needs
+       * to be handled in a codec specific manner ideally. */
+      if (ctts_soffset == G_MININT32)
+        ctts_soffset = 0;
+
       for (j = stream->ctts_sample_index; j < ctts_count; j++) {
         cur->pts_offset = ctts_soffset;
         cur++;
@@ -9870,8 +10272,8 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream,
   stream->segments = NULL;
   if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) {
     GNode *elst;
-    gint n_segments;
-    gint segment_number, entry_size;
+    guint n_segments;
+    guint segment_number, entry_size;
     guint64 time;
     GstClockTime stime;
     const guint8 *buffer;
@@ -10579,7 +10981,7 @@ qtdemux_parse_stereo_svmi_atom (GstQTDemux * qtdemux, QtDemuxStream * stream,
   /*parse svmi header if existing */
   svmi = qtdemux_tree_get_child_by_type (stbl, FOURCC_svmi);
   if (svmi) {
-    guint len = QT_UINT32 ((guint8 *) svmi->data);
+    guint32 len = QT_UINT32 ((guint8 *) svmi->data);
     guint32 version = QT_UINT32 ((guint8 *) svmi->data + 8);
     if (!version) {
       GstVideoMultiviewMode mode = GST_VIDEO_MULTIVIEW_MODE_NONE;
@@ -10986,7 +11388,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             break;
         }
       } else {
-        gint i, j, start, end;
+        guint i, j, start, end;
 
         if (len < 94)
           goto corrupt_file;
@@ -11102,7 +11504,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
 
       if (pasp) {
         const guint8 *pasp_data = (const guint8 *) pasp->data;
-        gint len = QT_UINT32 (pasp_data);
+        guint len = QT_UINT32 (pasp_data);
 
         if (len == 16) {
           CUR_STREAM (stream)->par_w = QT_UINT32 (pasp_data + 8);
@@ -11118,7 +11520,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
 
       if (fiel) {
         const guint8 *fiel_data = (const guint8 *) fiel->data;
-        gint len = QT_UINT32 (fiel_data);
+        guint len = QT_UINT32 (fiel_data);
 
         if (len == 10) {
           CUR_STREAM (stream)->interlace_mode = GST_READ_UINT8 (fiel_data + 8);
@@ -11128,7 +11530,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
 
       if (colr) {
         const guint8 *colr_data = (const guint8 *) colr->data;
-        gint len = QT_UINT32 (colr_data);
+        guint len = QT_UINT32 (colr_data);
 
         if (len == 19 || len == 18) {
           guint32 color_type = GST_READ_UINT32_LE (colr_data + 8);
@@ -11165,14 +11567,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           case FOURCC_avc1:
           case FOURCC_avc3:
           {
-            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            guint len = QT_UINT32 (stsd_entry_data);
+            len = len <= 0x56 ? 0 : len - 0x56;
             const guint8 *avc_data = stsd_entry_data + 0x56;
 
             /* find avcC */
             while (len >= 0x8) {
-              gint size;
+              guint size;
 
-              if (QT_UINT32 (avc_data) <= len)
+              if (QT_UINT32 (avc_data) <= 0x8)
+                size = 0;
+              else if (QT_UINT32 (avc_data) <= len)
                 size = QT_UINT32 (avc_data) - 0x8;
               else
                 size = len - 0x8;
@@ -11279,14 +11684,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           case FOURCC_dvh1:
           case FOURCC_dvhe:
           {
-            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            guint len = QT_UINT32 (stsd_entry_data);
+            len = len <= 0x56 ? 0 : len - 0x56;
             const guint8 *hevc_data = stsd_entry_data + 0x56;
 
             /* find hevc */
             while (len >= 0x8) {
-              gint size;
+              guint size;
 
-              if (QT_UINT32 (hevc_data) <= len)
+              if (QT_UINT32 (hevc_data) <= 0x8)
+                size = 0;
+              else if (QT_UINT32 (hevc_data) <= len)
                 size = QT_UINT32 (hevc_data) - 0x8;
               else
                 size = len - 0x8;
@@ -11342,7 +11750,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             if (glbl) {
               guint8 *data;
               GstBuffer *buf;
-              gint len;
+              guint len;
 
               GST_DEBUG_OBJECT (qtdemux, "found glbl data in stsd");
               data = glbl->data;
@@ -11526,7 +11934,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             /* add codec_data if provided */
             if (prefix) {
               GstBuffer *buf;
-              gint len;
+              guint len;
 
               GST_DEBUG_OBJECT (qtdemux, "found prefix data in stsd");
               data = prefix->data;
@@ -11548,7 +11956,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             GstBuffer *buf;
             GstBuffer *seqh = NULL;
             const guint8 *gamma_data = NULL;
-            gint len = QT_UINT32 (stsd_data);   /* FIXME review - why put the whole stsd in codec data? */
+            guint len = QT_UINT32 (stsd_data);  /* FIXME review - why put the whole stsd in codec data? */
 
             qtdemux_parse_svq3_stsd_data (qtdemux, stsd_entry_data, &gamma_data,
                 &seqh);
@@ -11700,14 +12108,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           }
           case FOURCC_vc_1:
           {
-            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            guint len = QT_UINT32 (stsd_entry_data);
+            len = len <= 0x56 ? 0 : len - 0x56;
             const guint8 *vc1_data = stsd_entry_data + 0x56;
 
             /* find dvc1 */
             while (len >= 8) {
-              gint size;
+              guint size;
 
-              if (QT_UINT32 (vc1_data) <= len)
+              if (QT_UINT32 (vc1_data) <= 8)
+                size = 0;
+              else if (QT_UINT32 (vc1_data) <= len)
                 size = QT_UINT32 (vc1_data) - 8;
               else
                 size = len - 8;
@@ -11739,14 +12150,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           }
           case FOURCC_av01:
           {
-            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            guint len = QT_UINT32 (stsd_entry_data);
+            len = len <= 0x56 ? 0 : len - 0x56;
             const guint8 *av1_data = stsd_entry_data + 0x56;
 
             /* find av1C */
             while (len >= 0x8) {
-              gint size;
+              guint size;
 
-              if (QT_UINT32 (av1_data) <= len)
+              if (QT_UINT32 (av1_data) <= 0x8)
+                size = 0;
+              else if (QT_UINT32 (av1_data) <= len)
                 size = QT_UINT32 (av1_data) - 0x8;
               else
                 size = len - 0x8;
@@ -11818,14 +12232,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
              * vp08, vp09, and vp10 fourcc. */
           case FOURCC_vp09:
           {
-            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            guint len = QT_UINT32 (stsd_entry_data);
+            len = len <= 0x56 ? 0 : len - 0x56;
             const guint8 *vpcc_data = stsd_entry_data + 0x56;
 
             /* find vpcC */
             while (len >= 0x8) {
-              gint size;
+              guint size;
 
-              if (QT_UINT32 (vpcc_data) <= len)
+              if (QT_UINT32 (vpcc_data) <= 0x8)
+                size = 0;
+              else if (QT_UINT32 (vpcc_data) <= len)
                 size = QT_UINT32 (vpcc_data) - 0x8;
               else
                 size = len - 0x8;
@@ -11973,7 +12390,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
 
     } else if (stream->subtype == FOURCC_soun) {
       GNode *wave;
-      int version, samplesize;
+      guint version, samplesize;
       guint16 compression_id;
       gboolean amrwb = FALSE;
 
@@ -12288,7 +12705,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
         }
         case FOURCC_wma_:
         {
-          gint len = QT_UINT32 (stsd_entry_data) - offset;
+          guint len = QT_UINT32 (stsd_entry_data);
+          len = len <= offset ? 0 : len - offset;
           const guint8 *wfex_data = stsd_entry_data + offset;
           const gchar *codec_name = NULL;
           gint version = 1;
@@ -12312,9 +12730,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
 
           /* find wfex */
           while (len >= 8) {
-            gint size;
+            guint size;
 
-            if (QT_UINT32 (wfex_data) <= len)
+            if (QT_UINT32 (wfex_data) <= 0x8)
+              size = 0;
+            else if (QT_UINT32 (wfex_data) <= len)
               size = QT_UINT32 (wfex_data) - 8;
             else
               size = len - 8;
@@ -12395,7 +12815,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
         }
         case FOURCC_opus:
         {
-          const guint8 *opus_data;
+          const guint8 *dops_data;
           guint8 *channel_mapping = NULL;
           guint32 rate;
           guint8 channels;
@@ -12404,23 +12824,28 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           guint8 coupled_count;
           guint8 i;
 
-          opus_data = stsd_entry_data;
+          version = GST_READ_UINT16_BE (stsd_entry_data + 16);
+          if (version == 1)
+            dops_data = stsd_entry_data + 51;
+          else
+            dops_data = stsd_entry_data + 35;
 
-          channels = GST_READ_UINT8 (opus_data + 45);
-          rate = GST_READ_UINT32_LE (opus_data + 48);
-          channel_mapping_family = GST_READ_UINT8 (opus_data + 54);
-          stream_count = GST_READ_UINT8 (opus_data + 55);
-          coupled_count = GST_READ_UINT8 (opus_data + 56);
+          channels = GST_READ_UINT8 (dops_data + 10);
+          rate = GST_READ_UINT32_LE (dops_data + 13);
+          channel_mapping_family = GST_READ_UINT8 (dops_data + 19);
+          stream_count = GST_READ_UINT8 (dops_data + 20);
+          coupled_count = GST_READ_UINT8 (dops_data + 21);
 
           if (channels > 0) {
             channel_mapping = g_malloc (channels * sizeof (guint8));
             for (i = 0; i < channels; i++)
-              channel_mapping[i] = GST_READ_UINT8 (opus_data + i + 57);
+              channel_mapping[i] = GST_READ_UINT8 (dops_data + i + 22);
           }
 
           entry->caps = gst_codec_utils_opus_create_caps (rate, channels,
               channel_mapping_family, stream_count, coupled_count,
               channel_mapping);
+          g_free (channel_mapping);
           break;
         }
         default:
@@ -12802,7 +13227,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       entry->sampled = TRUE;
     } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text
         || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt
-        || stream->subtype == FOURCC_clcp) {
+        || stream->subtype == FOURCC_clcp || stream->subtype == FOURCC_wvtt) {
 
       entry->sampled = TRUE;
       entry->sparse = TRUE;
@@ -12846,6 +13271,23 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       GST_INFO_OBJECT (qtdemux,
           "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT,
           GST_FOURCC_ARGS (fourcc), entry->caps);
+    } else if (stream->subtype == FOURCC_meta) {
+      entry->sampled = TRUE;
+      entry->sparse = TRUE;
+
+      entry->caps =
+          qtdemux_meta_caps (qtdemux, stream, entry, fourcc, stsd_entry_data,
+          &codec);
+      if (codec) {
+        gst_tag_list_add (stream->stream_tags, GST_TAG_MERGE_REPLACE,
+            GST_TAG_CODEC, codec, NULL);
+        g_free (codec);
+        codec = NULL;
+      }
+
+      GST_INFO_OBJECT (qtdemux,
+          "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT,
+          GST_FOURCC_ARGS (fourcc), entry->caps);
     } else {
       /* everything in 1 sample */
       entry->sampled = TRUE;
@@ -13179,7 +13621,8 @@ qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux,
 
   /* unset new_stream to prevent stream-start event, unless we are EOS in which
    * case we need to force one through */
-  newstream->new_stream = GST_PAD_IS_EOS (newstream->pad);
+  newstream->new_stream = newstream->pad != NULL
+      && GST_PAD_IS_EOS (newstream->pad);
 
   return gst_qtdemux_configure_stream (qtdemux, newstream);
 }
@@ -13671,6 +14114,10 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
 
   /* parse any protection system info */
   pssh = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_pssh);
+  if (pssh) {
+    /* Unref old protection events if we are going to receive new ones. */
+    qtdemux_clear_protection_events_on_all_streams (qtdemux);
+  }
   while (pssh) {
     GST_LOG_OBJECT (qtdemux, "Parsing pssh box.");
     qtdemux_parse_pssh (qtdemux, pssh);
@@ -14388,6 +14835,23 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
           "stream-format", G_TYPE_STRING, "avc3",
           "alignment", G_TYPE_STRING, "au", NULL);
       break;
+    case FOURCC_ai12:
+    case FOURCC_ai13:
+    case FOURCC_ai15:
+    case FOURCC_ai16:
+    case FOURCC_ai1p:
+    case FOURCC_ai1q:
+    case FOURCC_ai52:
+    case FOURCC_ai53:
+    case FOURCC_ai55:
+    case FOURCC_ai56:
+    case FOURCC_ai5p:
+    case FOURCC_ai5q:
+      _codec ("H.264 / AVC");
+      caps = gst_caps_new_simple ("video/x-h264",
+          "stream-format", G_TYPE_STRING, "byte-stream",
+          "alignment", G_TYPE_STRING, "au", NULL);
+      break;
     case FOURCC_H265:
     case FOURCC_hvc1:
     case FOURCC_dvh1:
@@ -14553,7 +15017,9 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       break;
     case FOURCC_av01:
       _codec ("AV1");
-      caps = gst_caps_new_empty_simple ("video/x-av1");
+      caps = gst_caps_new_simple ("video/x-av1",
+          "stream-format", G_TYPE_STRING, "obu-stream",
+          "alignment", G_TYPE_STRING, "tu", NULL);
       break;
     case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'):
     default:
@@ -14947,7 +15413,7 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
     case FOURCC_mp4s:
       _codec ("DVD subtitle");
       caps = gst_caps_new_empty_simple ("subpicture/x-dvd");
-      stream->need_process = TRUE;
+      stream->process_func = gst_qtdemux_process_buffer_dvd;
       break;
     case FOURCC_text:
       _codec ("Quicktime timed text");
@@ -14958,18 +15424,34 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING,
           "utf8", NULL);
       /* actual text piece needs to be extracted */
-      stream->need_process = TRUE;
+      stream->process_func = gst_qtdemux_process_buffer_text;
       break;
     case FOURCC_stpp:
       _codec ("XML subtitles");
       caps = gst_caps_new_empty_simple ("application/ttml+xml");
       break;
+    case FOURCC_wvtt:
+    {
+      GstBuffer *buffer;
+      const gchar *buf = "WEBVTT\n\n";
+
+      _codec ("WebVTT subtitles");
+      caps = gst_caps_new_empty_simple ("application/x-subtitle-vtt");
+      stream->process_func = gst_qtdemux_process_buffer_wvtt;
+
+      /* FIXME: Parse the vttC atom and get the entire WEBVTT header */
+      buffer = gst_buffer_new_and_alloc (8);
+      gst_buffer_fill (buffer, 0, buf, 8);
+      stream->buffers = g_slist_append (stream->buffers, buffer);
+
+      break;
+    }
     case FOURCC_c608:
       _codec ("CEA 608 Closed Caption");
       caps =
           gst_caps_new_simple ("closedcaption/x-cea-608", "format",
           G_TYPE_STRING, "s334-1a", NULL);
-      stream->need_process = TRUE;
+      stream->process_func = gst_qtdemux_process_buffer_clcp;
       stream->need_split = TRUE;
       break;
     case FOURCC_c708:
@@ -14977,7 +15459,7 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       caps =
           gst_caps_new_simple ("closedcaption/x-cea-708", "format",
           G_TYPE_STRING, "cdp", NULL);
-      stream->need_process = TRUE;
+      stream->process_func = gst_qtdemux_process_buffer_clcp;
       break;
 
     default:
@@ -14990,6 +15472,63 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
 }
 
 static GstCaps *
+qtdemux_meta_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    QtDemuxStreamStsdEntry * entry, guint32 fourcc,
+    const guint8 * stsd_entry_data, gchar ** codec_name)
+{
+  GstCaps *caps = NULL;
+
+  GST_DEBUG_OBJECT (qtdemux, "resolve fourcc 0x%08x", GUINT32_TO_BE (fourcc));
+
+  switch (fourcc) {
+    case FOURCC_metx:{
+      gsize size = QT_UINT32 (stsd_entry_data);
+      GstByteReader reader = GST_BYTE_READER_INIT (stsd_entry_data, size);
+      const gchar *content_encoding;
+      const gchar *namespaces;
+      const gchar *schema_locations;
+
+      if (!gst_byte_reader_skip (&reader, 8 + 6 + 2)) {
+        GST_WARNING_OBJECT (qtdemux, "Too short metx sample entry");
+        break;
+      }
+
+      if (!gst_byte_reader_get_string (&reader, &content_encoding) ||
+          !gst_byte_reader_get_string (&reader, &namespaces) ||
+          !gst_byte_reader_get_string (&reader, &schema_locations)) {
+        GST_WARNING_OBJECT (qtdemux, "Too short metx sample entry");
+        break;
+      }
+
+      if (strstr (namespaces, "http://www.onvif.org/ver10/schema") != 0) {
+        if (content_encoding == NULL || *content_encoding == '\0'
+            || g_ascii_strcasecmp (content_encoding, "xml") == 0) {
+          _codec ("ONVIF Timed XML MetaData");
+          caps =
+              gst_caps_new_simple ("application/x-onvif-metadata", "parsed",
+              G_TYPE_BOOLEAN, TRUE, NULL);
+        } else {
+          GST_DEBUG_OBJECT (qtdemux, "Unknown content encoding: %s",
+              content_encoding);
+        }
+      } else {
+        GST_DEBUG_OBJECT (qtdemux, "Unknown metadata namespaces: %s",
+            namespaces);
+      }
+
+      break;
+    }
+    default:
+      break;
+  }
+
+  if (!caps)
+    caps = _get_unknown_codec_name ("meta", fourcc);
+
+  return caps;
+}
+
+static GstCaps *
 qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
     QtDemuxStreamStsdEntry * entry, guint32 fourcc,
     const guint8 * stsd_entry_data, gchar ** codec_name)