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)
}
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;
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;
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;
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) {
}
}
+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.
}
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);
}
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;
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;
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));
}
}
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;
/* 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);
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;
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;
/* we're going to modify the metadata */
buf = gst_buffer_make_writable (buf);
+ 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;
GST_BUFFER_PTS (buf) = pts;
GST_BUFFER_DURATION (buf) = duration;
/* 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.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);
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;
} 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:
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) {
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
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;
/*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;
break;
}
} else {
- gint i, j, start, end;
+ guint i, j, start, end;
if (len < 94)
goto corrupt_file;
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);
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);
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);
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;
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;
if (glbl) {
guint8 *data;
GstBuffer *buf;
- gint len;
+ guint len;
GST_DEBUG_OBJECT (qtdemux, "found glbl data in stsd");
data = glbl->data;
/* 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;
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);
}
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;
}
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;
* 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;
} else if (stream->subtype == FOURCC_soun) {
GNode *wave;
- int version, samplesize;
+ guint version, samplesize;
guint16 compression_id;
gboolean amrwb = FALSE;
}
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;
/* 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;
/* 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);
}
/* 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);
"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:
case FOURCC_av01:
_codec ("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'):
|| g_ascii_strcasecmp (content_encoding, "xml") == 0) {
_codec ("ONVIF Timed XML MetaData");
caps =
- gst_caps_new_simple ("application/x-onvif-metadata", "encoding",
- G_TYPE_STRING, "utf8", NULL);
+ 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);