#include <gst/tag/tag.h>
#include <gst/audio/audio.h>
#include <gst/video/video.h>
+#include <gst/riff/riff.h>
+#include <gst/pbutils/pbutils.h>
#include "qtatomparser.h"
#include "qtdemux_types.h"
#include "qtdemux.h"
#include "qtpalette.h"
-#include "gst/riff/riff-media.h"
-#include "gst/riff/riff-read.h"
-
-#include <gst/pbutils/pbutils.h>
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define QTDEMUX_MAX_ATOM_SIZE (25*1024*1024)
/* if the sample index is larger than this, something is likely wrong */
-#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (50*1024*1024)
+#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (200*1024*1024)
/* For converting qt creation times to unix epoch times */
#define QTDEMUX_SECONDS_PER_DAY (60 * 60 * 24)
#define QTDEMUX_TREE_NODE_FOURCC(n) (QT_FOURCC(((guint8 *) (n)->data) + 4))
-#define STREAM_IS_EOS(s) (s->time_position == GST_CLOCK_TIME_NONE)
+#define STREAM_IS_EOS(s) ((s)->time_position == GST_CLOCK_TIME_NONE)
#define ABSDIFF(x, y) ( (x) > (y) ? ((x) - (y)) : ((y) - (x)) )
+#define QTDEMUX_STREAM(s) ((QtDemuxStream *)(s))
+#define QTDEMUX_N_STREAMS(demux) ((demux)->active_streams->len)
+#define QTDEMUX_NTH_STREAM(demux,idx) \
+ QTDEMUX_STREAM(g_ptr_array_index((demux)->active_streams,idx))
+#define QTDEMUX_NTH_OLD_STREAM(demux,idx) \
+ QTDEMUX_STREAM(g_ptr_array_index((demux)->old_streams,idx))
+
GST_DEBUG_CATEGORY (qtdemux_debug);
+#define GST_CAT_DEFAULT qtdemux_debug
typedef struct _QtDemuxSegment QtDemuxSegment;
typedef struct _QtDemuxSample QtDemuxSample;
#define QTSAMPLE_KEYFRAME(stream,sample) ((stream)->all_keyframe || (sample)->keyframe)
+#define QTDEMUX_EXPOSE_GET_LOCK(demux) (&((demux)->expose_lock))
+#define QTDEMUX_EXPOSE_LOCK(demux) G_STMT_START { \
+ GST_TRACE("Locking from thread %p", g_thread_self()); \
+ g_mutex_lock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \
+ GST_TRACE("Locked from thread %p", g_thread_self()); \
+ } G_STMT_END
+
+#define QTDEMUX_EXPOSE_UNLOCK(demux) G_STMT_START { \
+ GST_TRACE("Unlocking from thread %p", g_thread_self()); \
+ g_mutex_unlock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \
+ } G_STMT_END
+
/*
* Quicktime has tracks and segments. A track is a continuous piece of
* multimedia content. The track is not always played from start to finish but
{
GstPad *pad;
+ GstQTDemux *demux;
+ gchar *stream_id;
+
QtDemuxStreamStsdEntry *stsd_entries;
guint stsd_entries_length;
guint cur_stsd_entry_index;
guint32 n_samples;
QtDemuxSample *samples;
gboolean all_keyframe; /* TRUE when all samples are keyframes (no stss) */
- guint32 first_duration; /* duration in timescale of first sample, used for figuring out
- the framerate */
guint32 n_samples_moof; /* sample count in a moof */
guint64 duration_moof; /* duration in timescale of a moof, used for figure out
* the framerate of fragmented format stream */
/* buffer needs some custom processing, e.g. subtitles */
gboolean need_process;
+ /* buffer needs potentially be split, e.g. CEA608 subtitles */
+ gboolean need_split;
/* current position */
guint32 segment_index;
guint32 protection_scheme_version;
gpointer protection_scheme_info; /* specific to the protection scheme */
GQueue protection_scheme_event_queue;
+
+ gint ref_count; /* atomic */
};
/* Contains properties and cryptographic info for a set of samples from a
static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux);
+static void gst_qtdemux_check_send_pending_segment (GstQTDemux * demux);
+
static GstStaticPadTemplate gst_qtdemux_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
#endif
static GstStateChangeReturn gst_qtdemux_change_state (GstElement * element,
GstStateChange transition);
+static void gst_qtdemux_set_context (GstElement * element,
+ GstContext * context);
static gboolean qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent);
static gboolean qtdemux_sink_activate_mode (GstPad * sinkpad,
GstObject * parent, GstPadMode mode, gboolean active);
GstBuffer * inbuf);
static gboolean gst_qtdemux_handle_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
+static gboolean gst_qtdemux_handle_sink_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
static gboolean gst_qtdemux_setcaps (GstQTDemux * qtdemux, GstCaps * caps);
static gboolean gst_qtdemux_configure_stream (GstQTDemux * qtdemux,
QtDemuxStream * stream);
static GstFlowReturn gst_qtdemux_process_adapter (GstQTDemux * demux,
gboolean force);
+static void gst_qtdemux_check_seekability (GstQTDemux * demux);
+
static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux,
const guint8 * buffer, guint length);
static gboolean qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node,
static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux,
QtDemuxStream * stream, guint32 n);
static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux);
-static void gst_qtdemux_stream_free (GstQTDemux * qtdemux,
- QtDemuxStream * stream);
-static void gst_qtdemux_stream_clear (GstQTDemux * qtdemux,
- QtDemuxStream * stream);
-static void gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int index);
+static QtDemuxStream *gst_qtdemux_stream_ref (QtDemuxStream * stream);
+static void gst_qtdemux_stream_unref (QtDemuxStream * stream);
+static void gst_qtdemux_stream_clear (QtDemuxStream * stream);
static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux);
-static void qtdemux_do_allocation (GstQTDemux * qtdemux,
- QtDemuxStream * stream);
+static void qtdemux_do_allocation (QtDemuxStream * stream,
+ GstQTDemux * qtdemux);
static gboolean gst_qtdemux_activate_segment (GstQTDemux * qtdemux,
QtDemuxStream * stream, guint32 seg_idx, GstClockTime offset);
static gboolean gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux,
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
gst_qtdemux_class_init (GstQTDemuxClass * klass)
gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_qtdemux_set_index);
gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_qtdemux_get_index);
#endif
+ gstelement_class->set_context = GST_DEBUG_FUNCPTR (gst_qtdemux_set_context);
gst_tag_register_musicbrainz_tags ();
"David Schleef <ds@schleef.org>, Wim Taymans <wim@fluendo.com>");
GST_DEBUG_CATEGORY_INIT (qtdemux_debug, "qtdemux", 0, "qtdemux plugin");
-
+ gst_riff_init ();
}
static void
qtdemux_sink_activate_mode);
gst_pad_set_chain_function (qtdemux->sinkpad, gst_qtdemux_chain);
gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event);
+ gst_pad_set_query_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_query);
gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
- qtdemux->state = QTDEMUX_STATE_INITIAL;
- qtdemux->pullbased = FALSE;
- qtdemux->posted_redirect = FALSE;
- qtdemux->neededbytes = 16;
- qtdemux->todrop = 0;
qtdemux->adapter = gst_adapter_new ();
- qtdemux->offset = 0;
- qtdemux->first_mdat = -1;
- qtdemux->got_moov = FALSE;
- qtdemux->mdatoffset = -1;
- qtdemux->mdatbuffer = NULL;
- qtdemux->restoredata_buffer = NULL;
- qtdemux->restoredata_offset = -1;
- qtdemux->fragment_start = -1;
- qtdemux->fragment_start_offset = -1;
- qtdemux->media_caps = NULL;
- qtdemux->exposed = FALSE;
- qtdemux->mss_mode = FALSE;
- qtdemux->pending_newsegment = NULL;
- qtdemux->upstream_format_is_time = FALSE;
- qtdemux->have_group_id = FALSE;
- qtdemux->group_id = G_MAXUINT;
- qtdemux->cenc_aux_info_offset = 0;
- qtdemux->cenc_aux_info_sizes = NULL;
- qtdemux->cenc_aux_sample_count = 0;
- qtdemux->protection_system_ids = NULL;
g_queue_init (&qtdemux->protection_event_queue);
- gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
- qtdemux->tag_list = gst_tag_list_new_empty ();
- gst_tag_list_set_scope (qtdemux->tag_list, GST_TAG_SCOPE_GLOBAL);
qtdemux->flowcombiner = gst_flow_combiner_new ();
+ g_mutex_init (&qtdemux->expose_lock);
+
+ qtdemux->active_streams = g_ptr_array_new_with_free_func
+ ((GDestroyNotify) gst_qtdemux_stream_unref);
+ qtdemux->old_streams = g_ptr_array_new_with_free_func
+ ((GDestroyNotify) gst_qtdemux_stream_unref);
GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE);
+
+ gst_qtdemux_reset (qtdemux, TRUE);
}
static void
g_free (qtdemux->cenc_aux_info_sizes);
qtdemux->cenc_aux_info_sizes = NULL;
+ g_mutex_clear (&qtdemux->expose_lock);
+
+ g_ptr_array_free (qtdemux->active_streams, TRUE);
+ g_ptr_array_free (qtdemux->old_streams, TRUE);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
{
- guint n;
gboolean has_valid_stream = FALSE;
GstEventType etype = GST_EVENT_TYPE (event);
+ guint i;
GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads",
GST_EVENT_TYPE_NAME (event));
- for (n = 0; n < qtdemux->n_streams; n++) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
GstPad *pad;
- QtDemuxStream *stream = qtdemux->streams[n];
- GST_DEBUG_OBJECT (qtdemux, "pushing on pad %i", n);
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ GST_DEBUG_OBJECT (qtdemux, "pushing on track-id %u", stream->track_id);
if ((pad = stream->pad)) {
has_valid_stream = TRUE;
}
}
-/* push a pending newsegment event, if any from the streaming thread */
-static void
-gst_qtdemux_push_pending_newsegment (GstQTDemux * qtdemux)
-{
- if (qtdemux->pending_newsegment) {
- gst_qtdemux_push_event (qtdemux, qtdemux->pending_newsegment);
- qtdemux->pending_newsegment = NULL;
- }
-}
-
typedef struct
{
guint64 media_time;
static gint
find_func (QtDemuxSample * s1, gint64 * media_time, gpointer user_data)
{
- if ((gint64) s1->timestamp + s1->pts_offset > *media_time)
+ if ((gint64) s1->timestamp > *media_time)
return 1;
- if ((gint64) s1->timestamp + s1->pts_offset == *media_time)
+ if ((gint64) s1->timestamp == *media_time)
return 0;
return -1;
/* find the index of the sample that includes the data for @media_time using a
* binary search. Only to be called in optimized cases of linear search below.
*
- * Returns the index of the sample.
+ * Returns the index of the sample with the corresponding *DTS*.
*/
static guint32
gst_qtdemux_find_index (GstQTDemux * qtdemux, QtDemuxStream * str,
/* use faster search if requested time in already parsed range */
sample = str->samples + str->stbl_index;
- if (str->stbl_index >= 0 &&
- mov_time <= (sample->timestamp + sample->pts_offset))
- return gst_qtdemux_find_index (qtdemux, str, media_time);
+ if (str->stbl_index >= 0 && mov_time <= sample->timestamp) {
+ index = gst_qtdemux_find_index (qtdemux, str, media_time);
+ sample = str->samples + index;
+ } else {
+ while (index < str->n_samples - 1) {
+ if (!qtdemux_parse_samples (qtdemux, str, index + 1))
+ goto parse_failed;
- while (index < str->n_samples - 1) {
- if (!qtdemux_parse_samples (qtdemux, str, index + 1))
- goto parse_failed;
+ sample = str->samples + index + 1;
+ if (mov_time < sample->timestamp) {
+ sample = str->samples + index;
+ break;
+ }
- sample = str->samples + index + 1;
- if (mov_time < (sample->timestamp + sample->pts_offset))
- break;
+ index++;
+ }
+ }
- index++;
+ /* sample->timestamp is now <= media_time, need to find the corresponding
+ * PTS now by looking backwards */
+ while (index > 0 && sample->timestamp + sample->pts_offset > mov_time) {
+ index--;
+ sample = str->samples + index;
}
+
return index;
/* ERRORS */
{
guint64 min_offset;
gint64 min_byte_offset = -1;
- gint n;
+ guint i;
min_offset = desired_time;
/* for each stream, find the index of the sample in the segment
* and move back to the previous keyframe. */
- for (n = 0; n < qtdemux->n_streams; n++) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
QtDemuxStream *str;
guint32 index, kindex;
guint32 seg_idx;
QtDemuxSegment *seg;
gboolean empty_segment = FALSE;
- str = qtdemux->streams[n];
+ str = QTDEMUX_NTH_STREAM (qtdemux, i);
if (CUR_STREAM (str)->sparse && !use_sparse)
continue;
}
GST_OBJECT_UNLOCK (qtdemux);
+ qtdemux->segment_seqnum = seqnum;
/* BYTE seek event */
event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, byte_cur,
stop_type, stop);
guint32 seqnum, GstSeekFlags flags)
{
gint64 desired_offset;
- gint n;
+ guint i;
desired_offset = segment->position;
/* and set all streams to the final position */
gst_flow_combiner_reset (qtdemux->flowcombiner);
qtdemux->segment_seqnum = seqnum;
- for (n = 0; n < qtdemux->n_streams; n++) {
- QtDemuxStream *stream = qtdemux->streams[n];
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
stream->time_position = desired_offset;
stream->accumulated_base = 0;
gboolean flush;
gboolean update;
GstSegment seeksegment;
- guint32 seqnum = 0;
+ guint32 seqnum = GST_SEQNUM_INVALID;
GstEvent *flush_event;
gboolean ret;
/* stop streaming, either by flushing or by pausing the task */
if (flush) {
flush_event = gst_event_new_flush_start ();
- if (seqnum)
+ if (seqnum != GST_SEQNUM_INVALID)
gst_event_set_seqnum (flush_event, seqnum);
/* unlock upstream pull_range */
gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (flush_event));
/* prepare for streaming again */
if (flush) {
flush_event = gst_event_new_flush_stop (TRUE);
- if (seqnum)
+ if (seqnum != GST_SEQNUM_INVALID)
gst_event_set_seqnum (flush_event, seqnum);
gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (flush_event));
if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
GstMessage *msg = gst_message_new_segment_start (GST_OBJECT_CAST (qtdemux),
qtdemux->segment.format, qtdemux->segment.position);
- if (seqnum)
+ if (seqnum != GST_SEQNUM_INVALID)
gst_message_set_seqnum (msg, seqnum);
gst_element_post_message (GST_ELEMENT_CAST (qtdemux), msg);
}
GST_DEBUG_OBJECT (qtdemux, "collecting all metadata for all streams");
/* Build complete index */
- for (i = 0; i < qtdemux->n_streams; i++) {
- QtDemuxStream *stream = qtdemux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
- if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1))
- goto parse_error;
+ if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) {
+ GST_LOG_OBJECT (qtdemux,
+ "Building complete index of track-id %u for seeking failed!",
+ stream->track_id);
+ return FALSE;
+ }
}
- return TRUE;
- /* ERRORS */
-parse_error:
- {
- GST_LOG_OBJECT (qtdemux,
- "Building complete index of stream %u for seeking failed!", i);
- return FALSE;
- }
+ return TRUE;
}
static gboolean
#endif
guint32 seqnum = gst_event_get_seqnum (event);
+ qtdemux->received_seek = TRUE;
+
if (seqnum == qtdemux->segment_seqnum) {
GST_LOG_OBJECT (pad,
"Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum);
} else if (gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (event))) {
GST_DEBUG_OBJECT (qtdemux, "Upstream successfully seeked");
res = TRUE;
- } else if (qtdemux->state == QTDEMUX_STATE_MOVIE && qtdemux->n_streams
+ } else if (qtdemux->state == QTDEMUX_STATE_MOVIE
+ && QTDEMUX_N_STREAMS (qtdemux)
&& !qtdemux->fragmented) {
res = gst_qtdemux_do_push_seek (qtdemux, pad, event);
} else {
}
}
-/* stream/index return sample that is min/max w.r.t. byte position,
- * time is min/max w.r.t. time of samples,
- * the latter need not be time of the former sample */
+/* Find, for each track, the first sample in coding order that has a file offset >= @byte_pos.
+ *
+ * If @fw is false, the coding order is explored backwards.
+ *
+ * If @set is true, each stream will be moved to its matched sample, or EOS if no matching
+ * sample is found for that track.
+ *
+ * The stream and sample index of the sample with the minimum offset in the direction explored
+ * (see @fw) is returned in the output parameters @_stream and @_index respectively.
+ *
+ * @_time is set to the QTSAMPLE_PTS of the matched sample with the minimum QTSAMPLE_PTS in the
+ * direction explored, which may not always match the QTSAMPLE_PTS of the sample returned in
+ * @_stream and @_index. */
static void
gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw,
gboolean set, QtDemuxStream ** _stream, gint * _index, gint64 * _time)
{
- gint i, n, index;
+ gint i, index;
gint64 time, min_time;
QtDemuxStream *stream;
+ gint iter;
min_time = -1;
stream = NULL;
index = -1;
- for (n = 0; n < qtdemux->n_streams; ++n) {
+ for (iter = 0; iter < QTDEMUX_N_STREAMS (qtdemux); iter++) {
QtDemuxStream *str;
gint inc;
gboolean set_sample;
- str = qtdemux->streams[n];
+ str = QTDEMUX_NTH_STREAM (qtdemux, iter);
set_sample = !set;
if (fw) {
*_index = index;
}
+/* Copied from mpegtsbase code */
+/* FIXME: replace this function when we add new util function for stream-id creation */
+static gchar *
+_get_upstream_id (GstQTDemux * demux)
+{
+ gchar *upstream_id = gst_pad_get_stream_id (demux->sinkpad);
+
+ if (!upstream_id) {
+ /* Try to create one from the upstream URI, else use a randome number */
+ GstQuery *query;
+ gchar *uri = NULL;
+
+ /* Try to generate one from the URI query and
+ * if it fails take a random number instead */
+ query = gst_query_new_uri ();
+ if (gst_element_query (GST_ELEMENT_CAST (demux), query)) {
+ gst_query_parse_uri (query, &uri);
+ }
+
+ if (uri) {
+ GChecksum *cs;
+
+ /* And then generate an SHA256 sum of the URI */
+ cs = g_checksum_new (G_CHECKSUM_SHA256);
+ g_checksum_update (cs, (const guchar *) uri, strlen (uri));
+ g_free (uri);
+ upstream_id = g_strdup (g_checksum_get_string (cs));
+ g_checksum_free (cs);
+ } else {
+ /* Just get some random number if the URI query fails */
+ GST_FIXME_OBJECT (demux, "Creating random stream-id, consider "
+ "implementing a deterministic way of creating a stream-id");
+ upstream_id =
+ g_strdup_printf ("%08x%08x%08x%08x", g_random_int (), g_random_int (),
+ g_random_int (), g_random_int ());
+ }
+
+ gst_query_unref (query);
+ }
+ return upstream_id;
+}
+
static QtDemuxStream *
-_create_stream (void)
+_create_stream (GstQTDemux * demux, guint32 track_id)
{
QtDemuxStream *stream;
+ gchar *upstream_id;
stream = g_new0 (QtDemuxStream, 1);
+ stream->demux = demux;
+ stream->track_id = track_id;
+ upstream_id = _get_upstream_id (demux);
+ stream->stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id);
+ g_free (upstream_id);
/* new streams always need a discont */
stream->discont = TRUE;
/* we enable clipping for raw audio/video streams */
stream->stream_tags = gst_tag_list_new_empty ();
gst_tag_list_set_scope (stream->stream_tags, GST_TAG_SCOPE_STREAM);
g_queue_init (&stream->protection_scheme_event_queue);
+ stream->ref_count = 1;
+ /* consistent default for push based mode */
+ gst_segment_init (&stream->segment, GST_FORMAT_TIME);
return stream;
}
demux->fragmented = TRUE;
demux->mss_mode = TRUE;
- if (demux->n_streams > 1) {
+ if (QTDEMUX_N_STREAMS (demux) > 1) {
/* can't do this, we can only renegotiate for another mss format */
return FALSE;
}
/* TODO update when stream changes during playback */
- if (demux->n_streams == 0) {
- stream = _create_stream ();
- demux->streams[demux->n_streams] = stream;
- demux->n_streams = 1;
+ if (QTDEMUX_N_STREAMS (demux) == 0) {
+ stream = _create_stream (demux, 1);
+ g_ptr_array_add (demux->active_streams, stream);
/* mss has no stsd/stsd entry, use id 0 as default */
stream->stsd_entries_length = 1;
stream->stsd_sample_description_id = stream->cur_stsd_entry_index = 0;
stream->stsd_entries = g_new0 (QtDemuxStreamStsdEntry, 1);
} else {
- stream = demux->streams[0];
+ stream = QTDEMUX_NTH_STREAM (demux, 0);
}
timescale_v = gst_structure_get_value (structure, "timescale");
static void
gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
{
- gint n;
+ gint i;
GST_DEBUG_OBJECT (qtdemux, "Resetting demux");
gst_pad_stop_task (qtdemux->sinkpad);
qtdemux->element_index = NULL;
#endif
qtdemux->major_brand = 0;
- if (qtdemux->pending_newsegment)
- gst_event_unref (qtdemux->pending_newsegment);
- qtdemux->pending_newsegment = NULL;
qtdemux->upstream_format_is_time = FALSE;
qtdemux->upstream_seekable = FALSE;
qtdemux->upstream_size = 0;
g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
NULL);
g_queue_clear (&qtdemux->protection_event_queue);
+
+ qtdemux->received_seek = FALSE;
+ qtdemux->first_moof_already_parsed = FALSE;
}
qtdemux->offset = 0;
gst_adapter_clear (qtdemux->adapter);
gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
- qtdemux->segment_seqnum = 0;
+ qtdemux->need_segment = TRUE;
if (hard) {
- for (n = 0; n < qtdemux->n_streams; n++) {
- gst_qtdemux_stream_free (qtdemux, qtdemux->streams[n]);
- qtdemux->streams[n] = NULL;
- }
- qtdemux->n_streams = 0;
+ qtdemux->segment_seqnum = GST_SEQNUM_INVALID;
+ g_ptr_array_set_size (qtdemux->active_streams, 0);
+ g_ptr_array_set_size (qtdemux->old_streams, 0);
qtdemux->n_video_streams = 0;
qtdemux->n_audio_streams = 0;
qtdemux->n_sub_streams = 0;
gst_caps_replace (&qtdemux->media_caps, NULL);
qtdemux->timescale = 0;
qtdemux->got_moov = FALSE;
+ qtdemux->cenc_aux_info_offset = 0;
+ qtdemux->cenc_aux_info_sizes = NULL;
+ qtdemux->cenc_aux_sample_count = 0;
if (qtdemux->protection_system_ids) {
g_ptr_array_free (qtdemux->protection_system_ids, TRUE);
qtdemux->protection_system_ids = NULL;
}
+ qtdemux->streams_aware = GST_OBJECT_PARENT (qtdemux)
+ && GST_OBJECT_FLAG_IS_SET (GST_OBJECT_PARENT (qtdemux),
+ GST_BIN_FLAG_STREAMS_AWARE);
+
+ if (qtdemux->preferred_protection_system_id) {
+ g_free (qtdemux->preferred_protection_system_id);
+ qtdemux->preferred_protection_system_id = NULL;
+ }
} else if (qtdemux->mss_mode) {
gst_flow_combiner_reset (qtdemux->flowcombiner);
- for (n = 0; n < qtdemux->n_streams; n++)
- gst_qtdemux_stream_clear (qtdemux, qtdemux->streams[n]);
+ g_ptr_array_foreach (qtdemux->active_streams,
+ (GFunc) gst_qtdemux_stream_clear, NULL);
} else {
gst_flow_combiner_reset (qtdemux->flowcombiner);
- for (n = 0; n < qtdemux->n_streams; n++) {
- qtdemux->streams[n]->sent_eos = FALSE;
- qtdemux->streams[n]->time_position = 0;
- qtdemux->streams[n]->accumulated_base = 0;
- }
- if (!qtdemux->pending_newsegment) {
- qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment);
- if (qtdemux->segment_seqnum)
- gst_event_set_seqnum (qtdemux->pending_newsegment,
- qtdemux->segment_seqnum);
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ stream->sent_eos = FALSE;
+ stream->time_position = 0;
+ stream->accumulated_base = 0;
}
}
}
static void
gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment)
{
- gint n, i;
+ gint i, iter;
- for (n = 0; n < qtdemux->n_streams; n++) {
- QtDemuxStream *stream = qtdemux->streams[n];
+ for (iter = 0; iter < QTDEMUX_N_STREAMS (qtdemux); iter++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, iter);
stream->time_position = segment->start;
* supported and are discarded when parsing the edts */
for (i = 0; i < stream->n_segments; i++) {
if (stream->segments[i].stop_time > segment->start) {
+ /* push the empty segment and move to the next one */
gst_qtdemux_activate_segment (qtdemux, stream, i,
stream->time_position);
if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) {
- /* push the empty segment and move to the next one */
gst_qtdemux_send_gap_for_segment (qtdemux, stream, i,
stream->time_position);
+
+ /* accumulate previous segments */
+ if (GST_CLOCK_TIME_IS_VALID (stream->segment.stop))
+ stream->accumulated_base +=
+ (stream->segment.stop -
+ stream->segment.start) / ABS (stream->segment.rate);
continue;
}
}
}
+static void
+gst_qtdemux_stream_concat (GstQTDemux * qtdemux, GPtrArray * dest,
+ GPtrArray * src)
+{
+ guint i;
+ guint len;
+
+ len = src->len;
+
+ if (len == 0)
+ return;
+
+ for (i = 0; i < len; i++) {
+ QtDemuxStream *stream = g_ptr_array_index (src, i);
+
+#ifndef GST_DISABLE_GST_DEBUG
+ GST_DEBUG_OBJECT (qtdemux, "Move stream %p (stream-id %s) to %p",
+ stream, GST_STR_NULL (stream->stream_id), dest);
+#endif
+ g_ptr_array_add (dest, gst_qtdemux_stream_ref (stream));
+ }
+
+ g_ptr_array_set_size (src, 0);
+}
+
static gboolean
gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
GstEvent * event)
GST_DEBUG_OBJECT (demux, "received newsegment %" GST_SEGMENT_FORMAT,
&segment);
- /* erase any previously set segment */
- gst_event_replace (&demux->pending_newsegment, NULL);
-
if (segment.format == GST_FORMAT_TIME) {
- GST_DEBUG_OBJECT (demux, "new pending_newsegment");
- gst_event_replace (&demux->pending_newsegment, event);
demux->upstream_format_is_time = TRUE;
+ demux->segment_seqnum = gst_event_get_seqnum (event);
} else {
GST_DEBUG_OBJECT (demux, "Not storing upstream newsegment, "
"not in time format");
/* chain will send initial newsegment after pads have been added */
- if (demux->state != QTDEMUX_STATE_MOVIE || !demux->n_streams) {
+ if (demux->state != QTDEMUX_STATE_MOVIE || !QTDEMUX_N_STREAMS (demux)) {
GST_DEBUG_OBJECT (demux, "still starting, eating event");
goto exit;
}
GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment);
/* map segment to internal qt segments and push on each stream */
- if (demux->n_streams) {
- if (demux->fragmented) {
- GstEvent *segment_event = gst_event_new_segment (&segment);
-
- gst_event_replace (&demux->pending_newsegment, NULL);
- gst_event_set_seqnum (segment_event, demux->segment_seqnum);
- gst_qtdemux_push_event (demux, segment_event);
- } else {
- gst_event_replace (&demux->pending_newsegment, NULL);
- gst_qtdemux_map_and_push_segments (demux, &segment);
- }
+ if (QTDEMUX_N_STREAMS (demux)) {
+ demux->need_segment = TRUE;
+ gst_qtdemux_check_send_pending_segment (demux);
}
/* clear leftover in current segment, if any */
gst_event_unref (event);
goto drop;
}
- break;
+ QTDEMUX_EXPOSE_LOCK (demux);
+ res = gst_pad_event_default (demux->sinkpad, parent, event);
+ QTDEMUX_EXPOSE_UNLOCK (demux);
+ goto drop;
}
case GST_EVENT_FLUSH_STOP:
{
if (!demux->pullbased) {
gint i;
gboolean has_valid_stream = FALSE;
- for (i = 0; i < demux->n_streams; i++) {
- if (demux->streams[i]->pad != NULL) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ if (QTDEMUX_NTH_STREAM (demux, i)->pad != NULL) {
has_valid_stream = TRUE;
break;
}
res = TRUE;
goto drop;
}
+ case GST_EVENT_STREAM_START:
+ {
+ res = TRUE;
+ gst_event_unref (event);
+
+ /* Drain all the buffers */
+ gst_qtdemux_process_adapter (demux, TRUE);
+ gst_qtdemux_reset (demux, FALSE);
+ /* We expect new moov box after new stream-start event */
+ if (demux->exposed) {
+ gst_qtdemux_stream_concat (demux,
+ demux->old_streams, demux->active_streams);
+ }
+
+ goto drop;
+ }
default:
break;
}
return res;
}
+static gboolean
+gst_qtdemux_handle_sink_query (GstPad * pad, GstObject * parent,
+ GstQuery * query)
+{
+ GstQTDemux *demux = GST_QTDEMUX (parent);
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_BITRATE:
+ {
+ GstClockTime duration;
+
+ /* populate demux->upstream_size if not done yet */
+ gst_qtdemux_check_seekability (demux);
+
+ if (demux->upstream_size != -1
+ && gst_qtdemux_get_duration (demux, &duration)) {
+ guint bitrate =
+ gst_util_uint64_scale (8 * demux->upstream_size, GST_SECOND,
+ duration);
+
+ GST_LOG_OBJECT (demux, "bitrate query byte length: %" G_GUINT64_FORMAT
+ " duration %" GST_TIME_FORMAT " resulting a bitrate of %u",
+ demux->upstream_size, GST_TIME_ARGS (duration), bitrate);
+
+ /* TODO: better results based on ranges/index tables */
+ gst_query_set_bitrate (query, bitrate);
+ res = TRUE;
+ }
+ break;
+ }
+ default:
+ res = gst_pad_query_default (pad, (GstObject *) demux, query);
+ break;
+ }
+
+ return res;
+}
+
+
#if 0
static void
gst_qtdemux_set_index (GstElement * element, GstIndex * index)
}
static void
-gst_qtdemux_stream_flush_segments_data (GstQTDemux * qtdemux,
- QtDemuxStream * stream)
+gst_qtdemux_stream_flush_segments_data (QtDemuxStream * stream)
{
g_free (stream->segments);
stream->segments = NULL;
}
static void
-gst_qtdemux_stream_flush_samples_data (GstQTDemux * qtdemux,
- QtDemuxStream * stream)
+gst_qtdemux_stream_flush_samples_data (QtDemuxStream * stream)
{
g_free (stream->samples);
stream->samples = NULL;
}
static void
-gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_stream_clear (QtDemuxStream * stream)
{
gint i;
if (stream->allocator)
entry->sparse = FALSE;
}
- gst_tag_list_unref (stream->stream_tags);
+ if (stream->stream_tags)
+ gst_tag_list_unref (stream->stream_tags);
+
stream->stream_tags = gst_tag_list_new_empty ();
gst_tag_list_set_scope (stream->stream_tags, GST_TAG_SCOPE_STREAM);
g_free (stream->redirect_uri);
g_queue_foreach (&stream->protection_scheme_event_queue,
(GFunc) gst_event_unref, NULL);
g_queue_clear (&stream->protection_scheme_event_queue);
- gst_qtdemux_stream_flush_segments_data (qtdemux, stream);
- gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
+ gst_qtdemux_stream_flush_segments_data (stream);
+ gst_qtdemux_stream_flush_samples_data (stream);
}
static void
-gst_qtdemux_stream_free (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_stream_reset (QtDemuxStream * stream)
{
gint i;
- gst_qtdemux_stream_clear (qtdemux, stream);
+ gst_qtdemux_stream_clear (stream);
for (i = 0; i < stream->stsd_entries_length; i++) {
QtDemuxStreamStsdEntry *entry = &stream->stsd_entries[i];
if (entry->caps) {
entry->caps = NULL;
}
}
- gst_tag_list_unref (stream->stream_tags);
- if (stream->pad) {
- gst_element_remove_pad (GST_ELEMENT_CAST (qtdemux), stream->pad);
- gst_flow_combiner_remove_pad (qtdemux->flowcombiner, stream->pad);
- }
g_free (stream->stsd_entries);
- g_free (stream);
+ stream->stsd_entries = NULL;
+ stream->stsd_entries_length = 0;
}
-static void
-gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int i)
+static QtDemuxStream *
+gst_qtdemux_stream_ref (QtDemuxStream * stream)
{
- g_assert (i >= 0 && i < qtdemux->n_streams && qtdemux->streams[i] != NULL);
+ g_atomic_int_add (&stream->ref_count, 1);
+
+ return stream;
+}
- gst_qtdemux_stream_free (qtdemux, qtdemux->streams[i]);
- qtdemux->streams[i] = qtdemux->streams[qtdemux->n_streams - 1];
- qtdemux->streams[qtdemux->n_streams - 1] = NULL;
- qtdemux->n_streams--;
+static void
+gst_qtdemux_stream_unref (QtDemuxStream * stream)
+{
+ if (g_atomic_int_dec_and_test (&stream->ref_count)) {
+ gst_qtdemux_stream_reset (stream);
+ gst_tag_list_unref (stream->stream_tags);
+ if (stream->pad) {
+ GstQTDemux *demux = stream->demux;
+ gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad);
+ gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad);
+ }
+ g_free (stream->stream_id);
+ g_free (stream);
+ }
}
static GstStateChangeReturn
GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
switch (transition) {
- case GST_STATE_CHANGE_PAUSED_TO_READY:
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ gst_qtdemux_reset (qtdemux, TRUE);
break;
default:
break;
}
static void
+gst_qtdemux_set_context (GstElement * element, GstContext * context)
+{
+ GstQTDemux *qtdemux = GST_QTDEMUX (element);
+
+ g_return_if_fail (GST_IS_CONTEXT (context));
+
+ if (gst_context_has_context_type (context,
+ "drm-preferred-decryption-system-id")) {
+ const GstStructure *s;
+
+ s = gst_context_get_structure (context);
+ g_free (qtdemux->preferred_protection_system_id);
+ qtdemux->preferred_protection_system_id =
+ g_strdup (gst_structure_get_string (s, "decryption-system-id"));
+ GST_DEBUG_OBJECT (element, "set preferred decryption system to %s",
+ qtdemux->preferred_protection_system_id);
+ }
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static void
qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
{
/* counts as header data */
}
static void
+qtdemux_update_default_sample_encryption_settings (GstQTDemux * qtdemux,
+ QtDemuxCencSampleSetInfo * info, guint32 is_encrypted, guint8 iv_size,
+ const guint8 * kid)
+{
+ GstBuffer *kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
+ gst_buffer_fill (kid_buf, 0, kid, 16);
+ if (info->default_properties)
+ gst_structure_free (info->default_properties);
+ info->default_properties =
+ gst_structure_new ("application/x-cenc",
+ "iv_size", G_TYPE_UINT, iv_size,
+ "encrypted", G_TYPE_BOOLEAN, (is_encrypted == 1),
+ "kid", GST_TYPE_BUFFER, kid_buf, NULL);
+ GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
+ "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
+ gst_buffer_unref (kid_buf);
+}
+
+static gboolean
+qtdemux_update_default_piff_encryption_settings (GstQTDemux * qtdemux,
+ QtDemuxCencSampleSetInfo * info, GstByteReader * br)
+{
+ guint32 algorithm_id = 0;
+ const guint8 *kid;
+ gboolean is_encrypted = TRUE;
+ guint8 iv_size = 8;
+
+ if (!gst_byte_reader_get_uint24_le (br, &algorithm_id)) {
+ GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
+ return FALSE;
+ }
+
+ algorithm_id >>= 8;
+ if (algorithm_id == 0) {
+ is_encrypted = FALSE;
+ } else if (algorithm_id == 1) {
+ GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
+ } else if (algorithm_id == 2) {
+ GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
+ }
+
+ if (!gst_byte_reader_get_uint8 (br, &iv_size))
+ return FALSE;
+
+ if (!gst_byte_reader_get_data (br, 16, &kid))
+ return FALSE;
+
+ qtdemux_update_default_sample_encryption_settings (qtdemux, info,
+ is_encrypted, iv_size, kid);
+ gst_structure_set (info->default_properties, "piff_algorithm_id",
+ G_TYPE_UINT, algorithm_id, NULL);
+ return TRUE;
+}
+
+
+static void
qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
guint offset)
{
guint8 version;
guint32 flags = 0;
guint i;
- guint8 iv_size = 8;
+ guint iv_size = 8;
QtDemuxStream *stream;
GstStructure *structure;
QtDemuxCencSampleSetInfo *ss_info = NULL;
const gchar *system_id;
gboolean uses_sub_sample_encryption = FALSE;
+ guint32 sample_count;
- if (qtdemux->n_streams == 0)
+ if (QTDEMUX_N_STREAMS (qtdemux) == 0)
return;
- stream = qtdemux->streams[0];
+ stream = QTDEMUX_NTH_STREAM (qtdemux, 0);
structure = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
if (!gst_structure_has_name (structure, "application/x-cenc")) {
stream->protection_scheme_info = g_new0 (QtDemuxCencSampleSetInfo, 1);
ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+ if (!ss_info->default_properties) {
+ ss_info->default_properties =
+ gst_structure_new ("application/x-cenc",
+ "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE,
+ NULL);
- if (ss_info->default_properties)
- gst_structure_free (ss_info->default_properties);
-
- ss_info->default_properties =
- gst_structure_new ("application/x-cenc",
- "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE, NULL);
+ }
if (ss_info->crypto_info) {
GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info");
}
if ((flags & 0x000001)) {
- guint32 algorithm_id = 0;
- const guint8 *kid;
- GstBuffer *kid_buf;
- gboolean is_encrypted = TRUE;
-
- if (!gst_byte_reader_get_uint24_le (&br, &algorithm_id)) {
- GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
- return;
- }
-
- algorithm_id >>= 8;
- if (algorithm_id == 0) {
- is_encrypted = FALSE;
- } else if (algorithm_id == 1) {
- /* FIXME: maybe store this in properties? */
- GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
- } else if (algorithm_id == 2) {
- /* FIXME: maybe store this in properties? */
- GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
- }
-
- if (!gst_byte_reader_get_uint8 (&br, &iv_size))
- return;
-
- if (!gst_byte_reader_get_data (&br, 16, &kid))
+ if (!qtdemux_update_default_piff_encryption_settings (qtdemux, ss_info,
+ &br))
return;
-
- kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
- gst_buffer_fill (kid_buf, 0, kid, 16);
- if (ss_info->default_properties)
- gst_structure_free (ss_info->default_properties);
- ss_info->default_properties =
- gst_structure_new ("application/x-cenc",
- "iv_size", G_TYPE_UINT, iv_size,
- "encrypted", G_TYPE_BOOLEAN, is_encrypted,
- "kid", GST_TYPE_BUFFER, kid_buf, NULL);
- GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
- "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
- gst_buffer_unref (kid_buf);
} else if ((flags & 0x000002)) {
uses_sub_sample_encryption = TRUE;
}
- if (!gst_byte_reader_get_uint32_be (&br, &qtdemux->cenc_aux_sample_count)) {
+ if (!gst_structure_get_uint (ss_info->default_properties, "iv_size",
+ &iv_size)) {
+ GST_ERROR_OBJECT (qtdemux, "Error getting encryption IV size field");
+ return;
+ }
+
+ if (!gst_byte_reader_get_uint32_be (&br, &sample_count)) {
GST_ERROR_OBJECT (qtdemux, "Error getting box's sample count field");
return;
}
ss_info->crypto_info =
- g_ptr_array_new_full (qtdemux->cenc_aux_sample_count,
+ g_ptr_array_new_full (sample_count,
(GDestroyNotify) qtdemux_gst_structure_free);
- for (i = 0; i < qtdemux->cenc_aux_sample_count; ++i) {
+ for (i = 0; i < sample_count; ++i) {
GstStructure *properties;
guint8 *data;
GstBuffer *buf;
properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i);
if (properties == NULL) {
GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i);
+ qtdemux->cenc_aux_sample_count = i;
return;
}
if (!gst_byte_reader_dup_data (&br, iv_size, &data)) {
GST_ERROR_OBJECT (qtdemux, "IV data not present for sample %u", i);
gst_structure_free (properties);
+ qtdemux->cenc_aux_sample_count = i;
return;
}
buf = gst_buffer_new_wrapped (data, iv_size);
if (uses_sub_sample_encryption) {
guint16 n_subsamples;
+ const GValue *kid_buf_value;
if (!gst_byte_reader_get_uint16_be (&br, &n_subsamples)
|| n_subsamples == 0) {
GST_ERROR_OBJECT (qtdemux,
"failed to get subsample count for sample %u", i);
gst_structure_free (properties);
+ qtdemux->cenc_aux_sample_count = i;
return;
}
GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples);
GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u",
i);
gst_structure_free (properties);
+ qtdemux->cenc_aux_sample_count = i;
return;
}
buf = gst_buffer_new_wrapped (data, n_subsamples * 6);
+
+ kid_buf_value =
+ gst_structure_get_value (ss_info->default_properties, "kid");
+
gst_structure_set (properties,
"subsample_count", G_TYPE_UINT, n_subsamples,
"subsamples", GST_TYPE_BUFFER, buf, NULL);
+ gst_structure_set_value (properties, "kid", kid_buf_value);
gst_buffer_unref (buf);
} else {
gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL);
g_ptr_array_add (ss_info->crypto_info, properties);
}
+
+ qtdemux->cenc_aux_sample_count = sample_count;
}
static void
qtdemux->segment.stop = fixeddur;
}
}
- for (i = 0; i < qtdemux->n_streams; i++) {
- QtDemuxStream *stream = qtdemux->streams[i];
- if (stream) {
- movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration);
- if (movdur > stream->duration) {
- GST_DEBUG_OBJECT (qtdemux,
- "Updating stream #%d duration to %" GST_TIME_FORMAT, i,
- GST_TIME_ARGS (duration));
- stream->duration = movdur;
- if (stream->dummy_segment) {
- /* Update all dummy values to new duration */
- stream->segments[0].stop_time = duration;
- stream->segments[0].duration = duration;
- stream->segments[0].media_stop = duration;
-
- /* let downstream know we possibly have a new stop time */
- if (stream->segment_index != -1) {
- GstClockTime pos;
-
- if (qtdemux->segment.rate >= 0) {
- pos = stream->segment.start;
- } else {
- pos = stream->segment.stop;
- }
- gst_qtdemux_stream_update_segment (qtdemux, stream,
- stream->segment_index, pos, NULL, NULL);
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+ movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration);
+ if (movdur > stream->duration) {
+ GST_DEBUG_OBJECT (qtdemux,
+ "Updating stream #%d duration to %" GST_TIME_FORMAT, i,
+ GST_TIME_ARGS (duration));
+ stream->duration = movdur;
+ /* internal duration tracking state has been updated above, so */
+ /* preserve an open-ended dummy segment rather than repeatedly updating
+ * it and spamming downstream accordingly with segment events */
+ if (stream->dummy_segment &&
+ GST_CLOCK_TIME_IS_VALID (stream->segments[0].duration)) {
+ /* Update all dummy values to new duration */
+ stream->segments[0].stop_time = duration;
+ stream->segments[0].duration = duration;
+ stream->segments[0].media_stop = duration;
+
+ /* let downstream know we possibly have a new stop time */
+ if (stream->segment_index != -1) {
+ GstClockTime pos;
+
+ if (qtdemux->segment.rate >= 0) {
+ pos = stream->segment.start;
+ } else {
+ pos = stream->segment.stop;
}
+
+ gst_qtdemux_stream_update_segment (qtdemux, stream,
+ stream->segment_index, pos, NULL, NULL);
}
}
}
gboolean ismv = FALSE;
gint64 initial_offset;
- GST_LOG_OBJECT (qtdemux, "parsing trun stream %d; "
+ GST_LOG_OBJECT (qtdemux, "parsing trun track-id %d; "
"default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT ", "
"decode ts %" G_GINT64_FORMAT, stream->track_id, d_sample_duration,
d_sample_size, d_sample_flags, *base_offset, decode_ts);
return NULL;
}
- /* try to get it fast and simple */
- if (G_LIKELY (id <= qtdemux->n_streams)) {
- stream = qtdemux->streams[id - 1];
- if (G_LIKELY (stream->track_id == id))
- return stream;
- }
-
- /* linear search otherwise */
- for (i = 0; i < qtdemux->n_streams; i++) {
- stream = qtdemux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ stream = QTDEMUX_NTH_STREAM (qtdemux, i);
if (stream->track_id == id)
return stream;
}
if (qtdemux->mss_mode) {
/* mss should have only 1 stream anyway */
- return qtdemux->streams[0];
+ return QTDEMUX_NTH_STREAM (qtdemux, 0);
}
return NULL;
/* Push an event containing the pssh box onto the queues of all streams. */
event = gst_event_new_protection (sysid_string, pssh,
(parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof");
- for (i = 0; i < qtdemux->n_streams; ++i) {
- g_queue_push_tail (&qtdemux->streams[i]->protection_scheme_event_queue,
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ GST_TRACE_OBJECT (qtdemux,
+ "adding protection event for stream %s and system %s",
+ stream->stream_id, sysid_string);
+ g_queue_push_tail (&stream->protection_scheme_event_queue,
gst_event_ref (event));
}
g_free (sysid_string);
guint32 ds_size = 0, ds_duration = 0, ds_flags = 0;
gint64 base_offset, running_offset;
guint32 frag_num;
+ GstClockTime min_dts = GST_CLOCK_TIME_NONE;
/* NOTE @stream ignored */
if (G_UNLIKELY (base_offset < -1))
goto lost_offset;
- if (qtdemux->upstream_format_is_time)
- gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
+ min_dts = MIN (min_dts, QTSTREAMTIME_TO_GSTTIME (stream, decode_time));
+
+ if (!qtdemux->pullbased) {
+ /* Sample tables can grow enough to be problematic if the system memory
+ * is very low (e.g. embedded devices) and the videos very long
+ * (~8 MiB/hour for 25-30 fps video + typical AAC audio frames).
+ * Fortunately, we can easily discard them for each new fragment when
+ * we know qtdemux will not receive seeks outside of the current fragment.
+ * adaptivedemux honors this assumption.
+ * This optimization is also useful for applications that use qtdemux as
+ * a push-based simple demuxer, like Media Source Extensions. */
+ gst_qtdemux_stream_flush_samples_data (stream);
+ }
/* initialise moof sample data */
stream->n_samples_moof = 0;
pssh_node = qtdemux_tree_get_sibling_by_type (pssh_node, FOURCC_pssh);
}
+ if (!qtdemux->upstream_format_is_time && !qtdemux->first_moof_already_parsed
+ && !qtdemux->received_seek && GST_CLOCK_TIME_IS_VALID (min_dts)
+ && min_dts != 0) {
+ /* Unless the user has explictly requested another seek, perform an
+ * internal seek to the time specified in the tfdt.
+ *
+ * This way if the user opens a file where the first tfdt is 1 hour
+ * into the presentation, they will not have to wait 1 hour for run
+ * time to catch up and actual playback to start. */
+ gint i;
+
+ GST_DEBUG_OBJECT (qtdemux, "First fragment has a non-zero tfdt, "
+ "performing an internal seek to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (min_dts));
+
+ qtdemux->segment.start = min_dts;
+ qtdemux->segment.time = qtdemux->segment.position = min_dts;
+
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ stream->time_position = min_dts;
+ }
+
+ /* Before this code was run a segment was already sent when the moov was
+ * parsed... which is OK -- some apps (mostly tests) expect a segment to
+ * be emitted after a moov, and we can emit a second segment anyway for
+ * special cases like this. */
+ qtdemux->need_segment = TRUE;
+ }
+
+ qtdemux->first_moof_already_parsed = TRUE;
+
g_node_destroy (moof_node);
return TRUE;
if (ret == GST_FLOW_EOS && (qtdemux->got_moov || qtdemux->media_caps)) {
/* digested all data, show what we have */
qtdemux_prepare_streams (qtdemux);
+ QTDEMUX_EXPOSE_LOCK (qtdemux);
ret = qtdemux_expose_streams (qtdemux);
+ QTDEMUX_EXPOSE_UNLOCK (qtdemux);
qtdemux->state = QTDEMUX_STATE_MOVIE;
GST_DEBUG_OBJECT (qtdemux, "switching state to STATE_MOVIE (%d)",
static GstFlowReturn
gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
{
- guint8 n = 0;
guint32 seg_idx = 0, k_index = 0;
guint32 ref_seg_idx, ref_k_index;
GstClockTime k_pos = 0, last_stop = 0;
QtDemuxStream *ref_str = NULL;
guint64 seg_media_start_mov; /* segment media start time in mov format */
guint64 target_ts;
+ gint i;
/* Now we choose an arbitrary stream, get the previous keyframe timestamp
* and finally align all the other streams on that timestamp with their
* respective keyframes */
- for (n = 0; n < qtdemux->n_streams; n++) {
- QtDemuxStream *str = qtdemux->streams[n];
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
/* No candidate yet, take the first stream */
if (!ref_str) {
seg_media_start_mov = seg->trak_media_start;
GST_LOG_OBJECT (qtdemux, "keyframe index %u ts %" G_GUINT64_FORMAT
- " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT "\n",
+ " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT,
k_index, target_ts, seg_media_start_mov,
GST_TIME_ARGS (seg->media_start));
ref_k_index = k_index;
/* Align them all on this */
- for (n = 0; n < qtdemux->n_streams; n++) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
guint32 index = 0;
GstClockTime seg_time = 0;
- QtDemuxStream *str = qtdemux->streams[n];
+ QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
/* aligning reference stream again might lead to backing up to yet another
* keyframe (due to timestamp rounding issues),
seg_idx = ref_seg_idx;
seg = &str->segments[seg_idx];
k_index = ref_k_index;
- GST_DEBUG_OBJECT (qtdemux, "reference stream %d segment %d, "
- "sample at index %d", n, ref_str->segment_index, k_index);
+ GST_DEBUG_OBJECT (qtdemux, "reference track-id %u segment %d, "
+ "sample at index %d", str->track_id, ref_str->segment_index, k_index);
} else {
seg_idx = gst_qtdemux_find_segment (qtdemux, str, k_pos);
GST_DEBUG_OBJECT (qtdemux,
- "stream %d align segment %d for keyframe pos %" GST_TIME_FORMAT, n,
- seg_idx, GST_TIME_ARGS (k_pos));
+ "track-id %u align segment %d for keyframe pos %" GST_TIME_FORMAT,
+ str->track_id, seg_idx, GST_TIME_ARGS (k_pos));
/* get segment and time in the segment */
seg = &str->segments[seg_idx];
/* get the index of the sample with media time */
index = gst_qtdemux_find_index_linear (qtdemux, str, seg_time);
GST_DEBUG_OBJECT (qtdemux,
- "stream %d sample for %" GST_TIME_FORMAT " at %u", n,
+ "track-id %u sample for %" GST_TIME_FORMAT " at %u", str->track_id,
GST_TIME_ARGS (seg_time), index);
/* find previous keyframe */
/* Now seek back in time */
gst_qtdemux_move_stream (qtdemux, str, k_index);
- GST_DEBUG_OBJECT (qtdemux, "stream %d keyframe at %u, time position %"
- GST_TIME_FORMAT " playing from sample %u to %u", n, k_index,
+ GST_DEBUG_OBJECT (qtdemux, "track-id %u keyframe at %u, time position %"
+ GST_TIME_FORMAT " playing from sample %u to %u", str->track_id, k_index,
GST_TIME_ARGS (str->time_position), str->from_sample, str->to_sample);
}
/* now prepare and send the segment */
if (stream->pad) {
event = gst_event_new_segment (&stream->segment);
- if (qtdemux->segment_seqnum) {
+ if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) {
gst_event_set_seqnum (event, qtdemux->segment_seqnum);
}
gst_pad_push_event (stream->pad, event);
* (FIXME: doesn't seem to work so well with ismv and wmv, as no parser; the
* tfra entries tells us which trun/sample the key unit is in, but we don't
* make use of this additional information at the moment) */
- if (qtdemux->fragmented) {
+ if (qtdemux->fragmented && !qtdemux->fragmented_seek_pending) {
stream->to_sample = G_MAXUINT32;
return TRUE;
+ } else {
+ /* well, it will be taken care of below */
+ qtdemux->fragmented_seek_pending = FALSE;
+ /* FIXME ideally the do_fragmented_seek can be done right here,
+ * rather than at loop level
+ * (which might even allow handling edit lists in a fragmented file) */
}
/* We don't need to look for a sample in push-based */
if (G_UNLIKELY (stream->segment_index != seg_idx))
gst_qtdemux_activate_segment (qtdemux, stream, seg_idx, time_position);
- if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream->segments[stream->
- segment_index]))) {
+ if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream->
+ segments[stream->segment_index]))) {
QtDemuxSegment *seg = &stream->segments[stream->segment_index];
GST_LOG_OBJECT (qtdemux, "Empty segment activated,"
{
gint i;
- if (demux->n_streams <= 1)
+ if (QTDEMUX_N_STREAMS (demux) <= 1)
return;
- for (i = 0; i < demux->n_streams; i++) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
QtDemuxStream *stream;
GstClockTime end_time;
- stream = demux->streams[i];
+ stream = QTDEMUX_NTH_STREAM (demux, i);
if (!stream->pad)
continue;
GST_PAD_NAME (stream->pad));
stream->sent_eos = TRUE;
event = gst_event_new_eos ();
- if (demux->segment_seqnum)
+ if (demux->segment_seqnum != GST_SEQNUM_INVALID)
gst_event_set_seqnum (event, demux->segment_seqnum);
gst_pad_push_event (stream->pad, event);
}
return buffer;
}
-/* 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,
- GstBuffer * buf)
-{
- GstMapInfo map;
- guint nsize = 0;
- gchar *str;
-
- /* 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;
+static guint8 *
+convert_to_s334_1a (const guint8 * ccpair, guint8 ccpair_size, guint field,
+ gsize * res)
+{
+ guint8 *storage;
+ gsize i;
+
+ /* We are converting from pairs to triplets */
+ *res = ccpair_size / 2 * 3;
+ storage = g_malloc (*res);
+ for (i = 0; i * 2 < ccpair_size; i += 1) {
+ /* FIXME: Use line offset 0 as we simply can't know here */
+ if (field == 1)
+ storage[i * 3] = 0x80 | 0x00;
+ else
+ storage[i * 3] = 0x00 | 0x00;
+ storage[i * 3 + 1] = ccpair[i * 2];
+ storage[i * 3 + 2] = ccpair[i * 2 + 1];
}
- if (G_UNLIKELY (stream->subtype != FOURCC_text
- && stream->subtype != FOURCC_sbtl &&
- stream->subtype != FOURCC_subp)) {
+ return storage;
+}
+
+static guint8 *
+extract_cc_from_data (QtDemuxStream * stream, const guint8 * data, gsize size,
+ gsize * cclen)
+{
+ guint8 *res = NULL;
+ guint32 atom_length, fourcc;
+ QtDemuxStreamStsdEntry *stsd_entry;
+
+ GST_MEMDUMP ("caption atom", data, size);
+
+ /* There might be multiple atoms */
+
+ *cclen = 0;
+ if (size < 8)
+ goto invalid_cdat;
+ atom_length = QT_UINT32 (data);
+ fourcc = QT_FOURCC (data + 4);
+ if (G_UNLIKELY (atom_length > size || atom_length == 8))
+ goto invalid_cdat;
+
+ GST_DEBUG_OBJECT (stream->pad, "here");
+
+ /* Check if we have somethig compatible */
+ stsd_entry = CUR_STREAM (stream);
+ switch (stsd_entry->fourcc) {
+ case FOURCC_c608:{
+ guint8 *cdat = NULL, *cdt2 = NULL;
+ gsize cdat_size = 0, cdt2_size = 0;
+ /* Should be cdat or cdt2 */
+ if (fourcc != FOURCC_cdat && fourcc != FOURCC_cdt2) {
+ GST_WARNING_OBJECT (stream->pad,
+ "Unknown data atom (%" GST_FOURCC_FORMAT ") for CEA608",
+ GST_FOURCC_ARGS (fourcc));
+ goto invalid_cdat;
+ }
+
+ /* Convert to S334-1 Annex A byte triplet */
+ if (fourcc == FOURCC_cdat)
+ cdat = convert_to_s334_1a (data + 8, atom_length - 8, 1, &cdat_size);
+ else
+ cdt2 = convert_to_s334_1a (data + 8, atom_length - 8, 2, &cdt2_size);
+ GST_DEBUG_OBJECT (stream->pad, "size:%" G_GSIZE_FORMAT " atom_length:%u",
+ size, atom_length);
+
+ /* Check for another atom ? */
+ if (size > atom_length + 8) {
+ guint32 new_atom_length = QT_UINT32 (data + atom_length);
+ if (size >= atom_length + new_atom_length) {
+ fourcc = QT_FOURCC (data + atom_length + 4);
+ if (fourcc == FOURCC_cdat) {
+ if (cdat == NULL)
+ cdat =
+ convert_to_s334_1a (data + atom_length + 8,
+ new_atom_length - 8, 1, &cdat_size);
+ else
+ GST_WARNING_OBJECT (stream->pad,
+ "Got multiple [cdat] atoms in a c608 sample. This is unsupported for now. Please file a bug");
+ } else {
+ if (cdt2 == NULL)
+ cdt2 =
+ convert_to_s334_1a (data + atom_length + 8,
+ new_atom_length - 8, 2, &cdt2_size);
+ else
+ GST_WARNING_OBJECT (stream->pad,
+ "Got multiple [cdt2] atoms in a c608 sample. This is unsupported for now. Please file a bug");
+ }
+ }
+ }
+
+ *cclen = cdat_size + cdt2_size;
+ res = g_malloc (*cclen);
+ if (cdat_size)
+ memcpy (res, cdat, cdat_size);
+ if (cdt2_size)
+ memcpy (res + cdat_size, cdt2, cdt2_size);
+ g_free (cdat);
+ g_free (cdt2);
+ }
+ break;
+ case FOURCC_c708:
+ if (fourcc != FOURCC_ccdp) {
+ GST_WARNING_OBJECT (stream->pad,
+ "Unknown data atom (%" GST_FOURCC_FORMAT ") for CEA708",
+ GST_FOURCC_ARGS (fourcc));
+ goto invalid_cdat;
+ }
+ *cclen = atom_length - 8;
+ res = g_memdup (data + 8, *cclen);
+ break;
+ default:
+ /* Keep this here in case other closed caption formats are added */
+ g_assert_not_reached ();
+ break;
+ }
+
+ GST_MEMDUMP ("Output", res, *cclen);
+ return res;
+
+ /* Errors */
+invalid_cdat:
+ GST_WARNING ("[cdat] atom is too small or invalid");
+ return NULL;
+}
+
+/* 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,
+ GstBuffer * buf)
+{
+ GstMapInfo map;
+ guint nsize = 0;
+ gchar *str;
+
+ /* 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;
+ }
+
+ if (G_UNLIKELY (stream->subtype != FOURCC_text
+ && stream->subtype != FOURCC_sbtl &&
+ stream->subtype != FOURCC_subp && stream->subtype != FOURCC_clcp)) {
return buf;
}
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_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;
+ }
+
nsize = GST_READ_UINT16_BE (map.data);
nsize = MIN (nsize, map.size - 2);
return buf;
}
+static GstFlowReturn
+gst_qtdemux_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+ GstBuffer * buf)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstClockTime pts, duration;
+
+ if (stream->need_clip)
+ buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf);
+
+ if (G_UNLIKELY (buf == NULL))
+ goto exit;
+
+ if (G_UNLIKELY (stream->discont)) {
+ GST_LOG_OBJECT (qtdemux, "marking discont buffer");
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
+ stream->discont = FALSE;
+ } else {
+ GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
+ }
+
+ GST_LOG_OBJECT (qtdemux,
+ "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
+ ", duration %" GST_TIME_FORMAT " on pad %s",
+ GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
+ GST_TIME_ARGS (GST_BUFFER_PTS (buf)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_PAD_NAME (stream->pad));
+
+ if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
+ GstStructure *crypto_info;
+ QtDemuxCencSampleSetInfo *info =
+ (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+ gint index;
+ GstEvent *event;
+
+ while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
+ GST_TRACE_OBJECT (stream->pad, "pushing protection event: %"
+ GST_PTR_FORMAT, event);
+ gst_pad_push_event (stream->pad, event);
+ }
+
+ if (info->crypto_info == NULL) {
+ GST_DEBUG_OBJECT (qtdemux,
+ "cenc metadata hasn't been parsed yet, pushing buffer as if it wasn't encrypted");
+ } else {
+ /* The end of the crypto_info array matches our n_samples position,
+ * so count backward from there */
+ index = stream->sample_index - stream->n_samples + info->crypto_info->len;
+ if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
+ /* steal structure from array */
+ crypto_info = g_ptr_array_index (info->crypto_info, index);
+ g_ptr_array_index (info->crypto_info, index) = NULL;
+ GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u/%u]", index,
+ info->crypto_info->len);
+ if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
+ GST_ERROR_OBJECT (qtdemux,
+ "failed to attach cenc metadata to buffer");
+ } else {
+ GST_INFO_OBJECT (qtdemux, "No crypto info with index %d and sample %d",
+ index, stream->sample_index);
+ }
+ }
+ }
+
+ if (stream->alignment > 1)
+ buf = gst_qtdemux_align_buffer (qtdemux, buf, stream->alignment);
+
+ pts = GST_BUFFER_PTS (buf);
+ duration = GST_BUFFER_DURATION (buf);
+
+ ret = gst_pad_push (stream->pad, buf);
+
+ if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
+ /* mark position in stream, we'll need this to know when to send GAP event */
+ stream->segment.position = pts + duration;
+ }
+
+exit:
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_qtdemux_split_and_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+ GstBuffer * buf)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ if (stream->subtype == FOURCC_clcp
+ && CUR_STREAM (stream)->fourcc == FOURCC_c608 && stream->need_split) {
+ GstMapInfo map;
+ guint n_output_buffers, n_field1 = 0, n_field2 = 0;
+ guint n_triplets, i;
+ guint field1_off = 0, field2_off = 0;
+
+ /* We have to split CEA608 buffers so that each outgoing buffer contains
+ * one byte pair per field according to the framerate of the video track.
+ *
+ * If there is only a single byte pair per field we don't have to do
+ * anything
+ */
+
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+
+ n_triplets = map.size / 3;
+ for (i = 0; i < n_triplets; i++) {
+ if (map.data[3 * i] & 0x80)
+ n_field1++;
+ else
+ n_field2++;
+ }
+
+ g_assert (n_field1 || n_field2);
+
+ /* If there's more than 1 frame we have to split, otherwise we can just
+ * pass through */
+ if (n_field1 > 1 || n_field2 > 1) {
+ n_output_buffers =
+ gst_util_uint64_scale (GST_BUFFER_DURATION (buf),
+ CUR_STREAM (stream)->fps_n, GST_SECOND * CUR_STREAM (stream)->fps_d);
+
+ for (i = 0; i < n_output_buffers; i++) {
+ GstBuffer *outbuf =
+ gst_buffer_new_and_alloc ((n_field1 ? 3 : 0) + (n_field2 ? 3 : 0));
+ GstMapInfo outmap;
+ guint8 *outptr;
+
+ gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE);
+ outptr = outmap.data;
+
+ if (n_field1) {
+ gboolean found = FALSE;
+
+ while (map.data + field1_off < map.data + map.size) {
+ if (map.data[field1_off] & 0x80) {
+ memcpy (outptr, &map.data[field1_off], 3);
+ field1_off += 3;
+ found = TRUE;
+ break;
+ }
+ field1_off += 3;
+ }
+
+ if (!found) {
+ const guint8 empty[] = { 0x80, 0x80, 0x80 };
+
+ memcpy (outptr, empty, 3);
+ }
+
+ outptr += 3;
+ }
+
+ if (n_field2) {
+ gboolean found = FALSE;
+
+ while (map.data + field2_off < map.data + map.size) {
+ if ((map.data[field2_off] & 0x80) == 0) {
+ memcpy (outptr, &map.data[field2_off], 3);
+ field2_off += 3;
+ found = TRUE;
+ break;
+ }
+ field2_off += 3;
+ }
+
+ if (!found) {
+ const guint8 empty[] = { 0x00, 0x80, 0x80 };
+
+ memcpy (outptr, empty, 3);
+ }
+
+ outptr += 3;
+ }
+
+ gst_buffer_unmap (outbuf, &outmap);
+
+ GST_BUFFER_PTS (outbuf) =
+ GST_BUFFER_PTS (buf) + gst_util_uint64_scale (i,
+ GST_SECOND * CUR_STREAM (stream)->fps_d,
+ CUR_STREAM (stream)->fps_n);
+ GST_BUFFER_DURATION (outbuf) =
+ gst_util_uint64_scale (GST_SECOND, CUR_STREAM (stream)->fps_d,
+ CUR_STREAM (stream)->fps_n);
+ GST_BUFFER_OFFSET (outbuf) = -1;
+ GST_BUFFER_OFFSET_END (outbuf) = -1;
+
+ ret = gst_qtdemux_push_buffer (qtdemux, stream, outbuf);
+
+ if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)
+ break;
+ }
+ gst_buffer_unmap (buf, &map);
+ gst_buffer_unref (buf);
+ } else {
+ gst_buffer_unmap (buf, &map);
+ ret = gst_qtdemux_push_buffer (qtdemux, stream, buf);
+ }
+ } else {
+ ret = gst_qtdemux_push_buffer (qtdemux, stream, buf);
+ }
+
+ return ret;
+}
+
/* Sets a buffer's attributes properly and pushes it downstream.
* Also checks for additional actions and custom processing that may
* need to be done first.
GST_BUFFER_OFFSET (buf) = -1;
GST_BUFFER_OFFSET_END (buf) = -1;
+ if (!keyframe) {
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
+ stream->on_keyframe = FALSE;
+ } else {
+ stream->on_keyframe = TRUE;
+ }
+
if (G_UNLIKELY (CUR_STREAM (stream)->rgb8_palette))
gst_buffer_append_memory (buf,
gst_memory_ref (CUR_STREAM (stream)->rgb8_palette));
}
#endif
- if (stream->need_clip)
- buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf);
-
- if (G_UNLIKELY (buf == NULL))
- goto exit;
-
- if (G_UNLIKELY (stream->discont)) {
- GST_LOG_OBJECT (qtdemux, "marking discont buffer");
- GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
- stream->discont = FALSE;
- } else {
- GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
- }
-
- if (!keyframe) {
- GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
- stream->on_keyframe = FALSE;
- } else {
- stream->on_keyframe = TRUE;
- }
-
-
- GST_LOG_OBJECT (qtdemux,
- "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
- ", duration %" GST_TIME_FORMAT " on pad %s", GST_TIME_ARGS (dts),
- GST_TIME_ARGS (pts), GST_TIME_ARGS (duration),
- GST_PAD_NAME (stream->pad));
-
- if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
- GstStructure *crypto_info;
- QtDemuxCencSampleSetInfo *info =
- (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
- gint index;
- GstEvent *event;
-
- while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
- gst_pad_push_event (stream->pad, event);
- }
-
- if (info->crypto_info == NULL) {
- GST_DEBUG_OBJECT (qtdemux, "cenc metadata hasn't been parsed yet");
- gst_buffer_unref (buf);
- goto exit;
- }
-
- /* The end of the crypto_info array matches our n_samples position,
- * so count backward from there */
- index = stream->sample_index - stream->n_samples + info->crypto_info->len;
- if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
- /* steal structure from array */
- crypto_info = g_ptr_array_index (info->crypto_info, index);
- g_ptr_array_index (info->crypto_info, index) = NULL;
- GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u/%u]", index,
- info->crypto_info->len);
- if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
- GST_ERROR_OBJECT (qtdemux, "failed to attach cenc metadata to buffer");
- } else {
- GST_INFO_OBJECT (qtdemux, "No crypto info with index %d and sample %d",
- index, stream->sample_index);
- }
- }
-
- if (stream->alignment > 1)
- buf = gst_qtdemux_align_buffer (qtdemux, buf, stream->alignment);
-
- ret = gst_pad_push (stream->pad, buf);
-
- if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
- /* mark position in stream, we'll need this to know when to send GAP event */
- stream->segment.position = pts + duration;
- }
+ ret = gst_qtdemux_split_and_push_buffer (qtdemux, stream, buf);
exit:
return ret;
gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux)
{
const QtDemuxRandomAccessEntry *best_entry = NULL;
- guint i;
+ gint i;
GST_OBJECT_LOCK (qtdemux);
- g_assert (qtdemux->n_streams > 0);
+ g_assert (QTDEMUX_N_STREAMS (qtdemux) > 0);
- for (i = 0; i < qtdemux->n_streams; i++) {
+ /* first see if we can determine where to go to using mfra,
+ * before we start clearing things */
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
const QtDemuxRandomAccessEntry *entry;
QtDemuxStream *stream;
gboolean is_audio_or_video;
- stream = qtdemux->streams[i];
-
- g_free (stream->samples);
- stream->samples = NULL;
- stream->n_samples = 0;
- stream->stbl_index = -1; /* no samples have yet been parsed */
- stream->sample_index = -1;
-
- if (stream->protection_scheme_info) {
- /* Clear out any old cenc crypto info entries as we'll move to a new moof */
- if (stream->protection_scheme_type == FOURCC_cenc) {
- QtDemuxCencSampleSetInfo *info =
- (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
- if (info->crypto_info) {
- g_ptr_array_free (info->crypto_info, TRUE);
- info->crypto_info = NULL;
- }
- }
- }
+ stream = QTDEMUX_NTH_STREAM (qtdemux, i);
if (stream->ra_entries == NULL)
continue;
best_entry = entry;
}
+ /* no luck, will handle seek otherwise */
if (best_entry == NULL) {
GST_OBJECT_UNLOCK (qtdemux);
return FALSE;
}
+ /* ok, now we can prepare for processing as of located moof */
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream;
+
+ stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+ g_free (stream->samples);
+ stream->samples = NULL;
+ stream->n_samples = 0;
+ stream->stbl_index = -1; /* no samples have yet been parsed */
+ stream->sample_index = -1;
+
+ if (stream->protection_scheme_info) {
+ /* Clear out any old cenc crypto info entries as we'll move to a new moof */
+ if (stream->protection_scheme_type == FOURCC_cenc) {
+ QtDemuxCencSampleSetInfo *info =
+ (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+ if (info->crypto_info) {
+ g_ptr_array_free (info->crypto_info, TRUE);
+ info->crypto_info = NULL;
+ }
+ }
+ }
+ }
+
GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment "
"moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT,
- GST_TIME_ARGS (qtdemux->streams[0]->time_position),
+ GST_TIME_ARGS (QTDEMUX_NTH_STREAM (qtdemux, 0)->time_position),
best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts));
qtdemux->moof_offset = best_entry->moof_offset;
{
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *buf = NULL;
- QtDemuxStream *stream;
+ QtDemuxStream *stream, *target_stream = NULL;
GstClockTime min_time;
guint64 offset = 0;
GstClockTime dts = GST_CLOCK_TIME_NONE;
guint sample_size = 0;
gboolean empty = 0;
guint size;
- gint index;
gint i;
- gst_qtdemux_push_pending_newsegment (qtdemux);
-
if (qtdemux->fragmented_seek_pending) {
GST_INFO_OBJECT (qtdemux, "pending fragmented seek");
- gst_qtdemux_do_fragmented_seek (qtdemux);
- GST_INFO_OBJECT (qtdemux, "fragmented seek done!");
- qtdemux->fragmented_seek_pending = FALSE;
+ if (gst_qtdemux_do_fragmented_seek (qtdemux)) {
+ GST_INFO_OBJECT (qtdemux, "fragmented seek done!");
+ qtdemux->fragmented_seek_pending = FALSE;
+ } else {
+ GST_INFO_OBJECT (qtdemux, "fragmented seek still pending");
+ }
}
/* Figure out the next stream sample to output, min_time is expressed in
* global time and runs over the edit list segments. */
min_time = G_MAXUINT64;
- index = -1;
- for (i = 0; i < qtdemux->n_streams; i++) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
GstClockTime position;
- stream = qtdemux->streams[i];
+ stream = QTDEMUX_NTH_STREAM (qtdemux, i);
position = stream->time_position;
/* position of -1 is EOS */
if (position != GST_CLOCK_TIME_NONE && position < min_time) {
min_time = position;
- index = i;
+ target_stream = stream;
}
}
/* all are EOS */
- if (G_UNLIKELY (index == -1)) {
+ if (G_UNLIKELY (target_stream == NULL)) {
GST_DEBUG_OBJECT (qtdemux, "all streams are EOS");
goto eos;
}
&& ((qtdemux->segment.rate >= 0 && qtdemux->segment.stop <= min_time)
|| (qtdemux->segment.rate < 0
&& qtdemux->segment.start > min_time))
- && qtdemux->streams[index]->on_keyframe)) {
+ && target_stream->on_keyframe)) {
GST_DEBUG_OBJECT (qtdemux, "we reached the end of our segment.");
- qtdemux->streams[index]->time_position = GST_CLOCK_TIME_NONE;
+ target_stream->time_position = GST_CLOCK_TIME_NONE;
goto eos_stream;
}
/* gap events for subtitle streams */
- for (i = 0; i < qtdemux->n_streams; i++) {
- stream = qtdemux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ stream = QTDEMUX_NTH_STREAM (qtdemux, i);
if (stream->pad && (stream->subtype == FOURCC_subp
|| stream->subtype == FOURCC_text
|| stream->subtype == FOURCC_sbtl)) {
}
}
- stream = qtdemux->streams[index];
+ 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)))
gst_qtdemux_stream_check_and_change_stsd_index (qtdemux, stream);
if (stream->new_caps) {
gst_qtdemux_configure_stream (qtdemux, stream);
- qtdemux_do_allocation (qtdemux, stream);
+ qtdemux_do_allocation (stream, qtdemux);
}
/* If we're doing a keyframe-only trickmode, only push keyframes on video streams */
- if (G_UNLIKELY (qtdemux->
- segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
+ if (G_UNLIKELY (qtdemux->segment.
+ flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
if (stream->subtype == FOURCC_vide && !keyframe) {
- GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on stream %d", index);
+ GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on track-id %u",
+ stream->track_id);
goto next;
}
}
GST_DEBUG_OBJECT (qtdemux,
- "pushing from stream %d, empty %d offset %" G_GUINT64_FORMAT
+ "pushing from track-id %u, empty %d offset %" G_GUINT64_FORMAT
", size %d, dts=%" GST_TIME_FORMAT ", pts=%" GST_TIME_FORMAT
- ", duration %" GST_TIME_FORMAT, index, empty, offset, sample_size,
- GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), GST_TIME_ARGS (duration));
+ ", duration %" GST_TIME_FORMAT, stream->track_id, empty, offset,
+ sample_size, GST_TIME_ARGS (dts), GST_TIME_ARGS (pts),
+ GST_TIME_ARGS (duration));
if (G_UNLIKELY (empty)) {
- /* empty segment, push a gap and move to the next one */
- gst_pad_push_event (stream->pad, gst_event_new_gap (pts, duration));
+ /* empty segment, push a gap if there's a second or more
+ * difference and move to the next one */
+ if ((pts + duration - stream->segment.position) >= GST_SECOND)
+ gst_pad_push_event (stream->pad, gst_event_new_gap (pts, duration));
stream->segment.position = pts + duration;
goto next;
}
/* fatal errors need special actions */
/* check EOS */
if (ret == GST_FLOW_EOS) {
- if (qtdemux->n_streams == 0) {
+ if (QTDEMUX_N_STREAMS (qtdemux) == 0) {
/* we have no streams, post an error */
gst_qtdemux_post_no_playable_stream_error (qtdemux);
}
message = gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux),
GST_FORMAT_TIME, stop);
event = gst_event_new_segment_done (GST_FORMAT_TIME, stop);
- if (qtdemux->segment_seqnum) {
+ if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) {
gst_message_set_seqnum (message, qtdemux->segment_seqnum);
gst_event_set_seqnum (event, qtdemux->segment_seqnum);
}
GST_FORMAT_TIME, qtdemux->segment.start);
event = gst_event_new_segment_done (GST_FORMAT_TIME,
qtdemux->segment.start);
- if (qtdemux->segment_seqnum) {
+ if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) {
gst_message_set_seqnum (message, qtdemux->segment_seqnum);
gst_event_set_seqnum (event, qtdemux->segment_seqnum);
}
GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment");
event = gst_event_new_eos ();
- if (qtdemux->segment_seqnum)
+ if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID)
gst_event_set_seqnum (event, qtdemux->segment_seqnum);
gst_qtdemux_push_event (qtdemux, event);
}
has_next_entry (GstQTDemux * demux)
{
QtDemuxStream *stream;
- int i;
+ gint i;
GST_DEBUG_OBJECT (demux, "Checking if there are samples not played yet");
- for (i = 0; i < demux->n_streams; i++) {
- stream = demux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ stream = QTDEMUX_NTH_STREAM (demux, i);
if (stream->sample_index == -1) {
stream->sample_index = 0;
}
if (stream->sample_index >= stream->n_samples) {
- GST_LOG_OBJECT (demux, "stream %d samples exhausted", i);
+ GST_LOG_OBJECT (demux, "track-id %u samples exhausted", stream->track_id);
continue;
}
GST_DEBUG_OBJECT (demux, "Found a sample");
static guint64
next_entry_size (GstQTDemux * demux)
{
- QtDemuxStream *stream;
- int i;
- int smallidx = -1;
+ QtDemuxStream *stream, *target_stream = NULL;
guint64 smalloffs = (guint64) - 1;
QtDemuxSample *sample;
+ gint i;
GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT,
demux->offset);
- for (i = 0; i < demux->n_streams; i++) {
- stream = demux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ stream = QTDEMUX_NTH_STREAM (demux, i);
if (stream->sample_index == -1) {
stream->sample_index = 0;
}
if (stream->sample_index >= stream->n_samples) {
- GST_LOG_OBJECT (demux, "stream %d samples exhausted", i);
+ GST_LOG_OBJECT (demux, "track-id %u samples exhausted", stream->track_id);
continue;
}
sample = &stream->samples[stream->sample_index];
GST_LOG_OBJECT (demux,
- "Checking Stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT
- " / size:%" G_GUINT32_FORMAT ")", i, stream->sample_index,
- sample->offset, sample->size);
+ "Checking track-id %u (sample_index:%d / offset:%" G_GUINT64_FORMAT
+ " / size:%" G_GUINT32_FORMAT ")", stream->track_id,
+ stream->sample_index, sample->offset, sample->size);
if (((smalloffs == -1)
|| (sample->offset < smalloffs)) && (sample->size)) {
- smallidx = i;
smalloffs = sample->offset;
+ target_stream = stream;
}
}
- GST_LOG_OBJECT (demux,
- "stream %d offset %" G_GUINT64_FORMAT " demux->offset :%"
- G_GUINT64_FORMAT, smallidx, smalloffs, demux->offset);
-
- if (smallidx == -1)
+ if (!target_stream)
return -1;
- stream = demux->streams[smallidx];
+ GST_LOG_OBJECT (demux,
+ "track-id %u offset %" G_GUINT64_FORMAT " demux->offset :%"
+ G_GUINT64_FORMAT, target_stream->track_id, smalloffs, demux->offset);
+
+ stream = target_stream;
sample = &stream->samples[stream->sample_index];
if (sample->offset >= demux->offset) {
/* store seqnum to drop flush events, they don't need to reach downstream */
demux->offset_seek_seqnum = gst_event_get_seqnum (event);
res = gst_pad_push_event (demux->sinkpad, event);
- demux->offset_seek_seqnum = 0;
+ demux->offset_seek_seqnum = GST_SEQNUM_INVALID;
return res;
}
demux->todrop -= bytes;
}
+/* PUSH-MODE only: Send a segment, if not done already. */
static void
gst_qtdemux_check_send_pending_segment (GstQTDemux * demux)
{
- if (G_UNLIKELY (demux->pending_newsegment)) {
+ if (G_UNLIKELY (demux->need_segment)) {
gint i;
- gst_qtdemux_push_pending_newsegment (demux);
+ if (!demux->upstream_format_is_time) {
+ gst_qtdemux_map_and_push_segments (demux, &demux->segment);
+ } else {
+ GstEvent *segment_event;
+ segment_event = gst_event_new_segment (&demux->segment);
+ if (demux->segment_seqnum != GST_SEQNUM_INVALID)
+ gst_event_set_seqnum (segment_event, demux->segment_seqnum);
+ gst_qtdemux_push_event (demux, segment_event);
+ }
+
+ demux->need_segment = FALSE;
+
/* clear to send tags on all streams */
- for (i = 0; i < demux->n_streams; i++) {
- QtDemuxStream *stream;
- stream = demux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (demux, i);
gst_qtdemux_push_tags (demux, stream);
if (CUR_STREAM (stream)->sparse) {
GST_INFO_OBJECT (demux, "Sending gap event on stream %d", i);
}
}
+/* Used for push mode only. */
static void
gst_qtdemux_send_gap_for_segment (GstQTDemux * demux,
QtDemuxStream * stream, gint segment_index, GstClockTime pos)
{
GstClockTime ts, dur;
- GstEvent *gap;
ts = pos;
dur =
stream->segments[segment_index].duration - (pos -
stream->segments[segment_index].time);
- gap = gst_event_new_gap (ts, dur);
stream->time_position += dur;
- GST_DEBUG_OBJECT (stream->pad, "Pushing gap for empty "
- "segment: %" GST_PTR_FORMAT, gap);
- gst_pad_push_event (stream->pad, gap);
-}
+ /* Only gaps with a duration of at least one second are propagated.
+ * Same workaround as in pull mode.
+ * (See 2e45926a96ec5298c6ef29bf912e5e6a06dc3e0e) */
+ if (dur >= GST_SECOND) {
+ GstEvent *gap;
+ gap = gst_event_new_gap (ts, dur);
-static void
-gst_qtdemux_stream_send_initial_gap_segments (GstQTDemux * demux,
- QtDemuxStream * stream)
-{
- gint i;
-
- /* Push any initial gap segments before proceeding to the
- * 'real' data */
- for (i = 0; i < stream->n_segments; i++) {
- gst_qtdemux_activate_segment (demux, stream, i, stream->time_position);
-
- if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) {
- gst_qtdemux_send_gap_for_segment (demux, stream, i,
- stream->time_position);
- } else {
- /* Only support empty segment at the beginning followed by
- * one non-empty segment, this was checked when parsing the
- * edts atom, arriving here is unexpected */
- g_assert (i + 1 == stream->n_segments);
- break;
- }
+ GST_DEBUG_OBJECT (stream->pad, "Pushing gap for empty "
+ "segment: %" GST_PTR_FORMAT, gap);
+ gst_pad_push_event (stream->pad, gap);
}
}
GST_DEBUG_OBJECT (demux, "Got DISCONT, marking all streams as DISCONT");
- for (i = 0; i < demux->n_streams; i++) {
- demux->streams[i]->discont = TRUE;
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ QTDEMUX_NTH_STREAM (demux, i)->discont = TRUE;
}
/* Check if we can land back on our feet in the case where upstream is
* in the case of trick-mode DASH for example) */
if (demux->upstream_format_is_time
&& GST_BUFFER_OFFSET (inbuf) != GST_BUFFER_OFFSET_NONE) {
- gint i;
- for (i = 0; i < demux->n_streams; i++) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
guint32 res;
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (demux, i);
GST_LOG_OBJECT (demux,
- "Stream #%d , checking if offset %" G_GUINT64_FORMAT
- " is a sample start", i, GST_BUFFER_OFFSET (inbuf));
+ "track-id #%u , checking if offset %" G_GUINT64_FORMAT
+ " is a sample start", stream->track_id, GST_BUFFER_OFFSET (inbuf));
res =
gst_qtdemux_find_index_for_given_media_offset_linear (demux,
- demux->streams[i], GST_BUFFER_OFFSET (inbuf));
+ stream, GST_BUFFER_OFFSET (inbuf));
if (res != -1) {
- QtDemuxSample *sample = &demux->streams[i]->samples[res];
+ QtDemuxSample *sample = &stream->samples[res];
GST_LOG_OBJECT (demux,
- "Checking if sample %d from stream %d is valid (offset:%"
- G_GUINT64_FORMAT " size:%" G_GUINT32_FORMAT ")", res, i,
- sample->offset, sample->size);
+ "Checking if sample %d from track-id %u is valid (offset:%"
+ G_GUINT64_FORMAT " size:%" G_GUINT32_FORMAT ")", res,
+ stream->track_id, sample->offset, sample->size);
if (sample->offset == GST_BUFFER_OFFSET (inbuf)) {
GST_LOG_OBJECT (demux,
"new buffer corresponds to a valid sample : %" G_GUINT32_FORMAT,
/* We can go back to standard playback mode */
demux->state = QTDEMUX_STATE_MOVIE;
/* Remember which sample this stream is at */
- demux->streams[i]->sample_index = res;
+ stream->sample_index = res;
/* Finally update all push-based values to the expected values */
- demux->neededbytes = demux->streams[i]->samples[res].size;
+ demux->neededbytes = stream->samples[res].size;
demux->offset = GST_BUFFER_OFFSET (inbuf);
demux->mdatleft =
demux->mdatsize - demux->offset + demux->mdatoffset;
* previously received one. */
if (!is_gap_input && demux->fragmented && demux->segment.rate < 0) {
gst_qtdemux_process_adapter (demux, TRUE);
- for (i = 0; i < demux->n_streams; i++)
- gst_qtdemux_stream_flush_samples_data (demux, demux->streams[i]);
+ g_ptr_array_foreach (demux->active_streams,
+ (GFunc) gst_qtdemux_stream_flush_samples_data, NULL);
}
}
}
if (fourcc == FOURCC_mdat) {
gint next_entry = next_entry_size (demux);
- if (demux->n_streams > 0 && (next_entry != -1 || !demux->fragmented)) {
+ if (QTDEMUX_N_STREAMS (demux) > 0 && (next_entry != -1
+ || !demux->fragmented)) {
/* we have the headers, start playback */
demux->state = QTDEMUX_STATE_MOVIE;
demux->neededbytes = next_entry;
extract_initial_length_and_fourcc (data, demux->neededbytes, NULL,
&fourcc);
if (fourcc == FOURCC_moov) {
- gint n;
-
/* in usual fragmented setup we could try to scan for more
* and end up at the the moov (after mdat) again */
- if (demux->got_moov && demux->n_streams > 0 &&
+ if (demux->got_moov && QTDEMUX_N_STREAMS (demux) > 0 &&
(!demux->fragmented
|| demux->last_moov_offset == demux->offset)) {
GST_DEBUG_OBJECT (demux,
if (demux->moov_node)
g_node_destroy (demux->moov_node);
demux->moov_node = NULL;
- } else {
- /* prepare newsegment to send when streaming actually starts */
- if (!demux->pending_newsegment) {
- demux->pending_newsegment =
- gst_event_new_segment (&demux->segment);
- if (demux->segment_seqnum)
- gst_event_set_seqnum (demux->pending_newsegment,
- demux->segment_seqnum);
- }
}
demux->last_moov_offset = demux->offset;
+ /* Update streams with new moov */
+ gst_qtdemux_stream_concat (demux,
+ demux->old_streams, demux->active_streams);
+
qtdemux_parse_moov (demux, data, demux->neededbytes);
qtdemux_node_dump (demux, demux->moov_node);
qtdemux_parse_tree (demux);
qtdemux_prepare_streams (demux);
- if (!demux->got_moov)
- qtdemux_expose_streams (demux);
- else {
-
- for (n = 0; n < demux->n_streams; n++) {
- QtDemuxStream *stream = demux->streams[n];
-
- gst_qtdemux_configure_stream (demux, stream);
- }
- }
+ QTDEMUX_EXPOSE_LOCK (demux);
+ qtdemux_expose_streams (demux);
+ QTDEMUX_EXPOSE_UNLOCK (demux);
demux->got_moov = TRUE;
- gst_qtdemux_check_send_pending_segment (demux);
- /* fragmented streams headers shouldn't contain edts atoms */
- if (!demux->fragmented) {
- for (n = 0; n < demux->n_streams; n++) {
- gst_qtdemux_stream_send_initial_gap_segments (demux,
- demux->streams[n]);
- }
- }
+ gst_qtdemux_check_send_pending_segment (demux);
if (demux->moov_node_compressed) {
g_node_destroy (demux->moov_node_compressed);
ret = GST_FLOW_ERROR;
goto done;
}
+
/* in MSS we need to expose the pads after the first moof as we won't get a moov */
if (demux->mss_mode && !demux->exposed) {
- if (!demux->pending_newsegment) {
- GST_DEBUG_OBJECT (demux, "new pending_newsegment");
- demux->pending_newsegment =
- gst_event_new_segment (&demux->segment);
- if (demux->segment_seqnum)
- gst_event_set_seqnum (demux->pending_newsegment,
- demux->segment_seqnum);
- }
+ QTDEMUX_EXPOSE_LOCK (demux);
qtdemux_expose_streams (demux);
+ QTDEMUX_EXPOSE_UNLOCK (demux);
}
+
+ gst_qtdemux_check_send_pending_segment (demux);
} else {
GST_DEBUG_OBJECT (demux, "Discarding [moof]");
}
gst_adapter_unmap (demux->adapter);
data = NULL;
- if (demux->mdatbuffer && demux->n_streams) {
+ if (demux->mdatbuffer && QTDEMUX_N_STREAMS (demux)) {
gsize remaining_data_size = 0;
/* the mdat was before the header */
GST_DEBUG_OBJECT (demux, "We have n_streams:%d and mdatbuffer:%p",
- demux->n_streams, demux->mdatbuffer);
+ QTDEMUX_N_STREAMS (demux), demux->mdatbuffer);
/* restore our adapter/offset view of things with upstream;
* put preceding buffered data ahead of current moov data.
* This should also handle evil mdat, moov, mdat cases and alike */
case QTDEMUX_STATE_MOVIE:{
QtDemuxStream *stream = NULL;
QtDemuxSample *sample;
- int i = -1;
GstClockTime dts, pts, duration;
gboolean keyframe;
+ gint i;
GST_DEBUG_OBJECT (demux,
"BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset);
GST_DEBUG_OBJECT (demux, "parsing cenc auxiliary info");
data = gst_adapter_map (demux->adapter, demux->todrop);
gst_byte_reader_init (&br, data + 8, demux->todrop);
- if (!qtdemux_parse_cenc_aux_info (demux, demux->streams[0], &br,
+ if (!qtdemux_parse_cenc_aux_info (demux,
+ QTDEMUX_NTH_STREAM (demux, 0), &br,
demux->cenc_aux_info_sizes, demux->cenc_aux_sample_count)) {
GST_ERROR_OBJECT (demux, "failed to parse cenc auxiliary info");
ret = GST_FLOW_ERROR;
gst_qtdemux_check_send_pending_segment (demux);
/* Figure out which stream this packet belongs to */
- for (i = 0; i < demux->n_streams; i++) {
- stream = demux->streams[i];
- if (stream->sample_index >= stream->n_samples)
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ stream = QTDEMUX_NTH_STREAM (demux, i);
+ if (stream->sample_index >= stream->n_samples) {
+ /* reset to be checked below G_UNLIKELY (stream == NULL) */
+ stream = NULL;
continue;
+ }
GST_LOG_OBJECT (demux,
- "Checking stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT
- " / size:%d)", i, stream->sample_index,
+ "Checking track-id %u (sample_index:%d / offset:%"
+ G_GUINT64_FORMAT " / size:%d)", stream->track_id,
+ stream->sample_index,
stream->samples[stream->sample_index].offset,
stream->samples[stream->sample_index].size);
break;
}
- if (G_UNLIKELY (stream == NULL || i == demux->n_streams))
+ if (G_UNLIKELY (stream == NULL))
goto unknown_stream;
gst_qtdemux_stream_check_and_change_stsd_index (demux, stream);
/* check for segment end */
if (G_UNLIKELY (demux->segment.stop != -1
- && demux->segment.stop <= pts && stream->on_keyframe)) {
+ && demux->segment.stop <= pts && stream->on_keyframe)
+ && !(demux->upstream_format_is_time && demux->segment.rate < 0)) {
GST_DEBUG_OBJECT (demux, "we reached the end of our segment.");
stream->time_position = GST_CLOCK_TIME_NONE; /* this means EOS */
/* check if all streams are eos */
ret = GST_FLOW_EOS;
- for (i = 0; i < demux->n_streams; i++) {
- if (!STREAM_IS_EOS (demux->streams[i])) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+ if (!STREAM_IS_EOS (QTDEMUX_NTH_STREAM (demux, i))) {
ret = GST_FLOW_OK;
break;
}
case FOURCC_meta:
{
GST_DEBUG_OBJECT (qtdemux, "parsing meta atom");
- qtdemux_parse_container (qtdemux, node, buffer + 12, end);
+
+ /* You are reading this correctly. QTFF specifies that the
+ * metadata atom is a short atom, whereas ISO BMFF specifies
+ * it's a full atom. But since so many people are doing things
+ * differently, we actually peek into the atom to see which
+ * variant it is */
+ if (length < 16) {
+ GST_LOG_OBJECT (qtdemux, "skipping small %" GST_FOURCC_FORMAT " box",
+ GST_FOURCC_ARGS (fourcc));
+ break;
+ }
+ if (QT_FOURCC (buffer + 12) == FOURCC_hdlr) {
+ /* Variant 1: What QTFF specifies. 'meta' is a short header which
+ * starts with a 'hdlr' atom */
+ qtdemux_parse_container (qtdemux, node, buffer + 8, end);
+ } else if (QT_UINT32 (buffer + 8) == 0x00000000) {
+ /* Variant 2: What ISO BMFF specifies. 'meta' is a _full_ atom
+ * with version/flags both set to zero */
+ qtdemux_parse_container (qtdemux, node, buffer + 12, end);
+ } else
+ GST_WARNING_OBJECT (qtdemux, "Unknown 'meta' atom format");
break;
}
case FOURCC_mp4s:
}
static void
-qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream)
+qtdemux_do_allocation (QtDemuxStream * stream, GstQTDemux * qtdemux)
{
/* FIXME: This can only reliably work if demuxers have a
* separate streaming thread per srcpad. This should be
}
static gboolean
+pad_query (const GValue * item, GValue * value, gpointer user_data)
+{
+ GstPad *pad = g_value_get_object (item);
+ GstQuery *query = user_data;
+ gboolean res;
+
+ res = gst_pad_peer_query (pad, query);
+
+ if (res) {
+ g_value_set_boolean (value, TRUE);
+ return FALSE;
+ }
+
+ GST_INFO_OBJECT (pad, "pad peer query failed");
+ return TRUE;
+}
+
+static gboolean
+gst_qtdemux_run_query (GstElement * element, GstQuery * query,
+ GstPadDirection direction)
+{
+ GstIterator *it;
+ GstIteratorFoldFunction func = pad_query;
+ GValue res = { 0, };
+
+ g_value_init (&res, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&res, FALSE);
+
+ /* Ask neighbor */
+ if (direction == GST_PAD_SRC)
+ it = gst_element_iterate_src_pads (element);
+ else
+ it = gst_element_iterate_sink_pads (element);
+
+ while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC)
+ gst_iterator_resync (it);
+
+ gst_iterator_free (it);
+
+ return g_value_get_boolean (&res);
+}
+
+static void
+gst_qtdemux_request_protection_context (GstQTDemux * qtdemux,
+ QtDemuxStream * stream)
+{
+ GstQuery *query;
+ GstContext *ctxt;
+ GstElement *element = GST_ELEMENT (qtdemux);
+ GstStructure *st;
+ gchar **filtered_sys_ids;
+ GValue event_list = G_VALUE_INIT;
+ GList *walk;
+
+ /* 1. Check if we already have the context. */
+ if (qtdemux->preferred_protection_system_id != NULL) {
+ GST_LOG_OBJECT (element,
+ "already have the protection context, no need to request it again");
+ return;
+ }
+
+ g_ptr_array_add (qtdemux->protection_system_ids, NULL);
+ filtered_sys_ids = gst_protection_filter_systems_by_available_decryptors (
+ (const gchar **) qtdemux->protection_system_ids->pdata);
+
+ g_ptr_array_remove_index (qtdemux->protection_system_ids,
+ qtdemux->protection_system_ids->len - 1);
+ GST_TRACE_OBJECT (qtdemux, "detected %u protection systems, we have "
+ "decryptors for %u of them, running context request",
+ qtdemux->protection_system_ids->len,
+ filtered_sys_ids ? g_strv_length (filtered_sys_ids) : 0);
+
+
+ if (stream->protection_scheme_event_queue.length) {
+ GST_TRACE_OBJECT (qtdemux, "using stream event queue, length %u",
+ stream->protection_scheme_event_queue.length);
+ walk = stream->protection_scheme_event_queue.tail;
+ } else {
+ GST_TRACE_OBJECT (qtdemux, "using demuxer event queue, length %u",
+ qtdemux->protection_event_queue.length);
+ walk = qtdemux->protection_event_queue.tail;
+ }
+
+ 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);
+ }
+
+ /* 2a) Query downstream with GST_QUERY_CONTEXT for the context and
+ * check if downstream already has a context of the specific type
+ * 2b) Query upstream as above.
+ */
+ query = gst_query_new_context ("drm-preferred-decryption-system-id");
+ st = gst_query_writable_structure (query);
+ gst_structure_set (st, "track-id", G_TYPE_UINT, stream->track_id,
+ "available-stream-encryption-systems", G_TYPE_STRV, filtered_sys_ids,
+ NULL);
+ gst_structure_set_value (st, "stream-encryption-events", &event_list);
+ if (gst_qtdemux_run_query (element, query, GST_PAD_SRC)) {
+ gst_query_parse_context (query, &ctxt);
+ GST_INFO_OBJECT (element, "found context (%p) in downstream query", ctxt);
+ gst_element_set_context (element, ctxt);
+ } else if (gst_qtdemux_run_query (element, query, GST_PAD_SINK)) {
+ gst_query_parse_context (query, &ctxt);
+ GST_INFO_OBJECT (element, "found context (%p) in upstream query", ctxt);
+ gst_element_set_context (element, ctxt);
+ } else {
+ /* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with
+ * the required context type and afterwards check if a
+ * usable context was set now as in 1). The message could
+ * be handled by the parent bins of the element and the
+ * application.
+ */
+ GstMessage *msg;
+
+ GST_INFO_OBJECT (element, "posting need context message");
+ msg = gst_message_new_need_context (GST_OBJECT_CAST (element),
+ "drm-preferred-decryption-system-id");
+ st = (GstStructure *) gst_message_get_structure (msg);
+ gst_structure_set (st, "track-id", G_TYPE_UINT, stream->track_id,
+ "available-stream-encryption-systems", G_TYPE_STRV, filtered_sys_ids,
+ NULL);
+
+ gst_structure_set_value (st, "stream-encryption-events", &event_list);
+ gst_element_post_message (element, msg);
+ }
+
+ g_strfreev (filtered_sys_ids);
+ g_value_unset (&event_list);
+ gst_query_unref (query);
+}
+
+static gboolean
gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
QtDemuxStream * stream)
{
GstStructure *s;
- const gchar *selected_system;
+ const gchar *selected_system = NULL;
g_return_val_if_fail (qtdemux != NULL, FALSE);
g_return_val_if_fail (stream != NULL, FALSE);
FALSE);
if (stream->protection_scheme_type != FOURCC_cenc) {
- GST_ERROR_OBJECT (qtdemux, "unsupported protection scheme");
+ GST_ERROR_OBJECT (qtdemux,
+ "unsupported protection scheme: %" GST_FOURCC_FORMAT,
+ GST_FOURCC_ARGS (stream->protection_scheme_type));
return FALSE;
}
if (qtdemux->protection_system_ids == NULL) {
"cenc protection system information has been found");
return FALSE;
}
- g_ptr_array_add (qtdemux->protection_system_ids, NULL);
- selected_system = gst_protection_select_system ((const gchar **)
- qtdemux->protection_system_ids->pdata);
- g_ptr_array_remove_index (qtdemux->protection_system_ids,
- qtdemux->protection_system_ids->len - 1);
+
+ gst_qtdemux_request_protection_context (qtdemux, stream);
+ if (qtdemux->preferred_protection_system_id != NULL) {
+ const gchar *preferred_system_array[] =
+ { qtdemux->preferred_protection_system_id, NULL };
+
+ selected_system = gst_protection_select_system (preferred_system_array);
+
+ if (selected_system) {
+ GST_TRACE_OBJECT (qtdemux, "selected preferred system %s",
+ qtdemux->preferred_protection_system_id);
+ } else {
+ GST_WARNING_OBJECT (qtdemux, "could not select preferred system %s "
+ "because there is no available decryptor",
+ qtdemux->preferred_protection_system_id);
+ }
+ }
+
+ if (!selected_system) {
+ g_ptr_array_add (qtdemux->protection_system_ids, NULL);
+ selected_system = gst_protection_select_system ((const gchar **)
+ qtdemux->protection_system_ids->pdata);
+ g_ptr_array_remove_index (qtdemux->protection_system_ids,
+ qtdemux->protection_system_ids->len - 1);
+ }
+
if (!selected_system) {
GST_ERROR_OBJECT (qtdemux, "stream is protected, but no "
"suitable decryptor element has been found");
return FALSE;
}
+ GST_DEBUG_OBJECT (qtdemux, "selected protection system is %s",
+ selected_system);
+
s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
if (!gst_structure_has_name (s, "application/x-cenc")) {
gst_structure_set (s,
}
static gboolean
-gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_guess_framerate (GstQTDemux * qtdemux, QtDemuxStream * stream)
{
- if (stream->subtype == FOURCC_vide) {
- /* fps is calculated base on the duration of the average framerate since
- * qt does not have a fixed framerate. */
- gboolean fps_available = TRUE;
+ /* fps is calculated base on the duration of the average framerate since
+ * qt does not have a fixed framerate. */
+ gboolean fps_available = TRUE;
+ guint32 first_duration = 0;
+
+ if (stream->n_samples > 0)
+ first_duration = stream->samples[0].duration;
- if ((stream->n_samples == 1) && (stream->first_duration == 0)) {
- /* still frame */
- CUR_STREAM (stream)->fps_n = 0;
+ if ((stream->n_samples == 1 && first_duration == 0)
+ || (qtdemux->fragmented && stream->n_samples_moof == 1)) {
+ /* still frame */
+ CUR_STREAM (stream)->fps_n = 0;
+ CUR_STREAM (stream)->fps_d = 1;
+ } else {
+ if (stream->duration == 0 || stream->n_samples < 2) {
+ CUR_STREAM (stream)->fps_n = stream->timescale;
CUR_STREAM (stream)->fps_d = 1;
+ fps_available = FALSE;
} else {
- if (stream->duration == 0 || stream->n_samples < 2) {
- CUR_STREAM (stream)->fps_n = stream->timescale;
- CUR_STREAM (stream)->fps_d = 1;
- fps_available = FALSE;
+ GstClockTime avg_duration;
+ guint64 duration;
+ guint32 n_samples;
+
+ /* duration and n_samples can be updated for fragmented format
+ * so, framerate of fragmented format is calculated using data in a moof */
+ if (qtdemux->fragmented && stream->n_samples_moof > 0
+ && stream->duration_moof > 0) {
+ n_samples = stream->n_samples_moof;
+ duration = stream->duration_moof;
} else {
- GstClockTime avg_duration;
- guint64 duration;
- guint32 n_samples;
-
- /* duration and n_samples can be updated for fragmented format
- * so, framerate of fragmented format is calculated using data in a moof */
- if (qtdemux->fragmented && stream->n_samples_moof > 0
- && stream->duration_moof > 0) {
- n_samples = stream->n_samples_moof;
- duration = stream->duration_moof;
- } else {
- n_samples = stream->n_samples;
- duration = stream->duration;
- }
+ n_samples = stream->n_samples;
+ duration = stream->duration;
+ }
- /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
- /* stream->duration is guint64, timescale, n_samples are guint32 */
- avg_duration =
- gst_util_uint64_scale_round (duration -
- stream->first_duration, GST_SECOND,
- (guint64) (stream->timescale) * (n_samples - 1));
+ /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
+ /* stream->duration is guint64, timescale, n_samples are guint32 */
+ avg_duration =
+ gst_util_uint64_scale_round (duration -
+ first_duration, GST_SECOND,
+ (guint64) (stream->timescale) * (n_samples - 1));
- GST_LOG_OBJECT (qtdemux,
- "Calculating avg sample duration based on stream (or moof) duration %"
- G_GUINT64_FORMAT
- " minus first sample %u, leaving %d samples gives %"
- GST_TIME_FORMAT, duration, stream->first_duration,
- n_samples - 1, GST_TIME_ARGS (avg_duration));
+ GST_LOG_OBJECT (qtdemux,
+ "Calculating avg sample duration based on stream (or moof) duration %"
+ G_GUINT64_FORMAT
+ " minus first sample %u, leaving %d samples gives %"
+ GST_TIME_FORMAT, duration, first_duration,
+ n_samples - 1, GST_TIME_ARGS (avg_duration));
- gst_video_guess_framerate (avg_duration, &CUR_STREAM (stream)->fps_n,
- &CUR_STREAM (stream)->fps_d);
+ fps_available =
+ gst_video_guess_framerate (avg_duration,
+ &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d);
- GST_DEBUG_OBJECT (qtdemux,
- "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
- stream->timescale, CUR_STREAM (stream)->fps_n,
- CUR_STREAM (stream)->fps_d);
- }
+ GST_DEBUG_OBJECT (qtdemux,
+ "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
+ stream->timescale, CUR_STREAM (stream)->fps_n,
+ CUR_STREAM (stream)->fps_d);
}
+ }
+
+ return fps_available;
+}
+
+static gboolean
+gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+{
+ if (stream->subtype == FOURCC_vide) {
+ gboolean fps_available = gst_qtdemux_guess_framerate (qtdemux, stream);
if (CUR_STREAM (stream)->caps) {
CUR_STREAM (stream)->caps =
gst_caps_make_writable (CUR_STREAM (stream)->caps);
- gst_caps_set_simple (CUR_STREAM (stream)->caps,
- "width", G_TYPE_INT, CUR_STREAM (stream)->width,
- "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL);
+ if (CUR_STREAM (stream)->width && CUR_STREAM (stream)->height)
+ gst_caps_set_simple (CUR_STREAM (stream)->caps,
+ "width", G_TYPE_INT, CUR_STREAM (stream)->width,
+ "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL);
/* set framerate if calculated framerate is reliable */
if (fps_available) {
}
}
+ else if (stream->subtype == FOURCC_clcp && CUR_STREAM (stream)->caps) {
+ const GstStructure *s;
+ QtDemuxStream *fps_stream = NULL;
+ gboolean fps_available = FALSE;
+
+ /* CEA608 closed caption tracks are a bit special in that each sample
+ * can contain CCs for multiple frames, and CCs can be omitted and have to
+ * be inferred from the duration of the sample then.
+ *
+ * As such we take the framerate from the (first) video track here for
+ * CEA608 as there must be one CC byte pair for every video frame
+ * according to the spec.
+ *
+ * For CEA708 all is fine and there is one sample per frame.
+ */
+
+ s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
+ if (gst_structure_has_name (s, "closedcaption/x-cea-608")) {
+ gint i;
+
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *tmp = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+ if (tmp->subtype == FOURCC_vide) {
+ fps_stream = tmp;
+ break;
+ }
+ }
+
+ if (fps_stream) {
+ fps_available = gst_qtdemux_guess_framerate (qtdemux, fps_stream);
+ CUR_STREAM (stream)->fps_n = CUR_STREAM (fps_stream)->fps_n;
+ CUR_STREAM (stream)->fps_d = CUR_STREAM (fps_stream)->fps_d;
+ }
+ } else {
+ fps_available = gst_qtdemux_guess_framerate (qtdemux, stream);
+ fps_stream = stream;
+ }
+
+ CUR_STREAM (stream)->caps =
+ gst_caps_make_writable (CUR_STREAM (stream)->caps);
+
+ /* set framerate if calculated framerate is reliable */
+ if (fps_available) {
+ gst_caps_set_simple (CUR_STREAM (stream)->caps,
+ "framerate", GST_TYPE_FRACTION, CUR_STREAM (stream)->fps_n,
+ CUR_STREAM (stream)->fps_d, NULL);
+ }
+ }
+
if (stream->pad) {
GstCaps *prev_caps = NULL;
GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT,
CUR_STREAM (stream)->caps);
if (stream->new_stream) {
- gchar *stream_id;
GstEvent *event;
GstStreamFlags stream_flags = GST_STREAM_FLAG_NONE;
}
stream->new_stream = FALSE;
- stream_id =
- gst_pad_create_stream_id_printf (stream->pad,
- GST_ELEMENT_CAST (qtdemux), "%03u", stream->track_id);
- event = gst_event_new_stream_start (stream_id);
+ event = gst_event_new_stream_start (stream->stream_id);
if (qtdemux->have_group_id)
gst_event_set_group_id (event, qtdemux->group_id);
if (stream->disabled)
}
gst_event_set_stream_flags (event, stream_flags);
gst_pad_push_event (stream->pad, event);
- g_free (stream_id);
}
prev_caps = gst_pad_get_current_caps (stream->pad);
QtDemuxStream * stream, GstTagList * list)
{
gboolean ret = TRUE;
- /* consistent default for push based mode */
- gst_segment_init (&stream->segment, GST_FORMAT_TIME);
if (stream->subtype == FOURCC_vide) {
gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams);
} else if (stream->subtype == FOURCC_strm) {
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_sbtl || stream->subtype == FOURCC_subt
+ || stream->subtype == FOURCC_clcp) {
gchar *name = g_strdup_printf ("subtitle_%u", qtdemux->n_sub_streams);
stream->pad =
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 ((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;
+ return TRUE;
+ }
if (offset < cslg_least)
cslg_least = offset;
if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) {
GNode *elst;
gint n_segments;
- gint i, count, entry_size;
+ gint segment_number, entry_size;
guint64 time;
GstClockTime stime;
const guint8 *buffer;
/* segments always start from 0 */
time = 0;
stime = 0;
- count = 0;
buffer += 16;
- for (i = 0; i < n_segments; i++) {
+ for (segment_number = 0; segment_number < n_segments; segment_number++) {
guint64 duration;
guint64 media_time;
- gboolean time_valid = TRUE;
+ gboolean empty_edit = FALSE;
QtDemuxSegment *segment;
guint32 rate_int;
GstClockTime media_start = GST_CLOCK_TIME_NONE;
media_time = QT_UINT64 (buffer + 8);
duration = QT_UINT64 (buffer);
if (media_time == G_MAXUINT64)
- time_valid = FALSE;
+ empty_edit = TRUE;
} else {
media_time = QT_UINT32 (buffer + 4);
duration = QT_UINT32 (buffer);
if (media_time == G_MAXUINT32)
- time_valid = FALSE;
+ empty_edit = TRUE;
}
- if (time_valid)
+ if (!empty_edit)
media_start = QTSTREAMTIME_TO_GSTTIME (stream, media_time);
- segment = &stream->segments[count++];
+ segment = &stream->segments[segment_number];
/* time and duration expressed in global timescale */
segment->time = stime;
- /* add non scaled values so we don't cause roundoff errors */
- if (duration || media_start == GST_CLOCK_TIME_NONE) {
+ if (duration != 0 || empty_edit) {
+ /* edge case: empty edits with duration=zero are treated here.
+ * (files should not have these anyway). */
+
+ /* add non scaled values so we don't cause roundoff errors */
time += duration;
stime = QTTIME_TO_GSTTIME (qtdemux, time);
segment->duration = stime - segment->time;
} else {
/* zero duration does not imply media_start == media_stop
- * but, only specify media_start.*/
- stime = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration);
- if (GST_CLOCK_TIME_IS_VALID (stime) && time_valid
- && stime >= media_start) {
- segment->duration = stime - media_start;
- } else {
- segment->duration = GST_CLOCK_TIME_NONE;
- }
+ * but, only specify media_start. The edit ends with the track. */
+ stime = segment->duration = GST_CLOCK_TIME_NONE;
+ /* Don't allow more edits after this one. */
+ n_segments = segment_number + 1;
}
segment->stop_time = stime;
segment->trak_media_start = media_time;
/* media_time expressed in stream timescale */
- if (time_valid) {
+ if (!empty_edit) {
segment->media_start = media_start;
- segment->media_stop = segment->media_start + segment->duration;
+ segment->media_stop = GST_CLOCK_TIME_IS_VALID (segment->duration)
+ ? segment->media_start + segment->duration : GST_CLOCK_TIME_NONE;
media_segments_count++;
} else {
segment->media_start = GST_CLOCK_TIME_NONE;
", duration %" GST_TIME_FORMAT ", media_start %" GST_TIME_FORMAT
" (%" G_GUINT64_FORMAT ") , media_stop %" GST_TIME_FORMAT
" stop_time %" GST_TIME_FORMAT " rate %g, (%d) timescale %u",
- i, GST_TIME_ARGS (segment->time),
+ segment_number, GST_TIME_ARGS (segment->time),
GST_TIME_ARGS (segment->duration),
GST_TIME_ARGS (segment->media_start), media_time,
GST_TIME_ARGS (segment->media_stop),
GST_TIME_ARGS (segment->stop_time), segment->rate, rate_int,
stream->timescale);
- if (segment->stop_time > qtdemux->segment.stop) {
+ if (segment->stop_time > qtdemux->segment.stop &&
+ !qtdemux->upstream_format_is_time) {
GST_WARNING_OBJECT (qtdemux, "Segment %d "
" extends to %" GST_TIME_FORMAT
- " past the end of the file duration %" GST_TIME_FORMAT
- " it will be truncated", i, GST_TIME_ARGS (segment->stop_time),
+ " past the end of the declared movie duration %" GST_TIME_FORMAT
+ " movie segment will be extended", segment_number,
+ GST_TIME_ARGS (segment->stop_time),
GST_TIME_ARGS (qtdemux->segment.stop));
- qtdemux->segment.stop = segment->stop_time;
+ qtdemux->segment.stop = qtdemux->segment.duration = segment->stop_time;
}
buffer += entry_size;
}
- GST_DEBUG_OBJECT (qtdemux, "found %d segments", count);
- stream->n_segments = count;
+ GST_DEBUG_OBJECT (qtdemux, "found %d segments", n_segments);
+ stream->n_segments = n_segments;
if (media_segments_count != 1)
allow_pushbased_edts = FALSE;
}
GNode *frma;
GNode *schm;
GNode *schi;
+ QtDemuxCencSampleSetInfo *info;
+ GNode *tenc;
+ const guint8 *tenc_data;
g_return_val_if_fail (qtdemux != NULL, FALSE);
g_return_val_if_fail (stream != NULL, FALSE);
GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schi box");
return FALSE;
}
+ if (stream->protection_scheme_type != FOURCC_cenc &&
+ stream->protection_scheme_type != FOURCC_piff) {
+ GST_ERROR_OBJECT (qtdemux,
+ "Invalid protection_scheme_type: %" GST_FOURCC_FORMAT,
+ GST_FOURCC_ARGS (stream->protection_scheme_type));
+ return FALSE;
+ }
+
+ if (G_UNLIKELY (!stream->protection_scheme_info))
+ stream->protection_scheme_info =
+ g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
+
+ info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+
if (stream->protection_scheme_type == FOURCC_cenc) {
- QtDemuxCencSampleSetInfo *info;
- GNode *tenc;
- const guint8 *tenc_data;
- guint32 isEncrypted;
+ guint32 is_encrypted;
guint8 iv_size;
const guint8 *default_kid;
- GstBuffer *kid_buf;
-
- if (G_UNLIKELY (!stream->protection_scheme_info))
- stream->protection_scheme_info =
- g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
-
- info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_tenc);
if (!tenc) {
return FALSE;
}
tenc_data = (const guint8 *) tenc->data + 12;
- isEncrypted = QT_UINT24 (tenc_data);
+ is_encrypted = QT_UINT24 (tenc_data);
iv_size = QT_UINT8 (tenc_data + 3);
default_kid = (tenc_data + 4);
- kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
- gst_buffer_fill (kid_buf, 0, default_kid, 16);
- if (info->default_properties)
- gst_structure_free (info->default_properties);
- info->default_properties =
- gst_structure_new ("application/x-cenc",
- "iv_size", G_TYPE_UINT, iv_size,
- "encrypted", G_TYPE_BOOLEAN, (isEncrypted == 1),
- "kid", GST_TYPE_BUFFER, kid_buf, NULL);
- GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
- "is_encrypted=%u, iv_size=%u", isEncrypted, iv_size);
- gst_buffer_unref (kid_buf);
+ qtdemux_update_default_sample_encryption_settings (qtdemux, info,
+ is_encrypted, iv_size, default_kid);
+ } else if (stream->protection_scheme_type == FOURCC_piff) {
+ GstByteReader br;
+ static const guint8 piff_track_encryption_uuid[] = {
+ 0x89, 0x74, 0xdb, 0xce, 0x7b, 0xe7, 0x4c, 0x51,
+ 0x84, 0xf9, 0x71, 0x48, 0xf9, 0x88, 0x25, 0x54
+ };
+
+ tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_uuid);
+ if (!tenc) {
+ GST_ERROR_OBJECT (qtdemux, "schi box does not contain tenc box, "
+ "which is mandatory for Common Encryption");
+ return FALSE;
+ }
+
+ tenc_data = (const guint8 *) tenc->data + 8;
+ if (memcmp (tenc_data, piff_track_encryption_uuid, 16) != 0) {
+ gchar *box_uuid = qtdemux_uuid_bytes_to_string (tenc_data);
+ GST_ERROR_OBJECT (qtdemux,
+ "Unsupported track encryption box with uuid: %s", box_uuid);
+ g_free (box_uuid);
+ return FALSE;
+ }
+ tenc_data = (const guint8 *) tenc->data + 16 + 12;
+ gst_byte_reader_init (&br, tenc_data, 20);
+ if (!qtdemux_update_default_piff_encryption_settings (qtdemux, info, &br)) {
+ GST_ERROR_OBJECT (qtdemux, "PIFF track box parsing error");
+ return FALSE;
+ }
+ stream->protection_scheme_type = FOURCC_cenc;
}
+
return TRUE;
}
+static gint
+qtdemux_track_id_compare_func (QtDemuxStream ** stream1,
+ QtDemuxStream ** stream2)
+{
+ return (gint) (*stream1)->track_id - (gint) (*stream2)->track_id;
+}
+
/* parse the traks.
* With each track we associate a new QtDemuxStream that contains all the info
* about the trak.
GNode *stsd;
GNode *mp4a;
GNode *mp4v;
- GNode *wave;
GNode *esds;
- GNode *pasp;
- GNode *colr;
GNode *tref;
GNode *udta;
GNode *svmi;
- GNode *fiel;
QtDemuxStream *stream = NULL;
- gboolean new_stream = FALSE;
- gchar *codec = NULL;
const guint8 *stsd_data;
const guint8 *stsd_entry_data;
guint remaining_stsd_len;
guint32 tkhd_flags = 0;
guint8 tkhd_version = 0;
guint32 w = 0, h = 0;
- guint32 fourcc;
guint value_size, stsd_len, len;
guint32 track_id;
guint32 dummy;
!gst_byte_reader_get_uint32_be (&tkhd, &track_id))
goto corrupt_file;
- if (!qtdemux->got_moov) {
- if (qtdemux_find_stream (qtdemux, track_id))
- goto existing_stream;
- stream = _create_stream ();
- stream->track_id = track_id;
- new_stream = TRUE;
- } else {
- stream = qtdemux_find_stream (qtdemux, track_id);
- if (!stream) {
- GST_WARNING_OBJECT (qtdemux, "Stream not found, going to ignore it");
- goto skip_track;
- }
+ /* Check if current moov has duplicated track_id */
+ if (qtdemux_find_stream (qtdemux, track_id))
+ goto existing_stream;
- stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags);
+ stream = _create_stream (qtdemux, track_id);
+ stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags);
- /* flush samples data from this track from previous moov */
- gst_qtdemux_stream_flush_segments_data (qtdemux, stream);
- gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
- }
/* need defaults for fragments */
qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy);
version = QT_UINT32 ((guint8 *) mdhd->data + 8);
GST_LOG_OBJECT (qtdemux, "track version/flags: %08x", version);
if (version == 0x01000000) {
- if (len < 38)
+ if (len < 42)
goto corrupt_file;
stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 28);
stream->duration = QT_UINT64 ((guint8 *) mdhd->data + 32);
- lang_code = QT_UINT16 ((guint8 *) mdhd->data + 36);
+ lang_code = QT_UINT16 ((guint8 *) mdhd->data + 40);
} else {
if (len < 30)
goto corrupt_file;
"found, assuming preview image or something; skipping track",
stream->duration, stream->timescale, qtdemux->duration,
qtdemux->timescale);
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
+ gst_qtdemux_stream_unref (stream);
return TRUE;
}
}
if (stsd_len < 24) {
/* .. but skip stream with empty stsd produced by some Vivotek cameras */
if (stream->subtype == FOURCC_vivo) {
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
+ gst_qtdemux_stream_unref (stream);
return TRUE;
} else {
goto corrupt_file;
stsd_entry_data = stsd_data + 16;
remaining_stsd_len = stsd_len - 16;
for (stsd_index = 0; stsd_index < stsd_entry_count; stsd_index++) {
+ guint32 fourcc;
+ gchar *codec = NULL;
QtDemuxStreamStsdEntry *entry = &stream->stsd_entries[stsd_index];
/* and that entry should fit within stsd */
}
if (stream->subtype == FOURCC_vide) {
+ GNode *colr;
+ GNode *fiel;
+ GNode *pasp;
gboolean gray;
gint depth, palette_size, palette_count;
guint32 *palette_data = NULL;
fiel = NULL;
/* pick 'the' stsd child */
mp4v = qtdemux_tree_get_child_by_index (stsd, stsd_index);
- if (!stream->protected) {
- if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) {
+ // We should skip parsing the stsd for non-protected streams if
+ // the entry doesn't match the fourcc, since they don't change
+ // format. However, for protected streams we can have partial
+ // encryption, where parts of the stream are encrypted and parts
+ // not. For both parts of such streams, we should ensure the
+ // esds overrides are parsed for both from the stsd.
+ if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) {
+ if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv)
mp4v = NULL;
- }
- } else {
- if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) {
+ else if (!stream->protected)
mp4v = NULL;
- }
}
if (mp4v) {
/* parse, if found */
GstBuffer *buf;
- GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd");
+ GST_DEBUG_OBJECT (qtdemux, "found hvcC codec_data in stsd");
/* First 4 bytes are the length of the atom, the next 4 bytes
* are the fourcc, the next 1 byte is the version, and the
case FOURCC_WRLE:
{
gst_caps_set_simple (entry->caps,
- "depth", G_TYPE_INT, QT_UINT16 (stsd_entry_data + offset + 82),
+ "depth", G_TYPE_INT, QT_UINT16 (stsd_entry_data + offset + 66),
NULL);
break;
}
}
break;
}
+ case FOURCC_av01:
+ {
+ gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+ const guint8 *av1_data = stsd_entry_data + 0x56;
+
+ /* find av1C */
+ while (len >= 0x8) {
+ gint size;
+
+ if (QT_UINT32 (av1_data) <= len)
+ size = QT_UINT32 (av1_data) - 0x8;
+ else
+ size = len - 0x8;
+
+ if (size < 1)
+ /* No real data, so break out */
+ break;
+
+ switch (QT_FOURCC (av1_data + 0x4)) {
+ case FOURCC_av1C:
+ {
+ /* parse, if found */
+ GstBuffer *buf;
+ guint8 pres_delay_field;
+
+ GST_DEBUG_OBJECT (qtdemux,
+ "found av1C codec_data in stsd of size %d", size);
+
+ /* not enough data, just ignore and hope for the best */
+ if (size < 5)
+ break;
+
+ /* Content is:
+ * 4 bytes: atom length
+ * 4 bytes: fourcc
+ * 1 byte: version
+ * 3 bytes: flags
+ * 3 bits: reserved
+ * 1 bits: initial_presentation_delay_present
+ * 4 bits: initial_presentation_delay (if present else reserved
+ * rest: OBUs.
+ */
+
+ if (av1_data[9] != 0) {
+ GST_WARNING ("Unknown version %d of av1C box", av1_data[9]);
+ break;
+ }
+
+ /* We skip initial_presentation_delay* for now */
+ pres_delay_field = *(av1_data + 12);
+ if (pres_delay_field & (1 << 5)) {
+ gst_caps_set_simple (entry->caps,
+ "presentation-delay", G_TYPE_INT,
+ (gint) (pres_delay_field & 0x0F) + 1, NULL);
+ }
+ if (size > 5) {
+ buf = gst_buffer_new_and_alloc (size - 5);
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
+ gst_buffer_fill (buf, 0, av1_data + 13, size - 5);
+ gst_caps_set_simple (entry->caps,
+ "codec_data", GST_TYPE_BUFFER, buf, NULL);
+ gst_buffer_unref (buf);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ len -= size + 8;
+ av1_data += size + 8;
+ }
+
+ break;
+ }
default:
break;
}
GST_FOURCC_ARGS (fourcc), entry->caps);
} else if (stream->subtype == FOURCC_soun) {
+ GNode *wave;
int version, samplesize;
guint16 compression_id;
gboolean amrwb = FALSE;
case FOURCC_twos:
case FOURCC_sowt:
case FOURCC_raw_:
+ case FOURCC_lpcm:
break;
default:
{
}
mp4a = qtdemux_tree_get_child_by_index (stsd, stsd_index);
- if (!stream->protected) {
- } else {
- if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) {
- mp4v = NULL;
- }
- }
- if (stream->protected && fourcc == FOURCC_mp4a) {
- if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca) {
+ if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != fourcc) {
+ if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca)
mp4a = NULL;
- }
- } else {
- if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_mp4a) {
+ else if (!stream->protected)
mp4a = NULL;
- }
}
wave = NULL;
}
break;
}
+ case FOURCC_lpcm:
+ /* Fully handled elsewhere */
+ break;
default:
GST_INFO_OBJECT (qtdemux,
"unhandled type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc));
}
entry->sampled = TRUE;
} else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text
- || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt) {
+ || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt
+ || stream->subtype == FOURCC_clcp) {
entry->sampled = TRUE;
entry->sparse = TRUE;
qtdemux_parse_udta (qtdemux, stream->stream_tags, udta);
}
- /* now we are ready to add the stream */
- if (qtdemux->n_streams >= GST_QTDEMUX_MAX_STREAMS)
- goto too_many_streams;
-
- if (!qtdemux->got_moov) {
- qtdemux->streams[qtdemux->n_streams] = stream;
- qtdemux->n_streams++;
- GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams);
- }
+ /* Insert and sort new stream in track-id order.
+ * This will help in comparing old/new streams during stream update check */
+ g_ptr_array_add (qtdemux->active_streams, stream);
+ g_ptr_array_sort (qtdemux->active_streams,
+ (GCompareFunc) qtdemux_track_id_compare_func);
+ GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d",
+ QTDEMUX_N_STREAMS (qtdemux));
return TRUE;
/* ERRORS */
-skip_track:
- {
- GST_INFO_OBJECT (qtdemux, "skip disabled track");
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
- return TRUE;
- }
corrupt_file:
{
GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
(_("This file is corrupt and cannot be played.")), (NULL));
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
+ if (stream)
+ gst_qtdemux_stream_unref (stream);
return FALSE;
}
error_encrypted:
{
GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (NULL), (NULL));
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
+ gst_qtdemux_stream_unref (stream);
return FALSE;
}
samples_failed:
/* we posted an error already */
/* free stbl sub-atoms */
gst_qtdemux_stbl_free (stream);
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
+ gst_qtdemux_stream_unref (stream);
return FALSE;
}
existing_stream:
{
GST_INFO_OBJECT (qtdemux, "stream with track id %i already exists",
track_id);
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
return TRUE;
}
unknown_stream:
{
GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (stream->subtype));
- if (new_stream)
- gst_qtdemux_stream_free (qtdemux, stream);
- return TRUE;
- }
-too_many_streams:
- {
- GST_ELEMENT_WARNING (qtdemux, STREAM, DEMUX,
- (_("This file contains too many streams. Only playing first %d"),
- GST_QTDEMUX_MAX_STREAMS), (NULL));
+ gst_qtdemux_stream_unref (stream);
return TRUE;
}
}
QtDemuxStream *stream = NULL;
gint64 size, sys_bitrate, sum_bitrate = 0;
GstClockTime duration;
- gint i;
guint bitrate;
+ gint i;
if (qtdemux->fragmented)
return;
return;
}
- for (i = 0; i < qtdemux->n_streams; i++) {
- switch (qtdemux->streams[i]->subtype) {
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
+ switch (str->subtype) {
case FOURCC_soun:
case FOURCC_vide:
GST_DEBUG_OBJECT (qtdemux, "checking bitrate for %" GST_PTR_FORMAT,
- CUR_STREAM (qtdemux->streams[i])->caps);
+ CUR_STREAM (str)->caps);
/* retrieve bitrate, prefer avg then max */
bitrate = 0;
- if (qtdemux->streams[i]->stream_tags) {
- gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags,
- GST_TAG_MAXIMUM_BITRATE, &bitrate);
- GST_DEBUG_OBJECT (qtdemux, "max-bitrate: %u", bitrate);
- gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags,
- GST_TAG_NOMINAL_BITRATE, &bitrate);
- GST_DEBUG_OBJECT (qtdemux, "nominal-bitrate: %u", bitrate);
- gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags,
- GST_TAG_BITRATE, &bitrate);
- GST_DEBUG_OBJECT (qtdemux, "bitrate: %u", bitrate);
+ if (str->stream_tags) {
+ if (gst_tag_list_get_uint (str->stream_tags,
+ GST_TAG_MAXIMUM_BITRATE, &bitrate))
+ GST_DEBUG_OBJECT (qtdemux, "max-bitrate: %u", bitrate);
+ if (gst_tag_list_get_uint (str->stream_tags,
+ GST_TAG_NOMINAL_BITRATE, &bitrate))
+ GST_DEBUG_OBJECT (qtdemux, "nominal-bitrate: %u", bitrate);
+ if (gst_tag_list_get_uint (str->stream_tags,
+ GST_TAG_BITRATE, &bitrate))
+ GST_DEBUG_OBJECT (qtdemux, "bitrate: %u", bitrate);
}
if (bitrate)
sum_bitrate += bitrate;
">1 stream with unknown bitrate - bailing");
return;
} else
- stream = qtdemux->streams[i];
+ stream = str;
}
default:
static GstFlowReturn
qtdemux_prepare_streams (GstQTDemux * qtdemux)
{
- gint i;
GstFlowReturn ret = GST_FLOW_OK;
+ gint i;
GST_DEBUG_OBJECT (qtdemux, "prepare streams");
- for (i = 0; ret == GST_FLOW_OK && i < qtdemux->n_streams; i++) {
- QtDemuxStream *stream = qtdemux->streams[i];
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
guint32 sample_num = 0;
- GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT,
- i, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
+ GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT,
+ stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
if (qtdemux->fragmented) {
/* need all moov samples first */
* in push mode, we'll just have to deal with it */
if (G_UNLIKELY (qtdemux->pullbased && !stream->n_samples)) {
GST_DEBUG_OBJECT (qtdemux, "no samples for stream; discarding");
- gst_qtdemux_remove_stream (qtdemux, i);
+ g_ptr_array_remove_index (qtdemux->active_streams, i);
+ i--;
+ continue;
+ } else if (stream->track_id == qtdemux->chapters_track_id &&
+ (stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl)) {
+ /* TODO - parse chapters track and expose it as GstToc; For now just ignore it
+ so that it doesn't look like a subtitle track */
+ g_ptr_array_remove_index (qtdemux->active_streams, i);
i--;
continue;
}
break;
++sample_num;
}
- if (stream->n_samples > 0 && stream->stbl_index >= 0) {
- stream->first_duration = stream->samples[0].duration;
- GST_LOG_OBJECT (qtdemux, "stream %d first duration %u",
- stream->track_id, stream->first_duration);
- }
}
return ret;
}
+static gboolean
+_stream_equal_func (const QtDemuxStream * stream, const gchar * stream_id)
+{
+ return g_strcmp0 (stream->stream_id, stream_id) == 0;
+}
+
+static gboolean
+qtdemux_is_streams_update (GstQTDemux * qtdemux)
+{
+ gint i;
+
+ /* Different length, updated */
+ if (QTDEMUX_N_STREAMS (qtdemux) != qtdemux->old_streams->len)
+ return TRUE;
+
+ /* streams in list are sorted in track-id order */
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ /* Different stream-id, updated */
+ if (g_strcmp0 (QTDEMUX_NTH_STREAM (qtdemux, i)->stream_id,
+ QTDEMUX_NTH_OLD_STREAM (qtdemux, i)->stream_id))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux,
+ QtDemuxStream * oldstream, QtDemuxStream * newstream)
+{
+ /* Connect old stream's srcpad to new stream */
+ newstream->pad = oldstream->pad;
+ oldstream->pad = NULL;
+
+ /* unset new_stream to prevent stream-start event */
+ newstream->new_stream = FALSE;
+
+ return gst_qtdemux_configure_stream (qtdemux, newstream);
+}
+
+/* g_ptr_array_find_with_equal_func is available since 2.54,
+ * replacement until we can depend unconditionally on the real one in GLib */
+#if !GLIB_CHECK_VERSION(2,54,0)
+#define g_ptr_array_find_with_equal_func qtdemux_ptr_array_find_with_equal_func
+static gboolean
+qtdemux_ptr_array_find_with_equal_func (GPtrArray * haystack,
+ gconstpointer needle, GEqualFunc equal_func, guint * index_)
+{
+ guint i;
+
+ g_return_val_if_fail (haystack != NULL, FALSE);
+
+ if (equal_func == NULL)
+ equal_func = g_direct_equal;
+
+ for (i = 0; i < haystack->len; i++) {
+ if (equal_func (g_ptr_array_index (haystack, i), needle)) {
+ if (index_ != NULL)
+ *index_ = i;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+#endif
+
+static gboolean
+qtdemux_update_streams (GstQTDemux * qtdemux)
+{
+ gint i;
+ g_assert (qtdemux->streams_aware);
+
+ /* At below, figure out which stream in active_streams has identical stream-id
+ * with that of in old_streams. If there is matching stream-id,
+ * corresponding newstream will not be exposed again,
+ * but demux will reuse srcpad of matched old stream
+ *
+ * active_streams : newly created streams from the latest moov
+ * old_streams : existing streams (belong to previous moov)
+ */
+
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ QtDemuxStream *oldstream = NULL;
+ guint target;
+
+ GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT,
+ stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
+
+ if (g_ptr_array_find_with_equal_func (qtdemux->old_streams,
+ stream->stream_id, (GEqualFunc) _stream_equal_func, &target)) {
+ oldstream = QTDEMUX_NTH_OLD_STREAM (qtdemux, target);
+
+ /* null pad stream cannot be reused */
+ if (oldstream->pad == NULL)
+ oldstream = NULL;
+ }
+
+ if (oldstream) {
+ GST_DEBUG_OBJECT (qtdemux, "Reuse track-id %d", oldstream->track_id);
+
+ if (!qtdemux_reuse_and_configure_stream (qtdemux, oldstream, stream))
+ return FALSE;
+
+ /* we don't need to preserve order of old streams */
+ g_ptr_array_remove_fast (qtdemux->old_streams, oldstream);
+ } else {
+ GstTagList *list;
+
+ /* now we have all info and can expose */
+ list = stream->stream_tags;
+ stream->stream_tags = NULL;
+ if (!gst_qtdemux_add_stream (qtdemux, stream, list))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* Must be called with expose lock */
static GstFlowReturn
qtdemux_expose_streams (GstQTDemux * qtdemux)
{
gint i;
- GSList *oldpads = NULL;
- GSList *iter;
GST_DEBUG_OBJECT (qtdemux, "exposing streams");
- for (i = 0; i < qtdemux->n_streams; i++) {
- QtDemuxStream *stream = qtdemux->streams[i];
- GstPad *oldpad = stream->pad;
- GstTagList *list;
+ if (!qtdemux_is_streams_update (qtdemux)) {
+ GST_DEBUG_OBJECT (qtdemux, "Reuse all streams");
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *new_stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ QtDemuxStream *old_stream = QTDEMUX_NTH_OLD_STREAM (qtdemux, i);
+ if (!qtdemux_reuse_and_configure_stream (qtdemux, old_stream, new_stream))
+ return GST_FLOW_ERROR;
+ }
- GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT,
- i, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
+ g_ptr_array_set_size (qtdemux->old_streams, 0);
+ qtdemux->need_segment = TRUE;
- if ((stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl) &&
- stream->track_id == qtdemux->chapters_track_id) {
- /* TODO - parse chapters track and expose it as GstToc; For now just ignore it
- so that it doesn't look like a subtitle track */
- gst_qtdemux_remove_stream (qtdemux, i);
- i--;
- continue;
- }
+ return GST_FLOW_OK;
+ }
- /* now we have all info and can expose */
- list = stream->stream_tags;
- stream->stream_tags = NULL;
- if (oldpad)
- oldpads = g_slist_prepend (oldpads, oldpad);
- if (!gst_qtdemux_add_stream (qtdemux, stream, list))
+ if (qtdemux->streams_aware) {
+ if (!qtdemux_update_streams (qtdemux))
return GST_FLOW_ERROR;
+ } else {
+ for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+ GstTagList *list;
+
+ /* now we have all info and can expose */
+ list = stream->stream_tags;
+ stream->stream_tags = NULL;
+ if (!gst_qtdemux_add_stream (qtdemux, stream, list))
+ return GST_FLOW_ERROR;
+
+ }
}
gst_qtdemux_guess_bitrate (qtdemux);
gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux));
- for (iter = oldpads; iter; iter = g_slist_next (iter)) {
- GstPad *oldpad = iter->data;
- GstEvent *event;
+ /* If we have still old_streams, it's no more used stream */
+ for (i = 0; i < qtdemux->old_streams->len; i++) {
+ QtDemuxStream *stream = QTDEMUX_NTH_OLD_STREAM (qtdemux, i);
- event = gst_event_new_eos ();
- if (qtdemux->segment_seqnum)
- gst_event_set_seqnum (event, qtdemux->segment_seqnum);
+ if (stream->pad) {
+ GstEvent *event;
+
+ event = gst_event_new_eos ();
+ if (qtdemux->segment_seqnum)
+ gst_event_set_seqnum (event, qtdemux->segment_seqnum);
- gst_pad_push_event (oldpad, event);
- gst_pad_set_active (oldpad, FALSE);
- gst_element_remove_pad (GST_ELEMENT (qtdemux), oldpad);
- gst_flow_combiner_remove_pad (qtdemux->flowcombiner, oldpad);
- gst_object_unref (oldpad);
+ gst_pad_push_event (stream->pad, event);
+ }
}
+ g_ptr_array_set_size (qtdemux->old_streams, 0);
+
/* check if we should post a redirect in case there is a single trak
* and it is a redirecting trak */
- if (qtdemux->n_streams == 1 && qtdemux->streams[0]->redirect_uri != NULL) {
+ if (QTDEMUX_N_STREAMS (qtdemux) == 1 &&
+ QTDEMUX_NTH_STREAM (qtdemux, 0)->redirect_uri != NULL) {
GstMessage *m;
GST_INFO_OBJECT (qtdemux, "Issuing a redirect due to a single track with "
"an external content");
m = gst_message_new_element (GST_OBJECT_CAST (qtdemux),
gst_structure_new ("redirect",
- "new-location", G_TYPE_STRING, qtdemux->streams[0]->redirect_uri,
- NULL));
+ "new-location", G_TYPE_STRING,
+ QTDEMUX_NTH_STREAM (qtdemux, 0)->redirect_uri, NULL));
gst_element_post_message (GST_ELEMENT_CAST (qtdemux), m);
qtdemux->posted_redirect = TRUE;
}
- for (i = 0; i < qtdemux->n_streams; i++) {
- QtDemuxStream *stream = qtdemux->streams[i];
+ g_ptr_array_foreach (qtdemux->active_streams,
+ (GFunc) qtdemux_do_allocation, qtdemux);
- qtdemux_do_allocation (qtdemux, stream);
- }
+ qtdemux->need_segment = TRUE;
qtdemux->exposed = TRUE;
return GST_FLOW_OK;
const char *tag, const char *dummy, GNode * node)
{
GNode *data;
+ GstDateTime *datetime = NULL;
char *s;
int len;
int type;
s = g_strndup ((char *) data->data + 16, len - 16);
GST_DEBUG_OBJECT (qtdemux, "adding date '%s'", s);
+ datetime = gst_date_time_new_from_iso8601_string (s);
+ if (datetime != NULL) {
+ gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME,
+ datetime, NULL);
+ gst_date_time_unref (datetime);
+ }
+
ret = sscanf (s, "%u-%u-%u", &y, &m, &d);
if (ret >= 1 && y > 1500 && y < 3000) {
GDate *date;
GNode *trak;
GNode *udta;
GNode *mvex;
- GstClockTime duration;
GNode *pssh;
guint64 creation_time;
GstDateTime *datetime = NULL;
qtdemux_parse_mehd (qtdemux, &mehd_data);
}
- /* set duration in the segment info */
- gst_qtdemux_get_duration (qtdemux, &duration);
- if (duration) {
+ /* Update the movie segment duration, unless it was directly given to us
+ * by upstream. Otherwise let it as is, as we don't want to mangle the
+ * duration provided by upstream that may come e.g. from a MPD file. */
+ if (!qtdemux->upstream_format_is_time) {
+ GstClockTime duration;
+ /* set duration in the segment info */
+ gst_qtdemux_get_duration (qtdemux, &duration);
qtdemux->segment.duration = duration;
/* also do not exceed duration; stop is set that way post seek anyway,
* and segment activation falls back to duration,
return len;
}
+static GList *
+parse_xiph_stream_headers (GstQTDemux * qtdemux, gpointer codec_data,
+ gsize codec_data_size)
+{
+ GList *list = NULL;
+ guint8 *p = codec_data;
+ gint i, offset, num_packets;
+ guint *length, last;
+
+ GST_MEMDUMP_OBJECT (qtdemux, "xiph codec data", codec_data, codec_data_size);
+
+ if (codec_data == NULL || codec_data_size == 0)
+ goto error;
+
+ /* start of the stream and vorbis audio or theora video, need to
+ * send the codec_priv data as first three packets */
+ num_packets = p[0] + 1;
+ GST_DEBUG_OBJECT (qtdemux,
+ "%u stream headers, total length=%" G_GSIZE_FORMAT " bytes",
+ (guint) num_packets, codec_data_size);
+
+ /* Let's put some limits, Don't think there even is a xiph codec
+ * with more than 3-4 headers */
+ if (G_UNLIKELY (num_packets > 16)) {
+ GST_WARNING_OBJECT (qtdemux,
+ "Unlikely number of xiph headers, most likely not valid");
+ goto error;
+ }
+
+ length = g_alloca (num_packets * sizeof (guint));
+ last = 0;
+ offset = 1;
+
+ /* first packets, read length values */
+ for (i = 0; i < num_packets - 1; i++) {
+ length[i] = 0;
+ while (offset < codec_data_size) {
+ length[i] += p[offset];
+ if (p[offset++] != 0xff)
+ break;
+ }
+ last += length[i];
+ }
+ if (offset + last > codec_data_size)
+ goto error;
+
+ /* last packet is the remaining size */
+ length[i] = codec_data_size - offset - last;
+
+ for (i = 0; i < num_packets; i++) {
+ GstBuffer *hdr;
+
+ GST_DEBUG_OBJECT (qtdemux, "buffer %d: %u bytes", i, (guint) length[i]);
+
+ if (offset + length[i] > codec_data_size)
+ goto error;
+
+ hdr = gst_buffer_new_wrapped (g_memdup (p + offset, length[i]), length[i]);
+ list = g_list_append (list, hdr);
+
+ offset += length[i];
+ }
+
+ return list;
+
+ /* ERRORS */
+error:
+ {
+ if (list != NULL)
+ g_list_free_full (list, (GDestroyNotify) gst_buffer_unref);
+ return NULL;
+ }
+
+}
+
/* this can change the codec originally present in @list */
static void
gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream,
guint8 *data_ptr = NULL;
int data_len = 0;
guint8 object_type_id = 0;
+ guint8 stream_type = 0;
const char *codec_name = NULL;
GstCaps *caps = NULL;
switch (tag) {
case ES_DESCRIPTOR_TAG:
- GST_DEBUG_OBJECT (qtdemux, "ID %04x", QT_UINT16 (ptr));
- GST_DEBUG_OBJECT (qtdemux, "priority %04x", QT_UINT8 (ptr + 2));
+ GST_DEBUG_OBJECT (qtdemux, "ID 0x%04x", QT_UINT16 (ptr));
+ GST_DEBUG_OBJECT (qtdemux, "priority 0x%04x", QT_UINT8 (ptr + 2));
ptr += 3;
break;
case DECODER_CONFIG_DESC_TAG:{
guint max_bitrate, avg_bitrate;
object_type_id = QT_UINT8 (ptr);
+ stream_type = QT_UINT8 (ptr + 1) >> 2;
max_bitrate = QT_UINT32 (ptr + 5);
avg_bitrate = QT_UINT32 (ptr + 9);
GST_DEBUG_OBJECT (qtdemux, "object_type_id %02x", object_type_id);
- GST_DEBUG_OBJECT (qtdemux, "stream_type %02x", QT_UINT8 (ptr + 1));
+ GST_DEBUG_OBJECT (qtdemux, "stream_type %02x", stream_type);
GST_DEBUG_OBJECT (qtdemux, "buffer_size_db %02x", QT_UINT24 (ptr + 2));
GST_DEBUG_OBJECT (qtdemux, "max bitrate %u", max_bitrate);
GST_DEBUG_OBJECT (qtdemux, "avg bitrate %u", avg_bitrate);
case 0x69: /* MPEG-2 BC audio */
case 0x6B: /* MPEG-1 audio */
caps = gst_caps_new_simple ("audio/mpeg",
- "mpegversion", G_TYPE_INT, 1, "parsed", G_TYPE_BOOLEAN, TRUE, NULL);
+ "mpegversion", G_TYPE_INT, 1, NULL);
codec_name = "MPEG-1 audio";
break;
case 0x6A: /* MPEG-1 */
caps = gst_caps_new_simple ("audio/x-dts",
"framed", G_TYPE_BOOLEAN, TRUE, NULL);
break;
+ case 0xDD:
+ if (stream_type == 0x05 && data_ptr) {
+ GList *headers =
+ parse_xiph_stream_headers (qtdemux, data_ptr, data_len);
+ if (headers) {
+ GList *tmp;
+ GValue arr_val = G_VALUE_INIT;
+ GValue buf_val = G_VALUE_INIT;
+ GstStructure *s;
+
+ /* Let's assume it's vorbis if it's an audio stream of type 0xdd and we have codec data that extracts properly */
+ codec_name = "Vorbis";
+ caps = gst_caps_new_empty_simple ("audio/x-vorbis");
+ g_value_init (&arr_val, GST_TYPE_ARRAY);
+ g_value_init (&buf_val, GST_TYPE_BUFFER);
+ for (tmp = headers; tmp; tmp = tmp->next) {
+ g_value_set_boxed (&buf_val, (GstBuffer *) tmp->data);
+ gst_value_array_append_value (&arr_val, &buf_val);
+ }
+ s = gst_caps_get_structure (caps, 0);
+ gst_structure_take_value (s, "streamheader", &arr_val);
+ g_value_unset (&buf_val);
+ g_list_free (headers);
+
+ data_ptr = NULL;
+ data_len = 0;
+ }
+ }
+ break;
case 0xE1: /* QCELP */
/* QCELP, the codec_data is a riff tag (little endian) with
* more info (http://ftp.3gpp2.org/TSGC/Working/2003/2003-05-SanDiego/TSG-C-2003-05-San%20Diego/WG1/SWG12/C12-20030512-006%20=%20C12-20030217-015_Draft_Baseline%20Text%20of%20FFMS_R2.doc). */
GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
switch (fourcc) {
- case GST_MAKE_FOURCC ('p', 'n', 'g', ' '):
+ case FOURCC_png:
_codec ("PNG still images");
caps = gst_caps_new_empty_simple ("image/png");
break;
caps = gst_caps_from_string ("video/x-dnxhd");
break;
case FOURCC_VP80:
+ case FOURCC_vp08:
_codec ("On2 VP8");
caps = gst_caps_from_string ("video/x-vp8");
break;
+ case FOURCC_vp09:
+ _codec ("Google VP9");
+ caps = gst_caps_from_string ("video/x-vp9");
+ break;
case FOURCC_apcs:
_codec ("Apple ProRes LT");
caps =
caps = gst_caps_new_simple ("video/x-wmv",
"wmvversion", G_TYPE_INT, 3, "format", G_TYPE_STRING, "WVC1", NULL);
break;
+ case FOURCC_av01:
+ _codec ("AV1");
+ caps = gst_caps_new_empty_simple ("video/x-av1");
+ break;
case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'):
default:
{
_codec ("Opus");
caps = gst_caps_new_empty_simple ("audio/x-opus");
break;
- case GST_MAKE_FOURCC ('l', 'p', 'c', 'm'):
+ case FOURCC_lpcm:
{
guint32 flags = 0;
guint32 depth = 0;
};
_codec ("Raw LPCM audio");
- if (data && len >= 56) {
- depth = QT_UINT32 (data + 40);
- flags = QT_UINT32 (data + 44);
- width = QT_UINT32 (data + 48) * 8 / entry->n_channels;
+ if (data && len >= 36) {
+ depth = QT_UINT32 (data + 24);
+ flags = QT_UINT32 (data + 28);
+ width = QT_UINT32 (data + 32) * 8 / entry->n_channels;
}
if ((flags & FLAG_IS_FLOAT) == 0) {
if (depth == 0)
depth = 16;
if (width == 0)
width = 16;
+ if ((flags & FLAG_IS_ALIGNED_HIGH))
+ depth = width;
+
format = gst_audio_format_build_integer ((flags & FLAG_IS_SIGNED) ?
TRUE : FALSE, (flags & FLAG_IS_BIG_ENDIAN) ?
G_BIG_ENDIAN : G_LITTLE_ENDIAN, width, depth);
caps = gst_caps_new_simple ("audio/x-raw",
- "format", G_TYPE_STRING, gst_audio_format_to_string (format),
- "layout", G_TYPE_STRING, (flags & FLAG_IS_NON_INTERLEAVED) ?
- "non-interleaved" : "interleaved", NULL);
+ "format", G_TYPE_STRING,
+ format !=
+ GST_AUDIO_FORMAT_UNKNOWN ? gst_audio_format_to_string (format) :
+ "UNKNOWN", "layout", G_TYPE_STRING,
+ (flags & FLAG_IS_NON_INTERLEAVED) ? "non-interleaved" :
+ "interleaved", NULL);
stream->alignment = GST_ROUND_UP_8 (depth);
stream->alignment = round_up_pow2 (stream->alignment);
} else {
_codec ("XML subtitles");
caps = gst_caps_new_empty_simple ("application/ttml+xml");
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->need_split = TRUE;
+ break;
+ case FOURCC_c708:
+ _codec ("CEA 708 Closed Caption");
+ caps =
+ gst_caps_new_simple ("closedcaption/x-cea-708", "format",
+ G_TYPE_STRING, "cdp", NULL);
+ stream->need_process = TRUE;
+ break;
+
default:
{
caps = _get_unknown_codec_name ("text", fourcc);