From 4790603d3105938b35238b85992fb256514b7d10 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Mon, 1 Nov 2010 13:40:05 +0100 Subject: [PATCH] qtdemux: fragmented support; consider mvex and handle flags and offset fields --- gst/qtdemux/qtdemux.c | 236 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 206 insertions(+), 30 deletions(-) diff --git a/gst/qtdemux/qtdemux.c b/gst/qtdemux/qtdemux.c index 7ef05589b9..ff62f23b00 100644 --- a/gst/qtdemux/qtdemux.c +++ b/gst/qtdemux/qtdemux.c @@ -334,6 +334,12 @@ struct _QtDemuxStream guint32 ctts_sample_index; guint32 ctts_count; gint32 ctts_soffset; + + /* fragmented */ + gboolean parsed_trex; + guint32 def_sample_duration; + guint32 def_sample_size; + guint32 def_sample_flags; }; enum QtDemuxState @@ -1854,18 +1860,120 @@ extract_initial_length_and_fourcc (const guint8 * data, guint64 * plength, *pfourcc = fourcc; } +static gboolean +qtdemux_parse_mehd (GstQTDemux * qtdemux, GstByteReader * br) +{ + guint32 version; + guint64 duration; + + if (!gst_byte_reader_get_uint32_be (br, &version)) + goto failed; + + version >>= 24; + if (version == 1) { + if (!gst_byte_reader_get_uint64_be (br, &duration)) + goto failed; + } else { + guint32 dur; + + if (!gst_byte_reader_get_uint32_be (br, &dur)) + goto failed; + duration = dur; + } + + GST_INFO_OBJECT (qtdemux, "mehd duration: %" G_GUINT64_FORMAT, duration); + qtdemux->duration = duration; + + return TRUE; + +failed: + { + GST_DEBUG_OBJECT (qtdemux, "parsing mehd failed"); + return FALSE; + } +} + +static gboolean +qtdemux_parse_trex (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 * ds_duration, guint32 * ds_size, guint32 * ds_flags) +{ + if (!stream->parsed_trex && qtdemux->moov_node) { + GNode *mvex, *trex; + GstByteReader trex_data; + + mvex = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_mvex); + if (mvex) { + trex = qtdemux_tree_get_child_by_type_full (mvex, FOURCC_trex, + &trex_data); + while (trex) { + guint32 id, dur, size, flags; + + /* skip version/flags */ + if (!gst_byte_reader_skip (&trex_data, 4)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &id)) + goto next; + if (id != stream->track_id) + goto next; + /* sample description index; ignore */ + if (!gst_byte_reader_get_uint32_be (&trex_data, &dur)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &dur)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &size)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &flags)) + goto next; + + GST_DEBUG_OBJECT (qtdemux, "fragment defaults for stream %d; " + "duration %d, size %d, flags 0x%x", stream->track_id, + dur, size, flags); + + stream->parsed_trex = TRUE; + stream->def_sample_duration = dur; + stream->def_sample_size = size; + stream->def_sample_flags = flags; + + next: + /* iterate all siblings */ + trex = qtdemux_tree_get_sibling_by_type_full (trex, FOURCC_trex, + &trex_data); + } + } + } + + *ds_duration = stream->def_sample_duration; + *ds_size = stream->def_sample_size; + *ds_size = stream->def_sample_size; + + /* even then, above values are better than random ... */ + if (G_UNLIKELY (!stream->parsed_trex)) { + GST_WARNING_OBJECT (qtdemux, + "failed to find fragment defaults for stream %d", stream->track_id); + return FALSE; + } + + return TRUE; +} + static gboolean qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, - QtDemuxStream * stream, guint32 mdat_offset, - guint32 d_sample_duration, guint32 d_sample_size, guint32 * samples_count) + QtDemuxStream * stream, guint32 d_sample_duration, guint32 d_sample_size, + guint32 d_sample_flags, guint32 * samples_count, gint64 * base_offset) { guint64 timestamp; - guint32 flags, data_offset; + gint32 data_offset; + guint32 flags, first_flags = 0; gint i; guint8 *data; - guint entry_size, dur_offset, size_offset; + guint entry_size, dur_offset, size_offset, flags_offset, ct_offset; QtDemuxSample *sample; + GST_LOG_OBJECT (qtdemux, "parsing trun stream %d; " + "default dur %d, size %d, flags 0x%x, base offset %" G_GUINT64_FORMAT, + stream->track_id, d_sample_duration, d_sample_size, d_sample_flags, + data_offset); + if (!gst_byte_reader_skip (trun, 1) || !gst_byte_reader_get_uint24_be (trun, &flags)) goto fail; @@ -1873,32 +1981,50 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, if (!gst_byte_reader_get_uint32_be (trun, samples_count)) goto fail; - /*FIXME: handle this flag properly */ if (flags & TR_DATA_OFFSET) { - if (!gst_byte_reader_get_uint32_be (trun, &data_offset)) + /* note this is really signed */ + if (!gst_byte_reader_get_int32_be (trun, &data_offset)) goto fail; + *base_offset += data_offset; } - if (flags & TR_FIRST_SAMPLE_FLAGS) - if (!gst_byte_reader_skip (trun, 4)) - goto fail; + GST_LOG_OBJECT (qtdemux, "trun offset %d, flags 0x%x, entries %d", + data_offset, flags, *samples_count); + + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (G_UNLIKELY (flags & TR_SAMPLE_FLAGS)) { + GST_DEBUG_OBJECT (qtdemux, + "invalid flags; SAMPLE and FIRST_SAMPLE present, discarding latter"); + flags ^= TR_FIRST_SAMPLE_FLAGS; + } else { + if (!gst_byte_reader_get_uint32_be (trun, &first_flags)) + goto fail; + GST_LOG_OBJECT (qtdemux, "first flags: 0x%x", first_flags); + } + } /* FIXME ? spec says other bits should also be checked to determine * entry size (and prefix size for that matter) */ entry_size = 0; dur_offset = size_offset = 0; if (flags & TR_SAMPLE_DURATION) { + GST_LOG_OBJECT (qtdemux, "entry duration present"); dur_offset = entry_size; entry_size += 4; } if (flags & TR_SAMPLE_SIZE) { + GST_LOG_OBJECT (qtdemux, "entry size present"); size_offset = entry_size; entry_size += 4; } if (flags & TR_SAMPLE_FLAGS) { + GST_LOG_OBJECT (qtdemux, "entry flags present"); + flags_offset = entry_size; entry_size += 4; } if (flags & TR_COMPOSITION_TIME_OFFSETS) { + GST_LOG_OBJECT (qtdemux, "entry ct offset present"); + ct_offset = entry_size; entry_size += 4; } @@ -1906,8 +2032,6 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, goto fail; data = (guint8 *) gst_byte_reader_peek_data_unchecked (trun); - data_offset = mdat_offset + 8; - if (stream->n_samples >= QTDEMUX_MAX_SAMPLE_INDEX_SIZE / sizeof (QtDemuxSample)) goto index_too_big; @@ -1937,7 +2061,7 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, } sample = stream->samples + stream->n_samples; for (i = 0; i < *samples_count; i++) { - guint32 dur, size; + guint32 dur, size, sflags, ct; /* first read sample data */ if (flags & TR_SAMPLE_DURATION) { @@ -1950,16 +2074,33 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, } else { size = d_sample_size; } + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (i == 0) { + sflags = first_flags; + } else { + sflags = d_sample_flags; + } + } else if (flags & TR_SAMPLE_FLAGS) { + sflags = QT_UINT32 (data + flags_offset); + } else { + sflags = d_sample_flags; + } + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + ct = QT_UINT32 (data + ct_offset); + } else { + ct = 0; + } data += entry_size; /* fill the sample information */ - sample->offset = data_offset; - sample->pts_offset = 0; + sample->offset = *base_offset; + sample->pts_offset = ct; sample->size = size; sample->timestamp = timestamp; sample->duration = dur; - sample->keyframe = (i == 0); - data_offset += size; + /* sample-is-difference-sample */ + sample->keyframe = !(sflags & 0x10000); + *base_offset += size; timestamp += dur; sample++; } @@ -1991,7 +2132,8 @@ index_too_big: static gboolean qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, guint32 * track_id, guint32 * default_sample_duration, - guint32 * default_sample_size) + guint32 * default_sample_size, guint32 * default_sample_flags, + gint64 * base_offset) { guint32 flags; @@ -2002,9 +2144,8 @@ qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, if (!gst_byte_reader_get_uint32_be (tfhd, track_id)) goto invalid_track; - /* FIXME: Handle TF_BASE_DATA_OFFSET properly */ if (flags & TF_BASE_DATA_OFFSET) - if (!gst_byte_reader_skip (tfhd, 4)) + if (!gst_byte_reader_get_uint64_be (tfhd, (guint64 *) base_offset)) goto invalid_track; /* FIXME: Handle TF_SAMPLE_DESCRIPTION_INDEX properly */ @@ -2020,6 +2161,10 @@ qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_size)) goto invalid_track; + if (flags & TF_DEFAULT_SAMPLE_FLAGS) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_flags)) + goto invalid_track; + return TRUE; invalid_track: @@ -2035,16 +2180,20 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, { GNode *moof_node, *traf_node, *tfhd_node, *trun_node; GstByteReader trun_data, tfhd_data; - guint32 id = 0, default_sample_size = 0, default_sample_duration = 0; + guint32 id = 0; + guint32 ds_size = 0, ds_duration = 0, ds_flags = 0; guint32 samples_count = 0; - guint64 mdat_offset; + gint64 base_offset; - mdat_offset = moof_offset + length; + /* obtain stream defaults */ + qtdemux_parse_trex (qtdemux, stream, &ds_duration, &ds_size, &ds_flags); moof_node = g_node_new ((guint8 *) buffer); qtdemux_parse_node (qtdemux, moof_node, buffer, length); qtdemux_node_dump (qtdemux, moof_node); + /* default base offset = first byte of moof */ + base_offset = moof_offset; traf_node = qtdemux_tree_get_child_by_type (moof_node, FOURCC_traf); while (traf_node) { /* Fragment Header node */ @@ -2053,19 +2202,26 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, &tfhd_data); if (!tfhd_node) goto missing_tfhd; - if (!qtdemux_parse_tfhd (qtdemux, &tfhd_data, &id, &default_sample_duration, - &default_sample_size)) + if (!qtdemux_parse_tfhd (qtdemux, &tfhd_data, &id, &ds_duration, + &ds_size, &ds_flags, &base_offset)) goto missing_tfhd; /* skip trun atoms that don't match the track ID */ - if (id != stream->track_id) + if (id != stream->track_id) { + /* lost track of offset here */ + base_offset = -1; goto next; + } else if (base_offset == -1) { + GST_WARNING_OBJECT (qtdemux, "FIXME: no base_offset for data"); + /* FIXME modify parsing so we don't have this limitation */ + goto missing_tfhd; + } /* Track Run node */ trun_node = qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_trun, &trun_data); while (trun_node) { - qtdemux_parse_trun (qtdemux, &trun_data, stream, mdat_offset, - default_sample_duration, default_sample_size, &samples_count); + qtdemux_parse_trun (qtdemux, &trun_data, stream, + ds_duration, ds_size, ds_flags, &samples_count, &base_offset); /* iterate all siblings */ trun_node = qtdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun, &trun_data); @@ -7813,6 +7969,7 @@ qtdemux_parse_tree (GstQTDemux * qtdemux) GNode *mvhd; GNode *trak; GNode *udta; + GNode *mvex; gint64 duration; guint64 creation_time; GstDateTime *datetime = NULL; @@ -7861,9 +8018,12 @@ qtdemux_parse_tree (GstQTDemux * qtdemux) GST_INFO_OBJECT (qtdemux, "timescale: %u", qtdemux->timescale); GST_INFO_OBJECT (qtdemux, "duration: %" G_GUINT64_FORMAT, qtdemux->duration); - /* set duration in the segment info */ - gst_qtdemux_get_duration (qtdemux, &duration); - gst_segment_set_duration (&qtdemux->segment, GST_FORMAT_TIME, duration); + /* check for fragmented file and get some (default) data */ + mvex = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_mvex); + if (mvex) { + /* let track parsing or anyone know weird stuff might happen ... */ + qtdemux->fragmented = TRUE; + } /* parse all traks */ trak = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_trak); @@ -7872,6 +8032,22 @@ qtdemux_parse_tree (GstQTDemux * qtdemux) /* iterate all siblings */ trak = qtdemux_tree_get_sibling_by_type (trak, FOURCC_trak); } + + /* compensate for total duration */ + if (mvex) { + GNode *mehd; + GstByteReader mehd_data; + + mehd = qtdemux_tree_get_child_by_type_full (mvex, FOURCC_mehd, &mehd_data); + if (mehd) + qtdemux_parse_mehd (qtdemux, &mehd_data); + } + + /* set duration in the segment info */ + gst_qtdemux_get_duration (qtdemux, &duration); + if (duration) + gst_segment_set_duration (&qtdemux->segment, GST_FORMAT_TIME, duration); + gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux)); /* find and push tags, we do this after adding the pads so we can push the