qtdemux: parse mfra random access box for fragmented mp4 files
authorTim-Philipp Müller <tim@centricular.com>
Sat, 29 Nov 2014 14:37:25 +0000 (14:37 +0000)
committerTim-Philipp Müller <tim@centricular.com>
Sun, 30 Nov 2014 15:33:13 +0000 (15:33 +0000)
If it's present, and we operate in pull mode.

gst/isomp4/qtdemux.c
gst/isomp4/qtdemux.h

index 1db4a31..4420cbe 100644 (file)
@@ -6,7 +6,9 @@
  * Copyright (C) <2009> Tim-Philipp Müller <tim centricular net>
  * Copyright (C) <2009> STEricsson <benjamin.gaignard@stericsson.com>
  * Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com>
- * Copyright (C) <2013> Intel Coroporation
+ * Copyright (C) <2013> Intel Corporation
+ * Copyright (C) <2014> Centricular Ltd
+ *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  * License as published by the Free Software Foundation; either
@@ -199,6 +201,13 @@ struct _QtDemuxSegment
 
 #define QTSEGMENT_IS_EMPTY(s) ((s)->media_start == GST_CLOCK_TIME_NONE)
 
+/* Used with fragmented MP4 files (mfra atom) */
+typedef struct
+{
+  GstClockTime ts;
+  guint64 moof_offset;
+} QtDemuxRandomAccessEntry;
+
 struct _QtDemuxStream
 {
   GstPad *pad;
@@ -347,6 +356,9 @@ struct _QtDemuxStream
   gboolean stps_present;
   guint32 n_sample_partial_syncs;
   guint32 stps_index;
+  QtDemuxRandomAccessEntry *ra_entries;
+  guint n_ra_entries;
+
   /* ctts */
   gboolean ctts_present;
   guint32 n_composition_times;
@@ -471,6 +483,8 @@ static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux);
 static void qtdemux_do_allocation (GstQTDemux * qtdemux,
     QtDemuxStream * stream);
 
+static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux);
+
 static void
 gst_qtdemux_class_init (GstQTDemuxClass * klass)
 {
@@ -1844,7 +1858,6 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     qtdemux->fragment_start = -1;
     qtdemux->fragment_start_offset = -1;
     qtdemux->duration = 0;
-    qtdemux->mfra_offset = 0;
     qtdemux->moof_offset = 0;
     qtdemux->chapters_track_id = 0;
     qtdemux->have_group_id = FALSE;
@@ -2143,6 +2156,10 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
   stream->redirect_uri = NULL;
   /* free stbl sub-atoms */
   gst_qtdemux_stbl_free (stream);
+  /* fragments */
+  g_free (stream->ra_entries);
+  stream->ra_entries = NULL;
+  stream->n_ra_entries = 0;
 
   stream->sent_eos = FALSE;
   stream->segment_index = -1;
@@ -2888,44 +2905,53 @@ fail:
   }
 }
 
+#if 0
 /* might be used if some day we actually use mfra & co
  * for random access to fragments,
  * but that will require quite some modifications and much less relying
  * on a sample array */
-#if 0
+#endif
+
 static gboolean
