diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c index f823ff1a730d9892e478c33961fdcf8361c8875b..b74f4ef150892544e62a8b202a429be0fb9bb4bd 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c @@ -3210,6 +3210,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; @@ -3217,6 +3218,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 ", " @@ -3236,7 +3238,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; @@ -3392,7 +3394,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) { @@ -3416,8 +3419,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; } @@ -3437,8 +3449,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)); @@ -4940,8 +4967,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; @@ -9368,12 +9398,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; @@ -9385,10 +9421,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; @@ -9398,6 +9454,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++) { @@ -9407,35 +9465,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 @@ -9857,6 +9953,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++; diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.h b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.h index 742915cb0144ea976b98598b817d818900444ef9..074cd671c904983e4f9a1cf1228d21845f81ac31 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.h +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.h @@ -477,8 +477,16 @@ struct _QtDemuxStream guint32 ctts_count; gint32 ctts_soffset; - /* cslg */ - guint32 cslg_shift; + /* cslg composition_to_dts_shift or based on the smallest negative + * composition time offset. + * + * This is unsigned because only negative composition time offsets / + * positive composition_to_dts_shift matter here. In all other cases, + * DTS/PTS can be inferred directly without ending up with PTS>DTS. + * + * See 14496-12 6.4 + */ + guint64 cslg_shift; /* fragmented */ gboolean parsed_trex; diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c index 25921dfe2e721050d5898705c19108968870b067..e061c46cab453060b1647a46993460cefbbd8d28 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c @@ -577,21 +577,37 @@ qtdemux_dump_ctts (GstQTDemux * qtdemux, GstByteReader * data, int depth) gboolean qtdemux_dump_cslg (GstQTDemux * qtdemux, GstByteReader * data, int depth) { - guint32 ver_flags = 0, shift = 0; - gint32 least_offset = 0, start_time = 0, end_time = 0; + guint32 ver_flags = 0; - if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || - !gst_byte_reader_get_uint32_be (data, &shift) || - !gst_byte_reader_get_int32_be (data, &least_offset) || - !gst_byte_reader_get_int32_be (data, &start_time) || - !gst_byte_reader_get_int32_be (data, &end_time)) + if (!gst_byte_reader_get_uint32_be (data, &ver_flags)) return FALSE; GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); - GST_LOG ("%*s shift: %u", depth, "", shift); - GST_LOG ("%*s least offset: %d", depth, "", least_offset); - GST_LOG ("%*s start time: %d", depth, "", start_time); - GST_LOG ("%*s end time: %d", depth, "", end_time); + + if (ver_flags >> 24 == 0) { + gint32 shift = 0, least_offset = 0, start_time = 0, end_time = 0; + if (!gst_byte_reader_get_int32_be (data, &shift) || + !gst_byte_reader_get_int32_be (data, &least_offset) || + !gst_byte_reader_get_int32_be (data, &start_time) || + !gst_byte_reader_get_int32_be (data, &end_time)) + return FALSE; + GST_LOG ("%*s shift: %d", depth, "", shift); + GST_LOG ("%*s least offset: %d", depth, "", least_offset); + GST_LOG ("%*s start time: %d", depth, "", start_time); + GST_LOG ("%*s end time: %d", depth, "", end_time); + } else { + gint64 shift = 0, least_offset = 0, start_time = 0, end_time = 0; + if (!gst_byte_reader_get_int64_be (data, &shift) || + !gst_byte_reader_get_int64_be (data, &least_offset) || + !gst_byte_reader_get_int64_be (data, &start_time) || + !gst_byte_reader_get_int64_be (data, &end_time)) + return FALSE; + + GST_LOG ("%*s shift: %" G_GINT64_FORMAT, depth, "", shift); + GST_LOG ("%*s least offset: %" G_GINT64_FORMAT, depth, "", least_offset); + GST_LOG ("%*s start time: %" G_GINT64_FORMAT, depth, "", start_time); + GST_LOG ("%*s end time: %" G_GINT64_FORMAT, depth, "", end_time); + } return TRUE; } @@ -812,7 +828,7 @@ qtdemux_dump_trun (GstQTDemux * qtdemux, GstByteReader * data, int depth) if (flags & TR_COMPOSITION_TIME_OFFSETS) { if (!gst_byte_reader_get_uint32_be (data, &composition_time_offsets)) return FALSE; - GST_TRACE ("%*s composition_time_offsets: %u", depth, "", + GST_TRACE ("%*s composition_time_offsets: %d", depth, "", composition_time_offsets); } }