From e24f903b13ff9d1676ab81768c324364eff7901d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 29 Nov 2014 14:37:25 +0000 Subject: [PATCH] qtdemux: parse mfra random access box for fragmented mp4 files If it's present, and we operate in pull mode. --- gst/isomp4/qtdemux.c | 237 ++++++++++++++++++++++++++++++--------------------- gst/isomp4/qtdemux.h | 2 - 2 files changed, 139 insertions(+), 100 deletions(-) diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 1db4a31..4420cbe 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -6,7 +6,9 @@ * Copyright (C) <2009> Tim-Philipp Müller * Copyright (C) <2009> STEricsson * Copyright (C) <2013> Sreerenj Balachandran - * 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; diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h index 47c8202..d050503 100644 --- a/gst/isomp4/qtdemux.h +++ b/gst/isomp4/qtdemux.h @@ -79,8 +79,6 @@ struct _GstQTDemux { guint64 duration; gboolean fragmented; - /* offset of the mfra atom */ - guint64 mfra_offset; guint64 moof_offset; gint state; -- 2.7.4