-qtdemux_parse_tfra (GstQTDemux * qtdemux, GNode * tfra_node,
-    QtDemuxStream * stream)
+qtdemux_parse_tfra (GstQTDemux * qtdemux, GNode * tfra_node)
 {
-  guint64 time = 0, moof_offset = 0;
+  QtDemuxStream *stream;
   guint32 ver_flags, track_id, len, num_entries, i;
   guint value_size, traf_size, trun_size, sample_size;
+  guint64 time = 0, moof_offset = 0;
+#if 0
   GstBuffer *buf = NULL;
   GstFlowReturn ret;
+#endif
   GstByteReader tfra;
 
-  gst_byte_reader_init (&tfra, (guint8 *) tfra_node->data + (4 + 4),
-      QT_UINT32 ((guint8 *) tfra_node->data) - (4 + 4));
+  gst_byte_reader_init (&tfra, tfra_node->data, QT_UINT32 (tfra_node->data));
 
-  if (!gst_byte_reader_get_uint32_be (&tfra, &ver_flags))
+  if (!gst_byte_reader_skip (&tfra, 8))
     return FALSE;
 
-  if (!(gst_byte_reader_get_uint32_be (&tfra, &track_id) &&
-          gst_byte_reader_get_uint32_be (&tfra, &len) &&
-          gst_byte_reader_get_uint32_be (&tfra, &num_entries)))
+  if (!gst_byte_reader_get_uint32_be (&tfra, &ver_flags))
     return FALSE;
 
-  GST_LOG_OBJECT (qtdemux, "id %d == stream id %d ?",
-      track_id, stream->track_id);
-  if (track_id != stream->track_id) {
+  if (!gst_byte_reader_get_uint32_be (&tfra, &track_id)
+      || !gst_byte_reader_get_uint32_be (&tfra, &len)
+      || !gst_byte_reader_get_uint32_be (&tfra, &num_entries))
     return FALSE;
-  }
+
+  GST_DEBUG_OBJECT (qtdemux, "parsing tfra box for track id %u", track_id);
+
+  stream = qtdemux_find_stream (qtdemux, track_id);
+  if (stream == NULL)
+    goto unknown_trackid;
 
   value_size = ((ver_flags >> 24) == 1) ? sizeof (guint64) : sizeof (guint32);
   sample_size = (len & 3) + 1;
   trun_size = ((len & 12) >> 2) + 1;
   traf_size = ((len & 48) >> 4) + 1;
 
+  GST_DEBUG_OBJECT (qtdemux, "%u entries, sizes: value %u, traf %u, trun %u, "
+      "sample %u", num_entries, value_size, traf_size, trun_size, sample_size);
+
   if (num_entries == 0)
     goto no_samples;
 
@@ -2933,6 +2959,10 @@ qtdemux_parse_tfra (GstQTDemux * qtdemux, GNode * tfra_node,
           value_size + value_size + traf_size + trun_size + sample_size))
     goto corrupt_file;
 
+  g_free (stream->ra_entries);
+  stream->ra_entries = g_new (QtDemuxRandomAccessEntry, num_entries);
+  stream->n_ra_entries = num_entries;
+
   for (i = 0; i < num_entries; i++) {
     qt_atom_parser_get_offset (&tfra, value_size, &time);
     qt_atom_parser_get_offset (&tfra, value_size, &moof_offset);
@@ -2940,26 +2970,36 @@ qtdemux_parse_tfra (GstQTDemux * qtdemux, GNode * tfra_node,
     qt_atom_parser_get_uint_with_size_unchecked (&tfra, trun_size);
     qt_atom_parser_get_uint_with_size_unchecked (&tfra, sample_size);
 
-    GST_LOG_OBJECT (qtdemux,
-        "fragment time: %" GST_TIME_FORMAT " moof_offset: %u",
-        GST_TIME_ARGS (gst_util_uint64_scale (time, GST_SECOND,
-                stream->timescale)), moof_offset);
+    time = gst_util_uint64_scale (time, GST_SECOND, stream->timescale);
+
+    GST_LOG_OBJECT (qtdemux, "fragment time: %" GST_TIME_FORMAT ", "
+        " moof_offset: %" G_GUINT64_FORMAT, GST_TIME_ARGS (time), moof_offset);
+
+    stream->ra_entries[i].ts = time;
+    stream->ra_entries[i].moof_offset = moof_offset;
 
+    /* don't want to go through the entire file and read all moofs at startup */
+#if 0
     ret = gst_qtdemux_pull_atom (qtdemux, moof_offset, 0, &buf);
     if (ret != GST_FLOW_OK)
       goto corrupt_file;
     qtdemux_parse_moof (qtdemux, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf),
         moof_offset, stream);
     gst_buffer_unref (buf);
+#endif
   }
 
   return TRUE;
 
 /* ERRORS */
+unknown_trackid:
+  {
+    GST_WARNING_OBJECT (qtdemux, "Couldn't find stream for track %u", track_id);
+    return FALSE;
+  }
 corrupt_file:
   {
-    GST_ELEMENT_ERROR (qtdemux, STREAM, DECODE,
-        (_("This file is corrupt and cannot be played.")), (NULL));
+    GST_WARNING_OBJECT (qtdemux, "broken traf box, ignoring");
     return FALSE;
   }
 no_samples:
