qtdemux: parse mfra random access box for fragmented mp4 files

If it's present, and we operate in pull mode.
This commit is contained in:
Tim-Philipp Müller 2014-11-29 14:37:25 +00:00
parent 8a0f4e74e4
commit e24f903b13
2 changed files with 142 additions and 103 deletions

View file

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

View file

@ -79,8 +79,6 @@ struct _GstQTDemux {
guint64 duration; guint64 duration;
gboolean fragmented; gboolean fragmented;
/* offset of the mfra atom */
guint64 mfra_offset;
guint64 moof_offset; guint64 moof_offset;
gint state; gint state;