summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWim Taymans <wim.taymans@gmail.com>2006-04-06 19:16:02 +0000
committerWim Taymans <wim.taymans@gmail.com>2006-04-06 19:16:02 +0000
commit83208d042bea60f66475cc566d3d4647b9d1ad7b (patch)
tree4164c8045af9e5caaad7bac6bf28aaa892a9ff73
parentef5b79cf2956f3b0ad0c792bb8d2e0ca32a5cd54 (diff)
downloadgst-plugins-bad-83208d042bea60f66475cc566d3d4647b9d1ad7b.tar.gz
gst-plugins-bad-83208d042bea60f66475cc566d3d4647b9d1ad7b.tar.bz2
gst-plugins-bad-83208d042bea60f66475cc566d3d4647b9d1ad7b.zip
gst/qtdemux/qtdemux.c: Added full edit list support.
Original commit message from CVS: * gst/qtdemux/qtdemux.c: (gst_qtdemux_init), (gst_qtdemux_handle_src_query), (gst_qtdemux_find_index), (gst_qtdemux_find_keyframe), (gst_qtdemux_find_segment), (gst_qtdemux_move_stream), (gst_qtdemux_perform_seek), (gst_qtdemux_do_seek), (gst_qtdemux_change_state), (gst_qtdemux_activate_segment), (gst_qtdemux_prepare_current_sample), (gst_qtdemux_advance_sample), (gst_qtdemux_loop_state_movie), (gst_qtdemux_loop), (qtdemux_parse_trak): Added full edit list support. Avoid overflows in prologue image detection code. Avoid roundoff errors in timestamp calculations.
-rw-r--r--ChangeLog15
-rw-r--r--gst/qtdemux/qtdemux.c786
2 files changed, 593 insertions, 208 deletions
diff --git a/ChangeLog b/ChangeLog
index ec424c82..a4584d8a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2006-04-06 Wim Taymans <wim@fluendo.com>
+
+ * gst/qtdemux/qtdemux.c: (gst_qtdemux_init),
+ (gst_qtdemux_handle_src_query), (gst_qtdemux_find_index),
+ (gst_qtdemux_find_keyframe), (gst_qtdemux_find_segment),
+ (gst_qtdemux_move_stream), (gst_qtdemux_perform_seek),
+ (gst_qtdemux_do_seek), (gst_qtdemux_change_state),
+ (gst_qtdemux_activate_segment),
+ (gst_qtdemux_prepare_current_sample), (gst_qtdemux_advance_sample),
+ (gst_qtdemux_loop_state_movie), (gst_qtdemux_loop),
+ (qtdemux_parse_trak):
+ Added full edit list support.
+ Avoid overflows in prologue image detection code.
+ Avoid roundoff errors in timestamp calculations.
+
2006-04-06 Thomas Vander Stichele <thomas at apestaart dot org>
* configure.ac:
diff --git a/gst/qtdemux/qtdemux.c b/gst/qtdemux/qtdemux.c
index 31b4f24b..e24c1857 100644
--- a/gst/qtdemux/qtdemux.c
+++ b/gst/qtdemux/qtdemux.c
@@ -46,6 +46,7 @@ GST_DEBUG_CATEGORY_EXTERN (qtdemux_debug);
typedef struct _QtNode QtNode;
typedef struct _QtNodeType QtNodeType;
+typedef struct _QtDemuxSegment QtDemuxSegment;
typedef struct _QtDemuxSample QtDemuxSample;
//typedef struct _QtDemuxStream QtDemuxStream;
@@ -67,27 +68,43 @@ struct _QtNodeType
struct _QtDemuxSample
{
- gint sample_index;
- gint chunk;
- gint size;
+ guint32 chunk;
+ guint32 size;
guint64 offset;
guint64 timestamp; /* In GstClockTime */
guint32 duration; /* in stream->timescale units */
gboolean keyframe; /* TRUE when this packet is a keyframe */
};
+struct _QtDemuxSegment
+{
+ /* global time and duration, all gst time */
+ guint64 time;
+ guint64 stop_time;
+ guint64 duration;
+ /* media time of trak, all gst time */
+ guint64 media_start;
+ guint64 media_stop;
+ gdouble rate;
+};
+
struct _QtDemuxStream
{
+ GstPad *pad;
+
+ /* stream type */
guint32 subtype;
GstCaps *caps;
guint32 fourcc;
- GstPad *pad;
- gint n_samples;
- QtDemuxSample *samples;
- gint timescale;
- gboolean all_keyframe; /* TRUE when all packets are keyframes (no stss) */
- gint sample_index;
+ /* duration/scale */
+ guint32 duration; /* in timescale */
+ guint32 timescale;
+
+ /* our samples */
+ guint32 n_samples;
+ QtDemuxSample *samples;
+ gboolean all_keyframe; /* TRUE when all samples are keyframes (no stss) */
gint width;
gint height;
@@ -105,6 +122,16 @@ struct _QtDemuxStream
/* when a discontinuity is pending */
gboolean discont;
+
+ /* current position */
+ guint32 segment_index;
+ guint32 sample_index;
+ guint64 time_position; /* in gst time */
+
+ /* quicktime segments */
+ guint32 n_segments;
+ QtDemuxSegment *segments;
+ gboolean segment_pending;
};
enum QtDemuxState
@@ -335,6 +362,7 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
gst_element_add_pad (GST_ELEMENT (qtdemux), qtdemux->sinkpad);
qtdemux->state = QTDEMUX_STATE_INITIAL;
+ /* FIXME, use segment last_stop for this */
qtdemux->last_ts = GST_CLOCK_TIME_NONE;
qtdemux->pullbased = FALSE;
qtdemux->neededbytes = 16;
@@ -433,8 +461,9 @@ gst_qtdemux_handle_src_query (GstPad * pad, GstQuery * query)
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
- if (GST_CLOCK_TIME_IS_VALID (qtdemux->last_ts)) {
- gst_query_set_position (query, GST_FORMAT_TIME, qtdemux->last_ts);
+ if (GST_CLOCK_TIME_IS_VALID (qtdemux->segment.last_stop)) {
+ gst_query_set_position (query, GST_FORMAT_TIME,
+ qtdemux->segment.last_stop);
res = TRUE;
}
break;
@@ -475,113 +504,204 @@ gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
gst_event_unref (event);
}
-/* move all streams back on the keyframe before @offset.
- *
- * If @end is FALSE, the search is started from the current
- * sample_index position of each stream.
- * If @end is TRUE, the search is started from the last sample
- * of each stream.
+/* find the index of the sample that includes the data for @media_time
*
- * Returns: the minimum of the timestamps of the positions of all streams.
+ * Returns the index of the sample or n_samples when the sample was not
+ * found.
*/
/* FIXME, binary search would be nice here */
-static guint64
-gst_qtdemux_go_back (GstQTDemux * qtdemux, gboolean end, guint64 offset)
+static guint32
+gst_qtdemux_find_index (GstQTDemux * qtdemux, QtDemuxStream * str,
+ guint64 media_time)
{
- gint n;
- guint64 min_time = G_MAXUINT64;
+ guint32 i;
- /* resync to new time */
- for (n = 0; n < qtdemux->n_streams; n++) {
- QtDemuxStream *str;
- gboolean keyframe;
- gint search;
-
- str = qtdemux->streams[n];
- keyframe = str->all_keyframe;
+ if (str->n_samples == 0)
+ return 0;
- /* start from the last sample if @end == TRUE */
- if (end) {
- if (str->n_samples == 0)
- search = 0;
- else
- search = str->n_samples - 1;
- } else
- search = str->sample_index;
-
- for (; search > 0; search--) {
- guint64 timestamp;
-
- timestamp = str->samples[search].timestamp;
-
- /* Seek to the sample just before the desired offset and
- * let downstream throw away bits outside of the segment */
- if (timestamp <= offset) {
- /* update the keyframe flag */
- keyframe = keyframe | str->samples[search].keyframe;
- if (keyframe) {
- GST_DEBUG_OBJECT (qtdemux,
- "found keyframe at sample %d, %" GST_TIME_FORMAT, search,
- GST_TIME_ARGS (timestamp));
- /* update min_time */
- if (timestamp < min_time)
- min_time = timestamp;
- break;
- }
- }
+ for (i = 0; i < str->n_samples; i++) {
+ if (str->samples[i].timestamp > media_time) {
+ /* first sample after media_time, we need the previous one */
+ return (i == 0 ? 0 : i - 1);
}
+ }
+ return str->n_samples - 1;
+}
- /* did not find anything or we're at the beginning, position to beginning */
- if (search <= 0) {
- search = 0;
- min_time = 0;
- }
- /* and set stream to the index */
- if (search != str->sample_index) {
- str->sample_index = search;
- /* position changed, we have a discont */
- str->discont = TRUE;
+/* find the index of the keyframe needed to decode the sample at @index
+ * of stream @str.
+ *
+ * Returns the index of the keyframe.
+ */
+static guint32
+gst_qtdemux_find_keyframe (GstQTDemux * qtdemux, QtDemuxStream * str,
+ guint32 index)
+{
+ if (index >= str->n_samples)
+ return str->n_samples;
+
+ /* all keyframes, return index */
+ if (str->all_keyframe)
+ return index;
+
+ /* else go back until we have a keyframe */
+ while (TRUE) {
+ if (str->samples[index].keyframe)
+ break;
+
+ if (index == 0)
+ break;
+
+ index--;
+ }
+ return index;
+}
+
+/* find the segment for @time_position for @stream
+ *
+ * Returns -1 if the segment cannot be found.
+ */
+static guint32
+gst_qtdemux_find_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
+ guint64 time_position)
+{
+ gint i;
+ guint32 seg_idx;
+
+ /* find segment corresponding to time_position if we are looking
+ * for a segment. */
+ seg_idx = -1;
+ for (i = 0; i < stream->n_segments; i++) {
+ QtDemuxSegment *segment = &stream->segments[i];
+
+ if (segment->time <= time_position && time_position < segment->stop_time) {
+ seg_idx = i;
+ break;
}
}
- return min_time;
+ return seg_idx;
+}
+
+/* move the stream @str to the sample position @index.
+ *
+ * Updates @str->sample_index and marks discontinuity if needed.
+ */
+static void
+gst_qtdemux_move_stream (GstQTDemux * qtdemux, QtDemuxStream * str,
+ guint32 index)
+{
+ /* no change needed */
+ if (index == str->sample_index)
+ return;
+
+ GST_DEBUG_OBJECT (qtdemux, "moving to sample %u of %u", index,
+ str->n_samples);
+
+ /* position changed, we have a discont */
+ str->sample_index = index;
+ str->discont = TRUE;
}
/* perform the seek.
*
- * We always go to the keyframe before the desired seek position. If
- * the seek was to a keyframe, we update the last_stop and time with
- * the position of the keyframe, else we leve the event as-is and it
- * will be clipped automatically to the right segment boundaries by
- * downstream elements.
+ * We set all segment_indexes in the streams to unknown and
+ * adjust the time_position to the desired position. this is enough
+ * to trigger a segment switch in the streaming thread to start
+ * streaming from the desired position.
+ *
+ * Keyframe seeking is a little more complicated when dealing with
+ * segments. Ideally we want to move to the previous keyframe in
+ * the segment but there might not be a keyframe in the segment. In
+ * fact, none of the segments could contain a keyframe. We take a
+ * practical approach: seek to the previous keyframe in the segment,
+ * if there is none, seek to the beginning of the segment.
*/
static gboolean
gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment)
{
gint64 desired_offset;
- guint64 min;
+ gint n;
desired_offset = segment->last_stop;
GST_DEBUG_OBJECT (qtdemux, "seeking to %" GST_TIME_FORMAT,
GST_TIME_ARGS (desired_offset));
- /* position all streams to key unit before the desired time,
- * start searching from the last sample in the stream. */
- min = gst_qtdemux_go_back (qtdemux, TRUE, desired_offset);
-
if (segment->flags & GST_SEEK_FLAG_KEY_UNIT) {
- GST_DEBUG_OBJECT (qtdemux, "keyframe seek, align to %" GST_TIME_FORMAT,
- GST_TIME_ARGS (min));
- /* key unit, we seek to min, so back off streams to this new
- * position. We start from our current position. */
- gst_qtdemux_go_back (qtdemux, FALSE, min);
+ guint64 min_offset;
+
+ min_offset = desired_offset;
+
+ /* 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++) {
+ QtDemuxStream *str;
+ guint32 index;
+ guint32 seg_idx;
+ guint64 media_start;
+ guint64 media_time;
+ guint64 seg_time;
+ QtDemuxSegment *seg;
+
+ str = qtdemux->streams[n];
+
+ seg_idx = gst_qtdemux_find_segment (qtdemux, str, desired_offset);
+ GST_DEBUG_OBJECT (qtdemux, "align segment %d", seg_idx);
+
+ /* segment not found, continue with normal flow */
+ if (seg_idx == -1)
+ continue;
+
+ /* get segment and time in the segment */
+ seg = &str->segments[seg_idx];
+ seg_time = desired_offset - seg->time;
+
+ /* get the media time in the segment */
+ media_start = seg->media_start + seg_time;
+
+ /* get the index of the sample with media time */
+ index = gst_qtdemux_find_index (qtdemux, str, media_start);
+ GST_DEBUG_OBJECT (qtdemux, "sample for %" GST_TIME_FORMAT " at %u",
+ GST_TIME_ARGS (media_start), index);
+ /* find previous keyframe */
+ index = gst_qtdemux_find_keyframe (qtdemux, str, index);
+ /* get timestamp of keyframe */
+ media_time = str->samples[index].timestamp;
+
+ GST_DEBUG_OBJECT (qtdemux, "keyframe at %u with time %" GST_TIME_FORMAT,
+ index, GST_TIME_ARGS (media_time));
+
+ /* keyframes in the segment get a chance to change the
+ * desired_offset. keyframes out of the segment are
+ * ignored. */
+ if (media_time >= seg->media_start) {
+ guint64 seg_time;
+
+ /* this keyframe is inside the segment, convert back to
+ * segment time */
+ seg_time = (media_time - seg->media_start) + seg->time;
+ if (seg_time < min_offset)
+ min_offset = seg_time;
+ }
+ }
+ desired_offset = min_offset;
+
+ GST_DEBUG_OBJECT (qtdemux, "keyframe seek, align to %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (desired_offset));
+ }
+
+ /* and set all streams to the final position */
+ for (n = 0; n < qtdemux->n_streams; n++) {
+ QtDemuxStream *stream = qtdemux->streams[n];
- /* update the segment values to the position of the keyframes */
- segment->last_stop = min;
- segment->time = min;
+ stream->time_position = desired_offset;
+ stream->sample_index = 0;
+ stream->segment_index = -1;
}
+ segment->last_stop = desired_offset;
+ segment->time = desired_offset;
- /* and we stop at the end */
+ /* we stop at the end */
if (segment->stop == -1)
segment->stop = segment->duration;
@@ -601,7 +721,6 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
gboolean res;
gboolean update;
GstSegment seeksegment;
- GstEvent *newsegment;
if (event) {
GST_DEBUG_OBJECT (qtdemux, "doing seek with event");
@@ -690,19 +809,8 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
qtdemux->segment.format, qtdemux->segment.last_stop));
}
- /* send the newsegment */
- GST_DEBUG_OBJECT (qtdemux, "Sending newsegment from %" GST_TIME_FORMAT
- " to %" GST_TIME_FORMAT, GST_TIME_ARGS (qtdemux->segment.start),
- GST_TIME_ARGS (qtdemux->segment.stop));
-
- newsegment =
- gst_event_new_new_segment (FALSE, qtdemux->segment.rate,
- qtdemux->segment.format, qtdemux->segment.last_stop,
- qtdemux->segment.stop, qtdemux->segment.time);
-
- gst_qtdemux_push_event (qtdemux, newsegment);
-
- /* restart streaming */
+ /* restart streaming, NEWSEGMENT will be sent from the streaming
+ * thread. */
qtdemux->segment_running = TRUE;
gst_pad_start_task (qtdemux->sinkpad, (GstTaskFunction) gst_qtdemux_loop,
qtdemux->sinkpad);
@@ -812,6 +920,7 @@ gst_qtdemux_change_state (GstElement * element, GstStateChange transition)
if (qtdemux->streams[n]->caps)
gst_caps_unref (qtdemux->streams[n]->caps);
g_free (qtdemux->streams[n]);
+ g_free (qtdemux->streams[n]->segments);
}
qtdemux->n_streams = 0;
gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
@@ -925,6 +1034,208 @@ beach:
return ret;
}
+/* activate the given segment number @seg_idx of @stream at time @offset.
+ * @offset is an absolute global position over all the segments.
+ *
+ * This will push out a NEWSEGMENT event with the right values and
+ * position the stream index to the first decodable sample before
+ * @offset.
+ */
+static gboolean
+gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
+ guint32 seg_idx, guint64 offset)
+{
+ GstEvent *event;
+ QtDemuxSegment *segment;
+ guint32 index, kf_index;
+ guint64 seg_time;
+ guint64 start, stop;
+
+ /* update the current segment */
+ stream->segment_index = seg_idx;
+
+ /* get the segment */
+ segment = &stream->segments[seg_idx];
+
+ if (offset < segment->time)
+ return FALSE;
+
+ /* get time in this segment */
+ seg_time = offset - segment->time;
+
+ if (seg_time >= segment->duration)
+ return FALSE;
+
+ /* calc media start/stop */
+ start = segment->media_start + seg_time;
+ stop = segment->media_stop;
+
+ GST_DEBUG_OBJECT (qtdemux, "newsegment %d from %" GST_TIME_FORMAT
+ " to %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, seg_idx,
+ GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (offset));
+
+ event = gst_event_new_new_segment (FALSE, segment->rate, GST_FORMAT_TIME,
+ start, stop, offset);
+
+ gst_pad_push_event (stream->pad, event);
+
+ /* and move to the keyframe before the indicated media time of the
+ * segment */
+ index = gst_qtdemux_find_index (qtdemux, stream, start);
+
+ GST_DEBUG_OBJECT (qtdemux, "moving data pointer to %" GST_TIME_FORMAT
+ ", index: %u", GST_TIME_ARGS (start), index);
+
+ /* we're at the right spot */
+ if (index == stream->sample_index)
+ return TRUE;
+
+ /* find keyframe of the target index */
+ kf_index = gst_qtdemux_find_keyframe (qtdemux, stream, index);
+
+ /* if we move forwards, we don't have to go back to the previous
+ * keyframe since we already sent that. We can also just jump to
+ * the keyframe right before the target index if there is one. */
+ if (index > stream->sample_index) {
+ /* moving forwards check if we move past a keyframe */
+ if (kf_index > stream->sample_index) {
+ GST_DEBUG_OBJECT (qtdemux, "moving forwards to keyframe at %u", kf_index);
+ gst_qtdemux_move_stream (qtdemux, stream, kf_index);
+ } else {
+ GST_DEBUG_OBJECT (qtdemux, "moving forwards, keyframe at %u already sent",
+ kf_index);
+ }
+ } else {
+ GST_DEBUG_OBJECT (qtdemux, "moving backwards to keyframe at %u", kf_index);
+ gst_qtdemux_move_stream (qtdemux, stream, kf_index);
+ }
+
+ return TRUE;
+}
+
+/* prepare to get the current sample of @stream, getting essential values.
+ *
+ * This function will also prepare and send the segment when needed.
+ *
+ * Return FALSE if the stream is EOS.
+ */
+static gboolean
+gst_qtdemux_prepare_current_sample (GstQTDemux * qtdemux,
+ QtDemuxStream * stream, guint64 * offset, guint * size, guint64 * timestamp,
+ guint32 * duration, gboolean * keyframe)
+{
+ QtDemuxSample *sample;
+ guint64 time_position;
+ guint32 seg_idx;
+
+ g_return_val_if_fail (stream != NULL, FALSE);
+
+ time_position = stream->time_position;
+ if (time_position == -1)
+ goto eos;
+
+ seg_idx = stream->segment_index;
+ if (seg_idx == -1) {
+ /* find segment corresponding to time_position if we are looking
+ * for a segment. */
+ seg_idx = gst_qtdemux_find_segment (qtdemux, stream, time_position);
+
+ /* nothing found, we're really eos */
+ if (seg_idx == -1)
+ goto eos;
+ }
+
+ /* different segment, activate it, sample_index will be set. */
+ if (stream->segment_index != seg_idx)
+ gst_qtdemux_activate_segment (qtdemux, stream, seg_idx, time_position);
+
+ /* now get the info for the sample we're at */
+ sample = &stream->samples[stream->sample_index];
+
+ *offset = sample->offset;
+ *size = sample->size;
+
+ /* timestamps of AMR aren't known... FIXME */
+ if (stream->fourcc == GST_MAKE_FOURCC ('s', 'a', 'm', 'r')) {
+ *duration = -1;
+ if (stream->sample_index == 0)
+ *timestamp = 0;
+ else
+ *timestamp = -1;
+ } else {
+ *timestamp = sample->timestamp;
+ *duration = gst_util_uint64_scale_int
+ (sample->duration, GST_SECOND, stream->timescale);
+ }
+ *keyframe = stream->all_keyframe || sample->keyframe;
+
+ return TRUE;
+
+ /* special cases */
+eos:
+ {
+ stream->time_position = -1;
+ return FALSE;
+ }
+}
+
+/* move to the next sample in @stream.
+ *
+ * Moves to the next segment when needed.
+ */
+static void
+gst_qtdemux_advance_sample (GstQTDemux * qtdemux, QtDemuxStream * stream)
+{
+ QtDemuxSample *sample;
+ QtDemuxSegment *segment;
+
+ /* move to next sample */
+ stream->sample_index++;
+
+ /* get current segment */
+ segment = &stream->segments[stream->segment_index];
+
+ /* reached the last sample, we need the next segment */
+ if (stream->sample_index == stream->n_samples)
+ goto next_segment;
+
+ /* get next sample */
+ sample = &stream->samples[stream->sample_index];
+
+ /* see if we are past the segment */
+ if (sample->timestamp >= segment->media_stop)
+ goto next_segment;
+
+ if (sample->timestamp >= segment->media_start) {
+ /* inside the segment, update time_position, looks very familiar to
+ * GStreamer segments, doesn't it? */
+ stream->time_position =
+ (sample->timestamp - segment->media_start) + segment->time;
+ } else {
+ /* not yet in segment, time does not yet increment. This means
+ * that we are still prerolling keyframes to the decoder so it can
+ * decode the first sample of the segment. */
+ stream->time_position = segment->time;
+ }
+ return;
+
+ /* move to the next segment */
+next_segment:
+ {
+ GST_DEBUG_OBJECT (qtdemux, "segment %d ended ", stream->segment_index);
+
+ if (stream->segment_index == stream->n_segments - 1) {
+ /* are we at the end of the last segment, we're EOS */
+ stream->time_position = -1;
+ } else {
+ /* else we're only at the end of the current segment */
+ stream->time_position = segment->stop_time;
+ }
+ /* make sure we select a new segment */
+ stream->segment_index = -1;
+ }
+}
+
static GstFlowReturn
gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
{
@@ -934,29 +1245,29 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
guint64 min_time;
guint64 offset;
guint64 timestamp;
- gint size;
- gint index = -1;
+ guint32 duration;
+ gboolean keyframe;
+ guint size;
+ gint index;
gint i;
- /* Figure out the next stream sample to output */
+ /* 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++) {
- stream = qtdemux->streams[i];
- if (stream->sample_index < stream->n_samples) {
-
- timestamp = stream->samples[stream->sample_index].timestamp;
+ guint64 position;
- GST_LOG_OBJECT (qtdemux,
- "stream %d: sample_index %d, timestamp %" GST_TIME_FORMAT, i,
- stream->sample_index, GST_TIME_ARGS (timestamp));
+ stream = qtdemux->streams[i];
+ position = stream->time_position;
- if (timestamp < min_time) {
- min_time = timestamp;
- index = i;
- }
+ /* position of -1 is EOS */
+ if (position != -1 && position < min_time) {
+ min_time = position;
+ index = i;
}
}
+ /* all are EOS */
if (index == -1)
goto eos;
@@ -966,17 +1277,19 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
stream = qtdemux->streams[index];
- offset = stream->samples[stream->sample_index].offset;
- size = stream->samples[stream->sample_index].size;
- timestamp = stream->samples[stream->sample_index].timestamp;
+ /* fetch info for the current sample of this stream */
+ if (!gst_qtdemux_prepare_current_sample (qtdemux, stream, &offset, &size,
+ &timestamp, &duration, &keyframe))
+ goto eos;
GST_LOG_OBJECT (qtdemux,
- "pushing from stream %d, sample_index=%d offset=%" G_GUINT64_FORMAT
+ "pushing from stream %d, offset=%" G_GUINT64_FORMAT
",size=%d timestamp=%" GST_TIME_FORMAT,
- index, stream->sample_index, offset, size, GST_TIME_ARGS (timestamp));
+ index, offset, size, GST_TIME_ARGS (timestamp));
+ /* hmm, empty sample, skip and move to next sample */
if (G_UNLIKELY (size <= 0))
- goto beach;
+ goto next;
GST_LOG_OBJECT (qtdemux, "reading %d bytes @ %" G_GUINT64_FORMAT, size,
offset);
@@ -985,59 +1298,33 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
if (ret != GST_FLOW_OK)
goto beach;
+ /* we're going to modify the metadata */
buf = gst_buffer_make_metadata_writable (buf);
-#if 0
- /* hum... FIXME changing framerate breaks horribly, better set
- * an average framerate, or get rid of the framerate property. */
- if (stream->subtype == GST_MAKE_FOURCC ('v', 'i', 'd', 'e')) {
- float fps =
- 1. * GST_SECOND / stream->samples[stream->sample_index].duration;
- if (fps != stream->fps) {
- gst_caps_set_simple (stream->caps, "framerate", G_TYPE_DOUBLE, fps, NULL);
- stream->fps = fps;
- gst_pad_set_explicit_caps (stream->pad, stream->caps);
- }
- }
-#endif
-
- /* first buffer? */
- if (qtdemux->last_ts == GST_CLOCK_TIME_NONE) {
- gst_qtdemux_push_event (qtdemux,
- gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME,
- 0, GST_CLOCK_TIME_NONE, 0));
- }
-
if (stream->discont) {
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
stream->discont = FALSE;
}
- /* timestamps of AMR aren't known... */
- if (stream->fourcc == GST_MAKE_FOURCC ('s', 'a', 'm', 'r')) {
- if (stream->sample_index == 0)
- GST_BUFFER_TIMESTAMP (buf) = 0;
- } else {
- GST_BUFFER_TIMESTAMP (buf) = timestamp;
- qtdemux->last_ts = GST_BUFFER_TIMESTAMP (buf);
- GST_BUFFER_DURATION (buf) = gst_util_uint64_scale_int
- (stream->samples[stream->sample_index].duration, GST_SECOND,
- stream->timescale);
- }
- gst_segment_set_last_stop (&qtdemux->segment, GST_FORMAT_TIME,
- qtdemux->last_ts);
+ GST_BUFFER_TIMESTAMP (buf) = timestamp;
+ GST_BUFFER_DURATION (buf) = duration;
+
+ qtdemux->last_ts = min_time;
+ gst_segment_set_last_stop (&qtdemux->segment, GST_FORMAT_TIME, min_time);
- if (!(stream->all_keyframe || stream->samples[stream->sample_index].keyframe))
+ if (!keyframe)
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
GST_LOG_OBJECT (qtdemux,
"Pushing buffer with time %" GST_TIME_FORMAT " on pad %p",
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), stream->pad);
+ GST_TIME_ARGS (timestamp), stream->pad);
+
gst_buffer_set_caps (buf, stream->caps);
ret = gst_pad_push (stream->pad, buf);
- stream->sample_index++;
+next:
+ gst_qtdemux_advance_sample (qtdemux, stream);
beach:
return ret;
@@ -1096,10 +1383,15 @@ gst_qtdemux_loop (GstPad * pad)
/* check EOS */
if (ret == GST_FLOW_UNEXPECTED) {
if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
+ gint64 stop;
+
+ if ((stop = qtdemux->segment.stop) == -1)
+ stop = qtdemux->segment.duration;
+
GST_LOG_OBJECT (qtdemux, "Sending segment done, at end of segment");
gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux),
- GST_FORMAT_TIME, qtdemux->last_ts));
+ GST_FORMAT_TIME, stop));
} else {
GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment");
gst_qtdemux_push_event (qtdemux, gst_event_new_eos ());
@@ -1191,6 +1483,7 @@ gst_qtdemux_post_buffering (GstQTDemux * demux, gint num, gint denom)
"buffer-percent", G_TYPE_INT, perc, NULL)));
}
+/* FIXME, unverified after edit list updates */
static GstFlowReturn
gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
{
@@ -2664,6 +2957,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GNode *mp4v;
GNode *wave;
GNode *esds;
+ GNode *edts;
int n_samples;
QtDemuxSample *samples;
int n_samples_per_chunk;
@@ -2683,8 +2977,6 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GST_LOG ("track[tkhd] version/flags: 0x%08x",
QTDEMUX_GUINT32_GET (tkhd->data + 8));
- /* track duration? */
-
mdia = qtdemux_tree_get_child_by_type (trak, FOURCC_mdia);
g_assert (mdia);
@@ -2694,24 +2986,35 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
/* new streams always need a discont */
stream = g_new0 (QtDemuxStream, 1);
stream->discont = TRUE;
+ stream->segment_index = -1;
+ stream->time_position = 0;
+ stream->sample_index = 0;
stream->timescale = QTDEMUX_GUINT32_GET (mdhd->data + 20);
+ stream->duration = QTDEMUX_GUINT32_GET (mdhd->data + 24);
+
GST_LOG ("track timescale: %d", stream->timescale);
- GST_LOG ("track duration: %d", QTDEMUX_GUINT32_GET (mdhd->data + 24));
-
- /* HACK:
- * some of those trailers, nowadays, have prologue images that are
- * themselves vide tracks as well. I haven't really found a way to
- * identify those yet, except for just looking at their duration. */
- if (stream->timescale * qtdemux->duration != 0 &&
- (guint64) QTDEMUX_GUINT32_GET (mdhd->data + 24) *
- qtdemux->timescale * 10 / (stream->timescale * qtdemux->duration) < 2) {
- GST_WARNING ("Track shorter than 20%% (%d/%d vs. %d/%d) of the stream "
- "found, assuming preview image or something; skipping track",
- QTDEMUX_GUINT32_GET (mdhd->data + 24), stream->timescale,
- qtdemux->duration, qtdemux->timescale);
- g_free (stream);
- return;
+ GST_LOG ("track duration: %d", stream->duration);
+
+ {
+ guint64 tdur1, tdur2;
+
+ /* don't overflow */
+ tdur1 = stream->timescale * (guint64) qtdemux->duration;
+ tdur2 = qtdemux->timescale * (guint64) stream->duration;
+
+ /* HACK:
+ * some of those trailers, nowadays, have prologue images that are
+ * themselves vide tracks as well. I haven't really found a way to
+ * identify those yet, except for just looking at their duration. */
+ if (tdur1 != 0 && (tdur2 * 10 / tdur1) < 2) {
+ GST_WARNING ("Track shorter than 20%% (%d/%d vs. %d/%d) of the stream "
+ "found, assuming preview image or something; skipping track",
+ stream->duration, stream->timescale, qtdemux->duration,
+ qtdemux->timescale);
+ g_free (stream);
+ return;
+ }
}
hdlr = qtdemux_tree_get_child_by_type (mdia, FOURCC_hdlr);
@@ -3000,7 +3303,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GST_DEBUG_OBJECT (qtdemux, "stsz sample_size 0, allocating n_samples %d",
n_samples);
stream->n_samples = n_samples;
- samples = g_malloc (sizeof (QtDemuxSample) * n_samples);
+ samples = g_new0 (QtDemuxSample, n_samples);
stream->samples = samples;
for (i = 0; i < n_samples; i++) {
@@ -3013,12 +3316,12 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
index = 0;
offset = 16;
for (i = 0; i < n_samples_per_chunk; i++) {
- int first_chunk, last_chunk;
- int samples_per_chunk;
+ guint32 first_chunk, last_chunk;
+ guint32 samples_per_chunk;
first_chunk = QTDEMUX_GUINT32_GET (stsc->data + 16 + i * 12 + 0) - 1;
if (i == n_samples_per_chunk - 1) {
- last_chunk = INT_MAX;
+ last_chunk = G_MAXUINT32;
} else {
last_chunk = QTDEMUX_GUINT32_GET (stsc->data + 16 + i * 12 + 12) - 1;
}
@@ -3050,19 +3353,24 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
timestamp = 0;
index = 0;
for (i = 0; i < n_sample_times; i++) {
- int n;
- int duration;
- guint64 time;
+ guint32 n;
+ guint32 duration;
n = QTDEMUX_GUINT32_GET (stts->data + 16 + 8 * i);
duration = QTDEMUX_GUINT32_GET (stts->data + 16 + 8 * i + 4);
- time =
- gst_util_uint64_scale_int (duration, GST_SECOND, stream->timescale);
for (j = 0; j < n; j++) {
- //GST_INFO("moo %lld", timestamp);
- samples[index].timestamp = timestamp;
+ guint64 time;
+
+ time = gst_util_uint64_scale_int (timestamp,
+ GST_SECOND, stream->timescale);
+
+ GST_INFO_OBJECT (qtdemux, "sample %d: timestamp %" GST_TIME_FORMAT,
+ index, GST_TIME_ARGS (time));
+
+ samples[index].timestamp = time;
samples[index].duration = duration;
- timestamp += time;
+ /* add non-scaled values to avoid rounding errors */
+ timestamp += duration;
index++;
}
}
@@ -3100,7 +3408,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
}
stream->n_samples = n_samples;
GST_DEBUG_OBJECT (qtdemux, "allocating n_samples %d", n_samples);
- samples = g_malloc (sizeof (QtDemuxSample) * n_samples);
+ samples = g_new0 (QtDemuxSample, n_samples);
stream->samples = samples;
n_samples_per_chunk = QTDEMUX_GUINT32_GET (stsc->data + 12);
@@ -3151,6 +3459,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
samples[j].size = stream->bytes_per_frame;
else
samples[j].size = sample_size;
+ /* FIXME, divide by 2? must be sample size or something.. */
samples[j].duration =
samples_per_chunk * stream->timescale / (stream->rate / 2);
samples[j].timestamp = timestamp;
@@ -3160,28 +3469,89 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
timestamp += gst_util_uint64_scale_int (samples_per_chunk,
GST_SECOND, stream->rate);
}
-#if 0
- GST_INFO_OBJECT (qtdemux,
- "moo samples_per_chunk=%d rate=%d dur=%lld %lld",
- (int) samples_per_chunk, (int) stream->rate,
- (long long) ((samples_per_chunk * GST_SECOND) / stream->rate),
- (long long) timestamp);
-#endif
- samples[j].sample_index = sample_index;
sample_index += samples_per_chunk;
}
}
}
done2:
-#if 0
- for (i = 0; i < n_samples; i++) {
- GST_LOG ("%d: %d %d %d %d %" G_GUINT64_FORMAT, i,
- samples[i].sample_index, samples[i].chunk,
- samples[i].offset, samples[i].size, samples[i].timestamp);
- if (i > 10)
- break;
+
+ /* parse and prepare segment info from the edit list */
+ GST_DEBUG_OBJECT (qtdemux, "looking for edit list container");
+ if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) {
+ GNode *elst;
+ gint n_segments;
+ gint i, count;
+ guint64 time, stime;
+ guint8 *buffer;
+
+ GST_DEBUG_OBJECT (qtdemux, "looking for edit list");
+ if (!(elst = qtdemux_tree_get_child_by_type (edts, FOURCC_elst)))
+ goto done3;
+
+ buffer = elst->data;
+
+ n_segments = QTDEMUX_GUINT32_GET (buffer + 12);
+
+ /* we might allocate a bit too much, at least allocate 1 segment */
+ stream->segments = g_new (QtDemuxSegment, MAX (n_segments, 1));
+
+ /* segments always start from 0 */
+ time = 0;
+ stime = 0;
+ count = 0;
+ for (i = 0; i < n_segments; i++) {
+ guint64 duration;
+ guint64 media_time;
+ QtDemuxSegment *segment;
+
+ media_time = QTDEMUX_GUINT32_GET (buffer + 20 + i * 12);
+
+ /* -1 media time is an empty segment, just ignore it */
+ if (media_time == G_MAXUINT32)
+ continue;
+
+ duration = QTDEMUX_GUINT32_GET (buffer + 16 + i * 12);
+
+ segment = &stream->segments[count++];
+
+ /* time and duration expressed in global timescale */
+ segment->time = stime;
+ /* add non scaled values so we don't cause roundoff errors */
+ time += duration;
+ stime = gst_util_uint64_scale_int (time, GST_SECOND, qtdemux->timescale);
+ segment->stop_time = stime;
+ segment->duration = stime - segment->time;
+ /* media_time expressed in stream timescale */
+ segment->media_start =
+ gst_util_uint64_scale_int (media_time, GST_SECOND, stream->timescale);
+ segment->media_stop = segment->media_start + segment->duration;
+ segment->rate = QTDEMUX_FP32_GET (buffer + 24 + i * 12);
+
+ GST_DEBUG_OBJECT (qtdemux, "created segment %d time %" GST_TIME_FORMAT
+ ", duration %" GST_TIME_FORMAT ", media_time %" GST_TIME_FORMAT
+ ", rate %g", i, GST_TIME_ARGS (segment->time),
+ GST_TIME_ARGS (segment->duration),
+ GST_TIME_ARGS (segment->media_start), segment->rate);
+ }
+ GST_DEBUG_OBJECT (qtdemux, "found %d non-empty segments", count);
+
+ if (count == 0) {
+ /* no segments, create one to play the complete trak */
+ count = 1;
+ stream->segments[0].time = 0;
+ stream->segments[0].stop_time = stream->duration;
+ stream->segments[0].duration = stream->duration;
+ stream->segments[0].media_start = 0;
+ stream->segments[0].media_stop = stream->duration;
+ stream->segments[0].rate = 1.0;
+
+ GST_DEBUG_OBJECT (qtdemux, "created dummy segment");
+ }
+ GST_DEBUG_OBJECT (qtdemux, "using %d segments", count);
+ stream->n_segments = count;
}
-#endif
+done3:
+
gst_qtdemux_add_stream (qtdemux, stream, list);
}