@@ -2970,106 +3010,104 @@ no_samples:
 }
 
 static gboolean
-qtdemux_parse_mfra (GstQTDemux * qtdemux, QtDemuxStream * stream)
+qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux)
 {
-  GstFlowReturn ret;
+  GstMapInfo mfro_map = GST_MAP_INFO_INIT;
+  GstMapInfo mfra_map = GST_MAP_INFO_INIT;
+  GstBuffer *mfro = NULL, *mfra = NULL;
+  GstFlowReturn flow;
+  gboolean ret = FALSE;
   GNode *mfra_node, *tfra_node;
-  GstBuffer *buffer;
+  guint64 mfra_offset = 0;
+  guint32 fourcc, mfra_size;
+  gint64 len;
 
-  if (!qtdemux->mfra_offset)
-    return FALSE;
+  /* query upstream size in bytes */
+  if (!gst_pad_peer_query_duration (qtdemux->sinkpad, GST_FORMAT_BYTES, &len))
+    goto size_query_failed;
 
-  ret = gst_qtdemux_pull_atom (qtdemux, qtdemux->mfra_offset, 0, &buffer);
-  if (ret != GST_FLOW_OK)
-    goto corrupt_file;
+  /* mfro box should be at the very end of the file */
+  flow = gst_qtdemux_pull_atom (qtdemux, len - 16, 16, &mfro);
+  if (flow != GST_FLOW_OK)
+    goto exit;
+
+  gst_buffer_map (mfro, &mfro_map, GST_MAP_READ);
+
+  fourcc = QT_FOURCC (mfro_map.data + 4);
+  if (fourcc != FOURCC_mfro)
+    goto exit;
+
+  GST_INFO_OBJECT (qtdemux, "Found mfro box");
+  if (mfro_map.size < 16)
+    goto invalid_mfro_size;
+
+  mfra_size = QT_UINT32 (mfro_map.data + 12);
+  if (mfra_size >= len)
+    goto invalid_mfra_size;
+
+  mfra_offset = len - mfra_size;
+
+  GST_INFO_OBJECT (qtdemux, "mfra offset: %" G_GUINT64_FORMAT ", size %u",
+      mfra_offset, mfra_size);
+
+  /* now get and parse mfra box */
+  flow = gst_qtdemux_pull_atom (qtdemux, mfra_offset, mfra_size, &mfra);
+  if (flow != GST_FLOW_OK)
+    goto broken_file;
 
-  mfra_node = g_node_new ((guint8 *) GST_BUFFER_DATA (buffer));
-  qtdemux_parse_node (qtdemux, mfra_node, GST_BUFFER_DATA (buffer),
-      GST_BUFFER_SIZE (buffer));
+  gst_buffer_map (mfra, &mfra_map, GST_MAP_READ);
+
+  mfra_node = g_node_new ((guint8 *) mfra_map.data);
+  qtdemux_parse_node (qtdemux, mfra_node, mfra_map.data, mfra_map.size);
 
   tfra_node = qtdemux_tree_get_child_by_type (mfra_node, FOURCC_tfra);
 
   while (tfra_node) {
-    qtdemux_parse_tfra (qtdemux, tfra_node, stream);
+    qtdemux_parse_tfra (qtdemux, tfra_node);
     /* iterate all siblings */
     tfra_node = qtdemux_tree_get_sibling_by_type (tfra_node, FOURCC_tfra);
   }
   g_node_destroy (mfra_node);
-  gst_buffer_unref (buffer);
 
-  return TRUE;
+  GST_INFO_OBJECT (qtdemux, "parsed movie fragment random access box (mfra)");
+  ret = TRUE;
 
-corrupt_file:
-  {
-    GST_ELEMENT_ERROR (qtdemux, STREAM, DECODE,
-        (_("This file is corrupt and cannot be played.")), (NULL));
-    return FALSE;
-  }
-}
+exit:
 
