X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=subprojects%2Fgst-plugins-good%2Fgst%2Fisomp4%2Fqtdemux.c;h=b20c25565640bd84a999fefd4040e772aff5df48;hb=cab020b4cb5418359c13409a115c844b80d9c06f;hp=a1a2b918a719009bcceb2c6719871c76193bdd9d;hpb=a9d9189aa28e913897c4ee2815c8602b7314859b;p=platform%2Fupstream%2Fgstreamer.git diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c index a1a2b91..b20c255 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c @@ -50,7 +50,7 @@ #include "config.h" #endif -#include "gst/gst-i18n-plugin.h" +#include #include #include @@ -70,6 +70,7 @@ #include "qtpalette.h" #include "qtdemux_tags.h" #include "qtdemux_tree.h" +#include "qtdemux-webvtt.h" #include #include @@ -288,6 +289,12 @@ GST_STATIC_PAD_TEMPLATE ("subtitle_%u", GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); +static GstStaticPadTemplate gst_qtdemux_metasrc_template = +GST_STATIC_PAD_TEMPLATE ("meta_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + #define gst_qtdemux_parent_class parent_class G_DEFINE_TYPE (GstQTDemux, gst_qtdemux, GST_TYPE_ELEMENT); GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qtdemux, "qtdemux", @@ -350,6 +357,9 @@ static GstCaps *qtdemux_audio_caps (GstQTDemux * qtdemux, static GstCaps *qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * data, gchar ** codec_name); +static GstCaps *qtdemux_meta_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * data, + gchar ** codec_name); static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * stsd_entry_data, gchar ** codec_name); @@ -382,6 +392,8 @@ static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux, const gchar * id); static void qtdemux_gst_structure_free (GstStructure * gststructure); static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard); +static void qtdemux_clear_protection_events_on_all_streams (GstQTDemux * + qtdemux); static void gst_qtdemux_class_init (GstQTDemuxClass * klass) @@ -472,9 +484,8 @@ gst_qtdemux_dispose (GObject * object) } gst_tag_list_unref (qtdemux->tag_list); gst_flow_combiner_free (qtdemux->flowcombiner); - g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref, - NULL); - g_queue_clear (&qtdemux->protection_event_queue); + g_queue_clear_full (&qtdemux->protection_event_queue, + (GDestroyNotify) gst_event_unref); g_free (qtdemux->cenc_aux_info_sizes); qtdemux->cenc_aux_info_sizes = NULL; @@ -1849,7 +1860,7 @@ _create_stream (GstQTDemux * demux, guint32 track_id) stream->discont = TRUE; /* we enable clipping for raw audio/video streams */ stream->need_clip = FALSE; - stream->need_process = FALSE; + stream->process_func = NULL; stream->segment_index = -1; stream->time_position = 0; stream->sample_index = -1; @@ -1975,7 +1986,6 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) gint i; GST_DEBUG_OBJECT (qtdemux, "Resetting demux"); - gst_pad_stop_task (qtdemux->sinkpad); if (hard || qtdemux->upstream_format_is_time) { qtdemux->state = QTDEMUX_STATE_INITIAL; @@ -2030,9 +2040,8 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) qtdemux->have_group_id = FALSE; qtdemux->group_id = G_MAXUINT; - g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref, - NULL); - g_queue_clear (&qtdemux->protection_event_queue); + g_queue_clear_full (&qtdemux->protection_event_queue, + (GDestroyNotify) gst_event_unref); qtdemux->received_seek = FALSE; qtdemux->first_moof_already_parsed = FALSE; @@ -2050,13 +2059,16 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) qtdemux->n_video_streams = 0; qtdemux->n_audio_streams = 0; qtdemux->n_sub_streams = 0; + qtdemux->n_meta_streams = 0; qtdemux->exposed = FALSE; qtdemux->fragmented = FALSE; qtdemux->mss_mode = FALSE; gst_caps_replace (&qtdemux->media_caps, NULL); qtdemux->timescale = 0; qtdemux->got_moov = FALSE; + qtdemux->start_utc_time = GST_CLOCK_TIME_NONE; qtdemux->cenc_aux_info_offset = 0; + g_free (qtdemux->cenc_aux_info_sizes); qtdemux->cenc_aux_info_sizes = NULL; qtdemux->cenc_aux_sample_count = 0; if (qtdemux->protection_system_ids) { @@ -2087,6 +2099,15 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) } } +static void +qtdemux_clear_protection_events_on_all_streams (GstQTDemux * qtdemux) +{ + for (unsigned i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) { + QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i); + g_queue_clear_full (&stream->protection_scheme_event_queue, + (GDestroyNotify) gst_event_unref); + } +} /* Maps the @segment to the qt edts internal segments and pushes * the corresponding segment event. @@ -2562,9 +2583,8 @@ gst_qtdemux_stream_clear (QtDemuxStream * stream) } stream->protection_scheme_type = 0; stream->protection_scheme_version = 0; - g_queue_foreach (&stream->protection_scheme_event_queue, - (GFunc) gst_event_unref, NULL); - g_queue_clear (&stream->protection_scheme_event_queue); + g_queue_clear_full (&stream->protection_scheme_event_queue, + (GDestroyNotify) gst_event_unref); gst_qtdemux_stream_flush_segments_data (stream); gst_qtdemux_stream_flush_samples_data (stream); } @@ -2671,14 +2691,56 @@ qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length) /* only consider at least a sufficiently complete ftyp atom */ if (length >= 20) { GstBuffer *buf; + guint32 minor_version; + const guint8 *p; qtdemux->major_brand = QT_FOURCC (buffer + 8); - GST_DEBUG_OBJECT (qtdemux, "major brand: %" GST_FOURCC_FORMAT, + GST_DEBUG_OBJECT (qtdemux, "ftyp major brand: %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (qtdemux->major_brand)); + minor_version = QT_UINT32 (buffer + 12); + GST_DEBUG_OBJECT (qtdemux, "ftyp minor version: %u", minor_version); if (qtdemux->comp_brands) gst_buffer_unref (qtdemux->comp_brands); buf = qtdemux->comp_brands = gst_buffer_new_and_alloc (length - 16); gst_buffer_fill (buf, 0, buffer + 16, length - 16); + + p = buffer + 16; + length = length - 16; + while (length > 0) { + GST_DEBUG_OBJECT (qtdemux, "ftyp compatible brand: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (QT_FOURCC (p))); + length -= 4; + p += 4; + } + } +} + +static void +qtdemux_parse_styp (GstQTDemux * qtdemux, const guint8 * buffer, gint length) +{ + /* only consider at least a sufficiently complete styp atom */ + if (length >= 20) { + GstBuffer *buf; + guint32 major_brand; + guint32 minor_version; + const guint8 *p; + + major_brand = QT_FOURCC (buffer + 8); + GST_DEBUG_OBJECT (qtdemux, "styp major brand: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (major_brand)); + minor_version = QT_UINT32 (buffer + 12); + GST_DEBUG_OBJECT (qtdemux, "styp minor version: %u", minor_version); + buf = qtdemux->comp_brands = gst_buffer_new_and_alloc (length - 16); + gst_buffer_fill (buf, 0, buffer + 16, length - 16); + + p = buffer + 16; + length = length - 16; + while (length > 0) { + GST_DEBUG_OBJECT (qtdemux, "styp compatible brand: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (QT_FOURCC (p))); + length -= 4; + p += 4; + } } } @@ -2789,8 +2851,13 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length, return; } - gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, - G_TYPE_STRING, &system_id, NULL); + if (!gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, + G_TYPE_STRING, &system_id, NULL)) { + GST_WARNING_OBJECT (qtdemux, "%s field not present in caps", + GST_PROTECTION_SYSTEM_ID_CAPS_FIELD); + return; + } + gst_qtdemux_append_protection_system_id (qtdemux, system_id); stream->protected = TRUE; @@ -3001,9 +3068,60 @@ qtdemux_parse_sidx (GstQTDemux * qtdemux, const guint8 * buffer, gint length) gst_isoff_qt_sidx_parser_clear (&sidx_parser); } +static void +qtdemux_parse_cstb (GstQTDemux * qtdemux, GstByteReader * data) +{ + guint64 start_time; + guint32 entry_count; + + GST_DEBUG_OBJECT (qtdemux, "Parsing CorrectStartTime box"); + + qtdemux->start_utc_time = GST_CLOCK_TIME_NONE; + + if (gst_byte_reader_get_remaining (data) < 4) { + GST_WARNING_OBJECT (qtdemux, "Too small CorrectStartTime box"); + return; + } + + entry_count = gst_byte_reader_get_uint32_be_unchecked (data); + if (entry_count == 0) + return; + + /* XXX: We assume that all start times are the same as different start times + * would violate the MP4 synchronization model, so we just take the first + * one here and apply it to all tracks. + */ + + if (gst_byte_reader_get_remaining (data) < entry_count * 12) { + GST_WARNING_OBJECT (qtdemux, "Too small CorrectStartTime box"); + return; + } + + /* Skip track id */ + gst_byte_reader_skip_unchecked (data, 4); + + /* In 100ns intervals */ + start_time = gst_byte_reader_get_uint64_be_unchecked (data); + + /* Convert from Jan 1 1601 to Jan 1 1970 */ + if (start_time < 11644473600 * G_GUINT64_CONSTANT (10000000)) { + GST_WARNING_OBJECT (qtdemux, "Start UTC time before UNIX epoch"); + return; + } + start_time -= 11644473600 * G_GUINT64_CONSTANT (10000000); + + /* Convert to GstClockTime */ + start_time *= 100; + + GST_DEBUG_OBJECT (qtdemux, "Start UTC time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time)); + + qtdemux->start_utc_time = start_time; +} + /* caller verifies at least 8 bytes in buf */ static void -extract_initial_length_and_fourcc (const guint8 * data, guint size, +extract_initial_length_and_fourcc (const guint8 * data, gsize size, guint64 * plength, guint32 * pfourcc) { guint64 length; @@ -3209,6 +3327,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; @@ -3216,6 +3335,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 ", " @@ -3235,7 +3355,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; @@ -3357,33 +3477,22 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, GST_INFO_OBJECT (stream->pad, "first sample ts %" GST_TIME_FORMAT, GST_TIME_ARGS (gst_ts)); } else { - /* subsequent fragments extend stream */ - timestamp = - stream->samples[stream->n_samples - 1].timestamp + - stream->samples[stream->n_samples - 1].duration; - - /* If this is a GST_FORMAT_BYTES stream and there's a significant - * difference (1 sec.) between decode_ts and timestamp, prefer the - * former */ - if (has_tfdt && !qtdemux->upstream_format_is_time - && ABSDIFF (decode_ts, timestamp) > - MAX (stream->duration_last_moof / 2, - GSTTIME_TO_QTSTREAMTIME (stream, GST_SECOND))) { - GST_INFO_OBJECT (qtdemux, - "decode_ts (%" GST_TIME_FORMAT ") and timestamp (%" GST_TIME_FORMAT - ") are significantly different (more than %" GST_TIME_FORMAT - "), using decode_ts", - GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, decode_ts)), - GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, timestamp)), - GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, - MAX (stream->duration_last_moof / 2, - GSTTIME_TO_QTSTREAMTIME (stream, GST_SECOND))))); + /* If this is a GST_FORMAT_BYTES stream and we have a tfdt then use it + * instead of the sum of sample durations */ + if (has_tfdt && !qtdemux->upstream_format_is_time) { timestamp = decode_ts; + gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); + GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT + " (using tfdt)", GST_TIME_ARGS (gst_ts)); + } else { + /* subsequent fragments extend stream */ + timestamp = + stream->samples[stream->n_samples - 1].timestamp + + stream->samples[stream->n_samples - 1].duration; + gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); + GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT + " (extends previous samples)", GST_TIME_ARGS (gst_ts)); } - - gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); - GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT - " (extends previous samples)", GST_TIME_ARGS (gst_ts)); } } @@ -3391,7 +3500,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) { @@ -3415,8 +3525,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; } @@ -3436,8 +3555,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)); @@ -3547,11 +3681,13 @@ qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, goto invalid_track; /* obtain stream defaults */ - qtdemux_parse_trex (qtdemux, *stream, - default_sample_duration, default_sample_size, default_sample_flags); + if (qtdemux_parse_trex (qtdemux, *stream, + default_sample_duration, default_sample_size, default_sample_flags)) { - (*stream)->stsd_sample_description_id = - (*stream)->def_sample_description_index - 1; + /* Default sample description index is only valid if trex parsing succeeded */ + (*stream)->stsd_sample_description_id = + (*stream)->def_sample_description_index - 1; + } if (flags & TF_SAMPLE_DESCRIPTION_INDEX) { guint32 sample_description_index; @@ -4126,6 +4262,8 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, /* iterate all siblings */ trun_node = qtdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun, &trun_data); + /* don't use tfdt for subsequent trun as it only refers to the first */ + tfdt_node = NULL; } uuid_node = qtdemux_tree_get_child_by_type (traf_node, FOURCC_uuid); @@ -4151,6 +4289,10 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, /* parse any protection system info */ pssh_node = qtdemux_tree_get_child_by_type (moof_node, FOURCC_pssh); + if (pssh_node) { + /* Unref old protection events if we are going to receive new ones. */ + qtdemux_clear_protection_events_on_all_streams (qtdemux); + } while (pssh_node) { GST_LOG_OBJECT (qtdemux, "Parsing pssh box."); qtdemux_parse_pssh (qtdemux, pssh_node); @@ -4534,8 +4676,8 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux) GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, (_("This file is incomplete and cannot be played.")), ("We got less than expected (received %" G_GSIZE_FORMAT - ", wanted %u, offset %" G_GUINT64_FORMAT ")", map.size, - (guint) length, cur_offset)); + ", wanted %" G_GUINT64_FORMAT ", offset %" G_GUINT64_FORMAT ")", + map.size, length, cur_offset)); gst_buffer_unmap (moov, &map); gst_buffer_unref (moov); ret = GST_FLOW_ERROR; @@ -4575,6 +4717,20 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux) gst_buffer_unref (ftyp); break; } + case FOURCC_styp: + { + GstBuffer *styp = NULL; + + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &styp); + if (ret != GST_FLOW_OK) + goto beach; + qtdemux->offset += length; + gst_buffer_map (styp, &map, GST_MAP_READ); + qtdemux_parse_styp (qtdemux, map.data, map.size); + gst_buffer_unmap (styp, &map); + gst_buffer_unref (styp); + break; + } case FOURCC_uuid: { GstBuffer *uuid = NULL; @@ -4603,6 +4759,34 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux) gst_buffer_unref (sidx); break; } + case FOURCC_meta: + { + GstBuffer *meta = NULL; + GNode *node, *child; + GstByteReader child_data; + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &meta); + if (ret != GST_FLOW_OK) + goto beach; + qtdemux->offset += length; + gst_buffer_map (meta, &map, GST_MAP_READ); + + node = g_node_new (map.data); + + qtdemux_parse_node (qtdemux, node, map.data, map.size); + + /* Parse ONVIF Export File Format CorrectStartTime box if available */ + if ((child = + qtdemux_tree_get_child_by_type_full (node, FOURCC_cstb, + &child_data))) { + qtdemux_parse_cstb (qtdemux, &child_data); + } + + g_node_destroy (node); + + gst_buffer_unmap (meta, &map); + gst_buffer_unref (meta); + break; + } default: { GstBuffer *unknown = NULL; @@ -4939,8 +5123,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; @@ -5669,59 +5856,89 @@ invalid_cdat: return NULL; } -/* the input buffer metadata must be writable, +/* Handle Closed Caption sample buffers. + * The input buffer metadata must be writable, * but time/duration etc not yet set and need not be preserved */ static GstBuffer * -gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream, +gst_qtdemux_process_buffer_clcp (GstQTDemux * qtdemux, QtDemuxStream * stream, GstBuffer * buf) { + GstBuffer *outbuf = NULL; GstMapInfo map; - guint nsize = 0; - gchar *str; + guint8 *cc; + gsize cclen = 0; - /* not many cases for now */ - if (G_UNLIKELY (CUR_STREAM (stream)->fourcc == FOURCC_mp4s)) { - /* send a one time dvd clut event */ - if (stream->pending_event && stream->pad) - gst_pad_push_event (stream->pad, stream->pending_event); - stream->pending_event = NULL; + gst_buffer_map (buf, &map, GST_MAP_READ); + + /* empty buffer is sent to terminate previous subtitle */ + if (map.size <= 2) { + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + return NULL; } - if (G_UNLIKELY (stream->subtype != FOURCC_text - && stream->subtype != FOURCC_sbtl && - stream->subtype != FOURCC_subp && stream->subtype != FOURCC_clcp)) { - return buf; + /* For closed caption, we need to extract the information from the + * [cdat],[cdt2] or [ccdp] atom */ + cc = extract_cc_from_data (stream, map.data, map.size, &cclen); + gst_buffer_unmap (buf, &map); + if (cc) { + outbuf = _gst_buffer_new_wrapped (cc, cclen, g_free); + gst_buffer_copy_into (outbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1); + } else { + /* Conversion failed or there's nothing */ } + gst_buffer_unref (buf); - gst_buffer_map (buf, &map, GST_MAP_READ); + return outbuf; +} + +/* DVD subpicture specific sample handling. + * the input buffer metadata must be writable, + * but time/duration etc not yet set and need not be preserved */ +static GstBuffer * +gst_qtdemux_process_buffer_dvd (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstBuffer * buf) +{ + /* send a one time dvd clut event */ + if (stream->pending_event && stream->pad) + gst_pad_push_event (stream->pad, stream->pending_event); + stream->pending_event = NULL; /* empty buffer is sent to terminate previous subtitle */ - if (map.size <= 2) { - gst_buffer_unmap (buf, &map); + if (gst_buffer_get_size (buf) <= 2) { gst_buffer_unref (buf); return NULL; } - if (stream->subtype == FOURCC_subp) { - /* That's all the processing needed for subpictures */ - gst_buffer_unmap (buf, &map); + + /* That's all the processing needed for subpictures */ + return buf; +} + +/* Timed text formats + * the input buffer metadata must be writable, + * but time/duration etc not yet set and need not be preserved */ +static GstBuffer * +gst_qtdemux_process_buffer_text (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstBuffer * buf) +{ + GstBuffer *outbuf = NULL; + GstMapInfo map; + guint nsize = 0; + gchar *str; + + /* not many cases for now */ + if (G_UNLIKELY (stream->subtype != FOURCC_text && + stream->subtype != FOURCC_sbtl)) { return buf; } - if (stream->subtype == FOURCC_clcp) { - guint8 *cc; - gsize cclen = 0; - /* For closed caption, we need to extract the information from the - * [cdat],[cdt2] or [ccdp] atom */ - cc = extract_cc_from_data (stream, map.data, map.size, &cclen); + gst_buffer_map (buf, &map, GST_MAP_READ); + + /* empty buffer is sent to terminate previous subtitle */ + if (map.size <= 2) { gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); - if (cc) { - buf = _gst_buffer_new_wrapped (cc, cclen, g_free); - } else { - /* Conversion failed or there's nothing */ - buf = NULL; - } - return buf; + return NULL; } nsize = GST_READ_UINT16_BE (map.data); @@ -5734,18 +5951,53 @@ gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream, * no other encoding expected */ str = gst_tag_freeform_string_to_utf8 ((gchar *) map.data + 2, nsize, NULL); gst_buffer_unmap (buf, &map); + if (str) { - gst_buffer_unref (buf); - buf = _gst_buffer_new_wrapped (str, strlen (str), g_free); + outbuf = _gst_buffer_new_wrapped (str, strlen (str), g_free); + gst_buffer_copy_into (outbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1); } else { /* this should not really happen unless the subtitle is corrupted */ - gst_buffer_unref (buf); - buf = NULL; } + gst_buffer_unref (buf); /* FIXME ? convert optional subsequent style info to markup */ - return buf; + return outbuf; +} + +/* WebVTT sample handling according to 14496-30 */ +static GstBuffer * +gst_qtdemux_process_buffer_wvtt (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstBuffer * buf) +{ + GstBuffer *outbuf = NULL; + GstMapInfo map; + + if (!gst_buffer_map (buf, &map, GST_MAP_READ)) { + g_assert_not_reached (); /* The buffer must be mappable */ + } + + if (qtdemux_webvtt_is_empty (qtdemux, map.data, map.size)) { + GstEvent *gap = NULL; + /* Push a gap event */ + stream->segment.position = GST_BUFFER_PTS (buf); + gap = + gst_event_new_gap (stream->segment.position, GST_BUFFER_DURATION (buf)); + gst_pad_push_event (stream->pad, gap); + + if (GST_BUFFER_DURATION_IS_VALID (buf)) + stream->segment.position += GST_BUFFER_DURATION (buf); + } else { + outbuf = + qtdemux_webvtt_decode (qtdemux, GST_BUFFER_PTS (buf), + GST_BUFFER_DURATION (buf), map.data, map.size); + gst_buffer_copy_into (outbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1); + } + + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + + return outbuf; } static GstFlowReturn @@ -6044,11 +6296,13 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, /* we're going to modify the metadata */ buf = gst_buffer_make_writable (buf); - if (G_UNLIKELY (stream->need_process)) - buf = gst_qtdemux_process_buffer (qtdemux, stream, buf); - - if (!buf) { - goto exit; + if (qtdemux->start_utc_time != GST_CLOCK_TIME_NONE) { + static GstStaticCaps unix_caps = GST_STATIC_CAPS ("timestamp/x-unix"); + GstCaps *caps = gst_static_caps_get (&unix_caps); + gst_buffer_add_reference_timestamp_meta (buf, caps, + pts + qtdemux->start_utc_time - stream->cslg_shift, + GST_CLOCK_TIME_NONE); + gst_caps_unref (caps); } GST_BUFFER_DTS (buf) = dts; @@ -6057,6 +6311,13 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, GST_BUFFER_OFFSET (buf) = -1; GST_BUFFER_OFFSET_END (buf) = -1; + if (G_UNLIKELY (stream->process_func)) + buf = stream->process_func (qtdemux, stream, buf); + + if (!buf) { + goto exit; + } + if (!keyframe) { GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); stream->on_keyframe = FALSE; @@ -6277,37 +6538,63 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) goto eos_stream; } - /* gap events for subtitle streams */ - for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) { + /* fetch info for the current sample of this stream */ + if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, target_stream, + &empty, &offset, &sample_size, &dts, &pts, &duration, &keyframe))) + goto eos_stream; + + /* Send catche-up GAP event for each other stream if required. + * This logic will be applied only for positive rate */ + for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux) && + qtdemux->segment.rate >= 0; i++) { stream = QTDEMUX_NTH_STREAM (qtdemux, i); + + if (stream == target_stream || + !GST_CLOCK_TIME_IS_VALID (stream->segment.stop) || + !GST_CLOCK_TIME_IS_VALID (stream->segment.position)) + continue; + if (stream->pad) { GstClockTime gap_threshold; + /* kind of running time with offset segment.base and segment.start */ + GstClockTime pseudo_target_time = target_stream->segment.base; + GstClockTime pseudo_cur_time = stream->segment.base; + + /* make sure positive offset, segment.position can be smallr than + * segment.start for some reasons */ + if (target_stream->segment.position >= target_stream->segment.start) { + pseudo_target_time += + (target_stream->segment.position - target_stream->segment.start); + } + + if (stream->segment.position >= stream->segment.start) + pseudo_cur_time += (stream->segment.position - stream->segment.start); /* Only send gap events on non-subtitle streams if lagging way behind. */ if (stream->subtype == FOURCC_subp - || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl) + || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl || + stream->subtype == FOURCC_wvtt) gap_threshold = 1 * GST_SECOND; else gap_threshold = 3 * GST_SECOND; /* send gap events until the stream catches up */ /* gaps can only be sent after segment is activated (segment.stop is no longer -1) */ - while (GST_CLOCK_TIME_IS_VALID (stream->segment.stop) && - GST_CLOCK_TIME_IS_VALID (stream->segment.position) && - stream->segment.position + gap_threshold < min_time) { + while (GST_CLOCK_TIME_IS_VALID (stream->segment.position) && + pseudo_cur_time < (G_MAXUINT64 - gap_threshold) && + pseudo_cur_time + gap_threshold < pseudo_target_time) { GstEvent *gap = gst_event_new_gap (stream->segment.position, gap_threshold); + GST_LOG_OBJECT (stream->pad, "Sending %" GST_PTR_FORMAT, gap); + gst_pad_push_event (stream->pad, gap); stream->segment.position += gap_threshold; + pseudo_cur_time += gap_threshold; } } } stream = target_stream; - /* fetch info for the current sample of this stream */ - if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, stream, &empty, - &offset, &sample_size, &dts, &pts, &duration, &keyframe))) - goto eos_stream; gst_qtdemux_stream_check_and_change_stsd_index (qtdemux, stream); if (stream->new_caps) { @@ -7162,6 +7449,7 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) if (demux->moov_node) g_node_destroy (demux->moov_node); demux->moov_node = NULL; + demux->start_utc_time = GST_CLOCK_TIME_NONE; } demux->last_moov_offset = demux->offset; @@ -7284,6 +7572,21 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) } else if (fourcc == FOURCC_sidx) { GST_DEBUG_OBJECT (demux, "Parsing [sidx]"); qtdemux_parse_sidx (demux, data, demux->neededbytes); + } else if (fourcc == FOURCC_meta) { + GNode *node, *child; + GstByteReader child_data; + + node = g_node_new ((gpointer) data); + qtdemux_parse_node (demux, node, data, demux->neededbytes); + + /* Parse ONVIF Export File Format CorrectStartTime box if available */ + if ((child = + qtdemux_tree_get_child_by_type_full (node, FOURCC_cstb, + &child_data))) { + qtdemux_parse_cstb (demux, &child_data); + } + + g_node_destroy (node); } else { switch (fourcc) { case FOURCC_styp: @@ -7716,10 +8019,16 @@ qtdemux_inflate (void *z_buffer, guint z_length, guint * length) break; } + if (*length > G_MAXUINT - 4096 || *length > QTDEMUX_MAX_SAMPLE_INDEX_SIZE) { + GST_WARNING ("too big decompressed data"); + ret = Z_MEM_ERROR; + break; + } + *length += 4096; buffer = (guint8 *) g_realloc (buffer, *length); z.next_out = (Bytef *) (buffer + z.total_out); - z.avail_out += 4096; + z.avail_out += *length - z.total_out; } while (z.avail_in > 0); if (ret != Z_STREAM_END) { @@ -8314,10 +8623,10 @@ gst_qtdemux_request_protection_context (GstQTDemux * qtdemux, g_value_init (&event_list, GST_TYPE_LIST); for (; walk; walk = g_list_previous (walk)) { - GValue *event_value = g_new0 (GValue, 1); - g_value_init (event_value, GST_TYPE_EVENT); - g_value_set_boxed (event_value, walk->data); - gst_value_list_append_and_take_value (&event_list, event_value); + GValue event_value = G_VALUE_INIT; + g_value_init (&event_value, GST_TYPE_EVENT); + g_value_set_boxed (&event_value, walk->data); + gst_value_list_append_and_take_value (&event_list, &event_value); } /* 2a) Query downstream with GST_QUERY_CONTEXT for the context and @@ -8693,6 +9002,7 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) } if (stream->pad) { + gboolean forward_collection = FALSE; GstCaps *prev_caps = NULL; GST_PAD_ELEMENT_PRIVATE (stream->pad) = stream; @@ -8710,8 +9020,6 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) } } - GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT, - CUR_STREAM (stream)->caps); if (stream->new_stream) { GstEvent *event; GstStreamFlags stream_flags = GST_STREAM_FLAG_NONE; @@ -8744,6 +9052,8 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) } gst_event_set_stream_flags (event, stream_flags); gst_pad_push_event (stream->pad, event); + + forward_collection = TRUE; } prev_caps = gst_pad_get_current_caps (stream->pad); @@ -8764,6 +9074,14 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) if (prev_caps) gst_caps_unref (prev_caps); stream->new_caps = FALSE; + + if (forward_collection) { + /* Forward upstream collection and selection if any */ + GstEvent *upstream_event = gst_pad_get_sticky_event (qtdemux->sinkpad, + GST_EVENT_STREAM_COLLECTION, 0); + if (upstream_event) + gst_pad_push_event (stream->pad, upstream_event); + } } return TRUE; } @@ -8827,7 +9145,7 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux, GST_DEBUG_OBJECT (qtdemux, "stream type, not creating pad"); } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt - || stream->subtype == FOURCC_clcp) { + || stream->subtype == FOURCC_clcp || stream->subtype == FOURCC_wvtt) { gchar *name = g_strdup_printf ("subtitle_%u", qtdemux->n_sub_streams); stream->pad = @@ -8840,6 +9158,19 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux, goto done; } qtdemux->n_sub_streams++; + } else if (stream->subtype == FOURCC_meta) { + gchar *name = g_strdup_printf ("meta_%u", qtdemux->n_meta_streams); + + stream->pad = + gst_pad_new_from_static_template (&gst_qtdemux_metasrc_template, name); + g_free (name); + if (!gst_qtdemux_configure_stream (qtdemux, stream)) { + gst_object_unref (stream->pad); + stream->pad = NULL; + ret = FALSE; + goto done; + } + qtdemux->n_meta_streams++; } else if (CUR_STREAM (stream)->caps) { gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams); @@ -9301,12 +9632,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; @@ -9318,10 +9655,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; @@ -9331,6 +9688,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++) { @@ -9340,35 +9699,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 @@ -9790,6 +10187,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++; @@ -9870,8 +10272,8 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, stream->segments = NULL; if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) { GNode *elst; - gint n_segments; - gint segment_number, entry_size; + guint n_segments; + guint segment_number, entry_size; guint64 time; GstClockTime stime; const guint8 *buffer; @@ -10579,7 +10981,7 @@ qtdemux_parse_stereo_svmi_atom (GstQTDemux * qtdemux, QtDemuxStream * stream, /*parse svmi header if existing */ svmi = qtdemux_tree_get_child_by_type (stbl, FOURCC_svmi); if (svmi) { - guint len = QT_UINT32 ((guint8 *) svmi->data); + guint32 len = QT_UINT32 ((guint8 *) svmi->data); guint32 version = QT_UINT32 ((guint8 *) svmi->data + 8); if (!version) { GstVideoMultiviewMode mode = GST_VIDEO_MULTIVIEW_MODE_NONE; @@ -10986,7 +11388,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) break; } } else { - gint i, j, start, end; + guint i, j, start, end; if (len < 94) goto corrupt_file; @@ -11102,7 +11504,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (pasp) { const guint8 *pasp_data = (const guint8 *) pasp->data; - gint len = QT_UINT32 (pasp_data); + guint len = QT_UINT32 (pasp_data); if (len == 16) { CUR_STREAM (stream)->par_w = QT_UINT32 (pasp_data + 8); @@ -11118,7 +11520,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (fiel) { const guint8 *fiel_data = (const guint8 *) fiel->data; - gint len = QT_UINT32 (fiel_data); + guint len = QT_UINT32 (fiel_data); if (len == 10) { CUR_STREAM (stream)->interlace_mode = GST_READ_UINT8 (fiel_data + 8); @@ -11128,7 +11530,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (colr) { const guint8 *colr_data = (const guint8 *) colr->data; - gint len = QT_UINT32 (colr_data); + guint len = QT_UINT32 (colr_data); if (len == 19 || len == 18) { guint32 color_type = GST_READ_UINT32_LE (colr_data + 8); @@ -11165,14 +11567,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) case FOURCC_avc1: case FOURCC_avc3: { - gint len = QT_UINT32 (stsd_entry_data) - 0x56; + guint len = QT_UINT32 (stsd_entry_data); + len = len <= 0x56 ? 0 : len - 0x56; const guint8 *avc_data = stsd_entry_data + 0x56; /* find avcC */ while (len >= 0x8) { - gint size; + guint size; - if (QT_UINT32 (avc_data) <= len) + if (QT_UINT32 (avc_data) <= 0x8) + size = 0; + else if (QT_UINT32 (avc_data) <= len) size = QT_UINT32 (avc_data) - 0x8; else size = len - 0x8; @@ -11279,14 +11684,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) case FOURCC_dvh1: case FOURCC_dvhe: { - gint len = QT_UINT32 (stsd_entry_data) - 0x56; + guint len = QT_UINT32 (stsd_entry_data); + len = len <= 0x56 ? 0 : len - 0x56; const guint8 *hevc_data = stsd_entry_data + 0x56; /* find hevc */ while (len >= 0x8) { - gint size; + guint size; - if (QT_UINT32 (hevc_data) <= len) + if (QT_UINT32 (hevc_data) <= 0x8) + size = 0; + else if (QT_UINT32 (hevc_data) <= len) size = QT_UINT32 (hevc_data) - 0x8; else size = len - 0x8; @@ -11342,7 +11750,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (glbl) { guint8 *data; GstBuffer *buf; - gint len; + guint len; GST_DEBUG_OBJECT (qtdemux, "found glbl data in stsd"); data = glbl->data; @@ -11526,7 +11934,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) /* add codec_data if provided */ if (prefix) { GstBuffer *buf; - gint len; + guint len; GST_DEBUG_OBJECT (qtdemux, "found prefix data in stsd"); data = prefix->data; @@ -11548,7 +11956,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GstBuffer *buf; GstBuffer *seqh = NULL; const guint8 *gamma_data = NULL; - gint len = QT_UINT32 (stsd_data); /* FIXME review - why put the whole stsd in codec data? */ + guint len = QT_UINT32 (stsd_data); /* FIXME review - why put the whole stsd in codec data? */ qtdemux_parse_svq3_stsd_data (qtdemux, stsd_entry_data, &gamma_data, &seqh); @@ -11700,14 +12108,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } case FOURCC_vc_1: { - gint len = QT_UINT32 (stsd_entry_data) - 0x56; + guint len = QT_UINT32 (stsd_entry_data); + len = len <= 0x56 ? 0 : len - 0x56; const guint8 *vc1_data = stsd_entry_data + 0x56; /* find dvc1 */ while (len >= 8) { - gint size; + guint size; - if (QT_UINT32 (vc1_data) <= len) + if (QT_UINT32 (vc1_data) <= 8) + size = 0; + else if (QT_UINT32 (vc1_data) <= len) size = QT_UINT32 (vc1_data) - 8; else size = len - 8; @@ -11739,14 +12150,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } case FOURCC_av01: { - gint len = QT_UINT32 (stsd_entry_data) - 0x56; + guint len = QT_UINT32 (stsd_entry_data); + len = len <= 0x56 ? 0 : len - 0x56; const guint8 *av1_data = stsd_entry_data + 0x56; /* find av1C */ while (len >= 0x8) { - gint size; + guint size; - if (QT_UINT32 (av1_data) <= len) + if (QT_UINT32 (av1_data) <= 0x8) + size = 0; + else if (QT_UINT32 (av1_data) <= len) size = QT_UINT32 (av1_data) - 0x8; else size = len - 0x8; @@ -11818,14 +12232,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) * vp08, vp09, and vp10 fourcc. */ case FOURCC_vp09: { - gint len = QT_UINT32 (stsd_entry_data) - 0x56; + guint len = QT_UINT32 (stsd_entry_data); + len = len <= 0x56 ? 0 : len - 0x56; const guint8 *vpcc_data = stsd_entry_data + 0x56; /* find vpcC */ while (len >= 0x8) { - gint size; + guint size; - if (QT_UINT32 (vpcc_data) <= len) + if (QT_UINT32 (vpcc_data) <= 0x8) + size = 0; + else if (QT_UINT32 (vpcc_data) <= len) size = QT_UINT32 (vpcc_data) - 0x8; else size = len - 0x8; @@ -11973,7 +12390,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } else if (stream->subtype == FOURCC_soun) { GNode *wave; - int version, samplesize; + guint version, samplesize; guint16 compression_id; gboolean amrwb = FALSE; @@ -12288,7 +12705,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } case FOURCC_wma_: { - gint len = QT_UINT32 (stsd_entry_data) - offset; + guint len = QT_UINT32 (stsd_entry_data); + len = len <= offset ? 0 : len - offset; const guint8 *wfex_data = stsd_entry_data + offset; const gchar *codec_name = NULL; gint version = 1; @@ -12312,9 +12730,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) /* find wfex */ while (len >= 8) { - gint size; + guint size; - if (QT_UINT32 (wfex_data) <= len) + if (QT_UINT32 (wfex_data) <= 0x8) + size = 0; + else if (QT_UINT32 (wfex_data) <= len) size = QT_UINT32 (wfex_data) - 8; else size = len - 8; @@ -12395,7 +12815,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } case FOURCC_opus: { - const guint8 *opus_data; + const guint8 *dops_data; guint8 *channel_mapping = NULL; guint32 rate; guint8 channels; @@ -12404,23 +12824,28 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) guint8 coupled_count; guint8 i; - opus_data = stsd_entry_data; + version = GST_READ_UINT16_BE (stsd_entry_data + 16); + if (version == 1) + dops_data = stsd_entry_data + 51; + else + dops_data = stsd_entry_data + 35; - channels = GST_READ_UINT8 (opus_data + 45); - rate = GST_READ_UINT32_LE (opus_data + 48); - channel_mapping_family = GST_READ_UINT8 (opus_data + 54); - stream_count = GST_READ_UINT8 (opus_data + 55); - coupled_count = GST_READ_UINT8 (opus_data + 56); + channels = GST_READ_UINT8 (dops_data + 10); + rate = GST_READ_UINT32_LE (dops_data + 13); + channel_mapping_family = GST_READ_UINT8 (dops_data + 19); + stream_count = GST_READ_UINT8 (dops_data + 20); + coupled_count = GST_READ_UINT8 (dops_data + 21); if (channels > 0) { channel_mapping = g_malloc (channels * sizeof (guint8)); for (i = 0; i < channels; i++) - channel_mapping[i] = GST_READ_UINT8 (opus_data + i + 57); + channel_mapping[i] = GST_READ_UINT8 (dops_data + i + 22); } entry->caps = gst_codec_utils_opus_create_caps (rate, channels, channel_mapping_family, stream_count, coupled_count, channel_mapping); + g_free (channel_mapping); break; } default: @@ -12802,7 +13227,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) entry->sampled = TRUE; } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt - || stream->subtype == FOURCC_clcp) { + || stream->subtype == FOURCC_clcp || stream->subtype == FOURCC_wvtt) { entry->sampled = TRUE; entry->sparse = TRUE; @@ -12846,6 +13271,23 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GST_INFO_OBJECT (qtdemux, "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT, GST_FOURCC_ARGS (fourcc), entry->caps); + } else if (stream->subtype == FOURCC_meta) { + entry->sampled = TRUE; + entry->sparse = TRUE; + + entry->caps = + qtdemux_meta_caps (qtdemux, stream, entry, fourcc, stsd_entry_data, + &codec); + if (codec) { + gst_tag_list_add (stream->stream_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_CODEC, codec, NULL); + g_free (codec); + codec = NULL; + } + + GST_INFO_OBJECT (qtdemux, + "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT, + GST_FOURCC_ARGS (fourcc), entry->caps); } else { /* everything in 1 sample */ entry->sampled = TRUE; @@ -13179,7 +13621,8 @@ qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux, /* unset new_stream to prevent stream-start event, unless we are EOS in which * case we need to force one through */ - newstream->new_stream = GST_PAD_IS_EOS (newstream->pad); + newstream->new_stream = newstream->pad != NULL + && GST_PAD_IS_EOS (newstream->pad); return gst_qtdemux_configure_stream (qtdemux, newstream); } @@ -13671,6 +14114,10 @@ qtdemux_parse_tree (GstQTDemux * qtdemux) /* parse any protection system info */ pssh = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_pssh); + if (pssh) { + /* Unref old protection events if we are going to receive new ones. */ + qtdemux_clear_protection_events_on_all_streams (qtdemux); + } while (pssh) { GST_LOG_OBJECT (qtdemux, "Parsing pssh box."); qtdemux_parse_pssh (qtdemux, pssh); @@ -14388,6 +14835,23 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, "stream-format", G_TYPE_STRING, "avc3", "alignment", G_TYPE_STRING, "au", NULL); break; + case FOURCC_ai12: + case FOURCC_ai13: + case FOURCC_ai15: + case FOURCC_ai16: + case FOURCC_ai1p: + case FOURCC_ai1q: + case FOURCC_ai52: + case FOURCC_ai53: + case FOURCC_ai55: + case FOURCC_ai56: + case FOURCC_ai5p: + case FOURCC_ai5q: + _codec ("H.264 / AVC"); + caps = gst_caps_new_simple ("video/x-h264", + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "au", NULL); + break; case FOURCC_H265: case FOURCC_hvc1: case FOURCC_dvh1: @@ -14553,7 +15017,9 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, break; case FOURCC_av01: _codec ("AV1"); - caps = gst_caps_new_empty_simple ("video/x-av1"); + caps = gst_caps_new_simple ("video/x-av1", + "stream-format", G_TYPE_STRING, "obu-stream", + "alignment", G_TYPE_STRING, "tu", NULL); break; case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'): default: @@ -14947,7 +15413,7 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, case FOURCC_mp4s: _codec ("DVD subtitle"); caps = gst_caps_new_empty_simple ("subpicture/x-dvd"); - stream->need_process = TRUE; + stream->process_func = gst_qtdemux_process_buffer_dvd; break; case FOURCC_text: _codec ("Quicktime timed text"); @@ -14958,18 +15424,34 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING, "utf8", NULL); /* actual text piece needs to be extracted */ - stream->need_process = TRUE; + stream->process_func = gst_qtdemux_process_buffer_text; break; case FOURCC_stpp: _codec ("XML subtitles"); caps = gst_caps_new_empty_simple ("application/ttml+xml"); break; + case FOURCC_wvtt: + { + GstBuffer *buffer; + const gchar *buf = "WEBVTT\n\n"; + + _codec ("WebVTT subtitles"); + caps = gst_caps_new_empty_simple ("application/x-subtitle-vtt"); + stream->process_func = gst_qtdemux_process_buffer_wvtt; + + /* FIXME: Parse the vttC atom and get the entire WEBVTT header */ + buffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (buffer, 0, buf, 8); + stream->buffers = g_slist_append (stream->buffers, buffer); + + break; + } case FOURCC_c608: _codec ("CEA 608 Closed Caption"); caps = gst_caps_new_simple ("closedcaption/x-cea-608", "format", G_TYPE_STRING, "s334-1a", NULL); - stream->need_process = TRUE; + stream->process_func = gst_qtdemux_process_buffer_clcp; stream->need_split = TRUE; break; case FOURCC_c708: @@ -14977,7 +15459,7 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, caps = gst_caps_new_simple ("closedcaption/x-cea-708", "format", G_TYPE_STRING, "cdp", NULL); - stream->need_process = TRUE; + stream->process_func = gst_qtdemux_process_buffer_clcp; break; default: @@ -14990,6 +15472,63 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, } static GstCaps * +qtdemux_meta_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + QtDemuxStreamStsdEntry * entry, guint32 fourcc, + const guint8 * stsd_entry_data, gchar ** codec_name) +{ + GstCaps *caps = NULL; + + GST_DEBUG_OBJECT (qtdemux, "resolve fourcc 0x%08x", GUINT32_TO_BE (fourcc)); + + switch (fourcc) { + case FOURCC_metx:{ + gsize size = QT_UINT32 (stsd_entry_data); + GstByteReader reader = GST_BYTE_READER_INIT (stsd_entry_data, size); + const gchar *content_encoding; + const gchar *namespaces; + const gchar *schema_locations; + + if (!gst_byte_reader_skip (&reader, 8 + 6 + 2)) { + GST_WARNING_OBJECT (qtdemux, "Too short metx sample entry"); + break; + } + + if (!gst_byte_reader_get_string (&reader, &content_encoding) || + !gst_byte_reader_get_string (&reader, &namespaces) || + !gst_byte_reader_get_string (&reader, &schema_locations)) { + GST_WARNING_OBJECT (qtdemux, "Too short metx sample entry"); + break; + } + + if (strstr (namespaces, "http://www.onvif.org/ver10/schema") != 0) { + if (content_encoding == NULL || *content_encoding == '\0' + || g_ascii_strcasecmp (content_encoding, "xml") == 0) { + _codec ("ONVIF Timed XML MetaData"); + caps = + gst_caps_new_simple ("application/x-onvif-metadata", "parsed", + G_TYPE_BOOLEAN, TRUE, NULL); + } else { + GST_DEBUG_OBJECT (qtdemux, "Unknown content encoding: %s", + content_encoding); + } + } else { + GST_DEBUG_OBJECT (qtdemux, "Unknown metadata namespaces: %s", + namespaces); + } + + break; + } + default: + break; + } + + if (!caps) + caps = _get_unknown_codec_name ("meta", fourcc); + + return caps; +} + +static GstCaps * qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * stsd_entry_data, gchar ** codec_name)