#include "config.h"
#endif
-#include "gst/gst-i18n-plugin.h"
+#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <gst/base/base.h>
#include "qtpalette.h"
#include "qtdemux_tags.h"
#include "qtdemux_tree.h"
+#include "qtdemux-webvtt.h"
#include <stdlib.h>
#include <string.h>
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",
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);
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;
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;
qtdemux->cenc_aux_info_sizes = NULL;
qtdemux->cenc_aux_sample_count = 0;
/* 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;
+ }
}
}
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,
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;
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 ", "
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;
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) {
} 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;
}
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));
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;
/* 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);
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;
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;
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;
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);
* 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
/* 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;
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;
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) {
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) {
}
if (stream->pad) {
+ gboolean forward_collection = FALSE;
GstCaps *prev_caps = NULL;
GST_PAD_ELEMENT_PRIVATE (stream->pad) = 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;
}
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);
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;
}
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 =
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);
! !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;
/* 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;
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++) {
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
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++;
}
case FOURCC_opus:
{
- const guint8 *opus_data;
+ const guint8 *dops_data;
guint8 *channel_mapping = NULL;
guint32 rate;
guint8 channels;
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:
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;
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;
/* 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);
}
break;
case FOURCC_av01:
_codec ("AV1");
- caps = gst_caps_new_empty_simple ("video/x-av1");
+ caps = gst_caps_new_simple ("video/x-av1",
+ "alignment", G_TYPE_STRING, "tu", NULL);
break;
case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'):
default:
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");
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:
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:
}
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)