-static GstFlowReturn
-qtdemux_parse_mfro (GstQTDemux * qtdemux, guint64 * mfra_offset,
-    guint32 * mfro_size)
-{
-  GstFlowReturn ret = GST_FLOW_ERROR;
-  GstBuffer *mfro = NULL;
-  guint32 fourcc;
-  gint64 len;
-  GstFormat fmt = GST_FORMAT_BYTES;
+  if (mfro) {
+    if (mfro_map.memory != NULL)
+      gst_buffer_unmap (mfro, &mfro_map);
+    gst_buffer_unref (mfro);
+  }
+  if (mfra) {
+    if (mfra_map.memory != NULL)
+      gst_buffer_unmap (mfra, &mfra_map);
+    gst_buffer_unref (mfra);
+  }
+  return ret;
 
-  if (!gst_pad_peer_query_duration (qtdemux->sinkpad, &fmt, &len)) {
-    GST_DEBUG_OBJECT (qtdemux, "upstream size not available; "
-        "can not locate mfro");
+/* ERRORS */
+size_query_failed:
+  {
+    GST_WARNING_OBJECT (qtdemux, "could not query upstream size");
     goto exit;
   }
-
-  ret = gst_qtdemux_pull_atom (qtdemux, len - 16, 16, &mfro);
-  if (ret != GST_FLOW_OK)
+invalid_mfro_size:
+  {
+    GST_WARNING_OBJECT (qtdemux, "mfro size is too small");
     goto exit;
-
-  fourcc = QT_FOURCC (GST_BUFFER_DATA (mfro) + 4);
-  if (fourcc != FOURCC_mfro)
+  }
+invalid_mfra_size:
+  {
+    GST_WARNING_OBJECT (qtdemux, "mfra_size in mfro box is invalid");
     goto exit;
-
-  GST_INFO_OBJECT (qtdemux, "Found mfro atom: fragmented mp4 container");
-  if (GST_BUFFER_SIZE (mfro) >= 16) {
-    GST_DEBUG_OBJECT (qtdemux, "parsing 'mfro' atom");
-    *mfro_size = QT_UINT32 (GST_BUFFER_DATA (mfro) + 12);
-    if (*mfro_size >= len) {
-      GST_WARNING_OBJECT (qtdemux, "mfro.size is invalid");
-      ret = GST_FLOW_ERROR;
-      goto exit;
-    }
-    *mfra_offset = len - *mfro_size;
   }
-
-exit:
-  if (mfro)
-    gst_buffer_unref (mfro);
-
-  return ret;
-}
-
-static void
-qtdemux_parse_fragmented (GstQTDemux * qtdemux)
-{
-  GstFlowReturn ret;
-  guint32 mfra_size = 0;
-  guint64 mfra_offset = 0;
-
-  /* default */
-  qtdemux->fragmented = FALSE;
-
-  /* We check here if it is a fragmented mp4 container */
-  ret = qtdemux_parse_mfro (qtdemux, &mfra_offset, &mfra_size);
-  if (ret == GST_FLOW_OK && mfra_size != 0 && mfra_offset != 0) {
-    qtdemux->fragmented = TRUE;
-    GST_DEBUG_OBJECT (qtdemux,
-        "mfra atom expected at offset %" G_GUINT64_FORMAT, mfra_offset);
-    qtdemux->mfra_offset = mfra_offset;
+broken_file:
+  {
+    GST_WARNING_OBJECT (qtdemux, "bogus mfra offset or size, broken file");
+    goto exit;
   }
 }
-#endif
 
 static GstFlowReturn
 gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
@@ -3106,6 +3144,9 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
       if (!qtdemux->moof_offset) {
         qtdemux->moof_offset = qtdemux->offset;
       }
+      if (qtdemux_pull_mfro_mfra (qtdemux)) {
+        /* FIXME */
+      }
       if (qtdemux->got_moov) {
         GST_INFO_OBJECT (qtdemux, "moof header, got moov, done with headers");
         ret = GST_FLOW_EOS;
index 47c8202..d050503 100644 (file)
@@ -79,8 +79,6 @@ struct _GstQTDemux {
   guint64 duration;
 
   gboolean fragmented;
-  /* offset of the mfra atom */
-  guint64 mfra_offset;
   guint64 moof_offset;
 
   gint state;