gst/qtdemux/qtdemux.c: More cleanups, added comments.

Original commit message from CVS:
* gst/qtdemux/qtdemux.c: (gst_qtdemux_go_back),
(gst_qtdemux_perform_seek), (gst_qtdemux_do_seek),
(gst_qtdemux_loop_state_movie), (gst_qtdemux_loop),
(gst_qtdemux_chain), (qtdemux_parse_tree), (qtdemux_parse_trak):
More cleanups, added comments.
Mark discontinuities on outgoing buffers.
Post better errors when something goes wrong.
Handle EOS and segment end properly.
This commit is contained in:
Wim Taymans 2006-04-04 11:20:58 +00:00
parent 075303cf8e
commit 378bcc051d

View file

@ -1,6 +1,7 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2003> David A. Schleef <ds@schleef.org>
* Copyright (C) <2006> Wim Taymans <wim@fluendo.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@ -53,22 +54,22 @@ struct _QtNode
{
guint32 type;
gpointer data;
int len;
gint len;
};
struct _QtNodeType
{
guint32 fourcc;
char *name;
int flags;
gchar *name;
gint flags;
void (*dump) (GstQTDemux * qtdemux, void *buffer, int depth);
};
struct _QtDemuxSample
{
int sample_index;
int chunk;
int size;
gint sample_index;
gint chunk;
gint size;
guint64 offset;
guint64 timestamp; /* In GstClockTime */
guint32 duration; /* in stream->timescale units */
@ -81,26 +82,29 @@ struct _QtDemuxStream
GstCaps *caps;
guint32 fourcc;
GstPad *pad;
int n_samples;
gint n_samples;
QtDemuxSample *samples;
int timescale;
gint timescale;
gboolean all_keyframe; /* TRUE when all packets are keyframes (no stss) */
int sample_index;
gint sample_index;
int width;
int height;
gint width;
gint height;
/* Numerator/denominator framerate */
gint fps_n;
gint fps_d;
double rate;
int n_channels;
gdouble rate;
gint n_channels;
guint bytes_per_frame;
guint compression;
guint samples_per_packet;
guint16 bits_per_sample;
guint16 color_table_id;
/* when a discontinuity is pending */
gboolean discont;
};
enum QtDemuxState
@ -472,14 +476,16 @@ gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
gst_event_unref (event);
}
/* move all streams back to the given position, This function will
* always position the streams on a keyframe before offset.
/* move all streams back on the keyframe before @offset.
*
* Start to search back from the stream sample_index position or
* when end is TRUE, from the last sample of the stream.
* 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.
*
* Returns the minimum of the timestamps of the positions of all streams.
* Returns: the minimum of the timestamps of the positions of all streams.
*/
/* FIXME, binary search would be nice here */
static guint64
gst_qtdemux_go_back (GstQTDemux * qtdemux, gboolean end, guint64 offset)
{
@ -490,34 +496,35 @@ gst_qtdemux_go_back (GstQTDemux * qtdemux, gboolean end, guint64 offset)
for (n = 0; n < qtdemux->n_streams; n++) {
QtDemuxStream *str;
gboolean keyframe;
gint i;
gint search;
str = qtdemux->streams[n];
keyframe = str->all_keyframe;
if (end)
str->sample_index = str->n_samples - 1;
/* 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;
if (str->sample_index == 0) {
/* can't go back, we're at the beginning */
min_time = 0;
continue;
}
for (i = str->sample_index; i >= 0; i--) {
for (; search > 0; search--) {
guint64 timestamp;
timestamp = str->samples[i].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[i].keyframe;
keyframe = keyframe | str->samples[search].keyframe;
if (keyframe) {
GST_DEBUG_OBJECT (qtdemux,
"found keyframe at sample %d, %" GST_TIME_FORMAT, i,
"found keyframe at sample %d, %" GST_TIME_FORMAT, search,
GST_TIME_ARGS (timestamp));
/* update min_time */
if (timestamp < min_time)
min_time = timestamp;
break;
@ -525,13 +532,17 @@ gst_qtdemux_go_back (GstQTDemux * qtdemux, gboolean end, guint64 offset)
}
}
/* did not find anything, position to beginning */
if (i <= 0) {
i = 0;
/* did not find anything or we're at the beginning, position to beginning */
if (search <= 0) {
search = 0;
min_time = 0;
}
str->sample_index = i;
/* and set stream to the index */
if (search != str->sample_index) {
str->sample_index = search;
/* position changed, we have a discont */
str->discont = TRUE;
}
}
return min_time;
}
@ -539,9 +550,10 @@ gst_qtdemux_go_back (GstQTDemux * qtdemux, gboolean end, guint64 offset)
/* 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 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.
* 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.
*/
static gboolean
gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment)
@ -565,13 +577,14 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment)
* position. We start from our current position. */
gst_qtdemux_go_back (qtdemux, FALSE, min);
/* update the segment to the position of the keyframes */
/* update the segment values to the position of the keyframes */
segment->last_stop = min;
segment->time = min;
}
return TRUE;
}
/* do a seek in pull based mode */
static gboolean
gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
{
@ -617,12 +630,14 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
GST_DEBUG_OBJECT (qtdemux, "seek format %d", format);
/* stop streaming, either by flushing or by pausing the task */
if (flush) {
/* unlock upstream pull_range */
gst_pad_push_event (qtdemux->sinkpad, gst_event_new_flush_start ());
/* make sure out loop function exits */
gst_qtdemux_push_event (qtdemux, gst_event_new_flush_start ());
} else {
/* non flushing seek, pause the task */
qtdemux->segment_running = FALSE;
gst_pad_pause_task (qtdemux->sinkpad);
}
@ -641,7 +656,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
cur_type, cur, stop_type, stop, &update);
}
/* now do the seek */
/* now do the seek, this actually never returns FALSE */
res = gst_qtdemux_perform_seek (qtdemux, &seeksegment);
/* prepare for streaming again */
@ -662,6 +677,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
qtdemux->segment.time));
}
/* commit the new segment */
memcpy (&qtdemux->segment, &seeksegment, sizeof (GstSegment));
if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
@ -670,7 +686,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
qtdemux->segment.format, qtdemux->segment.last_stop));
}
/* now send the newsegment */
/* send the newsegment */
GST_DEBUG_OBJECT (qtdemux, "Sending newsegment from %" G_GINT64_FORMAT
" to %" G_GINT64_FORMAT, qtdemux->segment.start, stop);
@ -681,11 +697,11 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
gst_qtdemux_push_event (qtdemux, newsegment);
/* restart streaming */
qtdemux->segment_running = TRUE;
gst_pad_start_task (qtdemux->sinkpad, (GstTaskFunction) gst_qtdemux_loop,
qtdemux->sinkpad);
/* and restart */
GST_PAD_STREAM_UNLOCK (qtdemux->sinkpad);
return TRUE;
@ -912,22 +928,26 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
QtDemuxStream *stream;
guint64 min_time;
guint64 offset;
int size;
int index = -1;
int i;
guint64 timestamp;
gint size;
gint index = -1;
gint i;
/* Figure out the next stream sample to output */
min_time = G_MAXUINT64;
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;
GST_LOG_OBJECT (qtdemux,
"stream %d: sample_index %d, timestamp %" GST_TIME_FORMAT, i,
stream->sample_index,
GST_TIME_ARGS (stream->samples[stream->sample_index].timestamp));
stream->sample_index, GST_TIME_ARGS (timestamp));
if (stream->samples[stream->sample_index].timestamp < min_time) {
min_time = stream->samples[stream->sample_index].timestamp;
if (timestamp < min_time) {
min_time = timestamp;
index = i;
}
}
@ -935,6 +955,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
if (index == -1) {
GST_DEBUG_OBJECT (qtdemux, "No samples left for any streams - EOS");
gst_pad_event_default (qtdemux->sinkpad, gst_event_new_eos ());
ret = GST_FLOW_UNEXPECTED;
goto beach;
}
@ -942,39 +963,38 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
offset = stream->samples[stream->sample_index].offset;
size = stream->samples[stream->sample_index].size;
timestamp = stream->samples[stream->sample_index].timestamp;
GST_LOG_OBJECT (qtdemux,
"pushing from stream %d, sample_index=%d offset=%" G_GUINT64_FORMAT
",size=%d timestamp=%" GST_TIME_FORMAT,
index, stream->sample_index, offset, size,
GST_TIME_ARGS (stream->samples[stream->sample_index].timestamp));
index, stream->sample_index, offset, size, GST_TIME_ARGS (timestamp));
if (G_UNLIKELY (size <= 0))
goto beach;
buf = NULL;
if (size > 0) {
GST_LOG_OBJECT (qtdemux, "reading %d bytes @ %" G_GUINT64_FORMAT, size,
offset);
ret = gst_pad_pull_range (qtdemux->sinkpad, offset, size, &buf);
if (ret != GST_FLOW_OK)
goto beach;
}
if (buf) {
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;
/*
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);
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) {
@ -983,13 +1003,17 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
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) =
stream->samples[stream->sample_index].timestamp;
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,
@ -998,10 +1022,8 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
gst_segment_set_last_stop (&qtdemux->segment, GST_FORMAT_TIME,
qtdemux->last_ts);
if (!(stream->all_keyframe
|| stream->samples[stream->sample_index].keyframe)) {
if (!(stream->all_keyframe || stream->samples[stream->sample_index].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",
@ -1009,7 +1031,6 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
gst_buffer_set_caps (buf, stream->caps);
ret = gst_pad_push (stream->pad, buf);
}
stream->sample_index++;
@ -1020,9 +1041,11 @@ beach:
static void
gst_qtdemux_loop (GstPad * pad)
{
GstQTDemux *qtdemux = GST_QTDEMUX (GST_OBJECT_PARENT (pad));
GstQTDemux *qtdemux;
guint64 cur_offset;
GstFlowReturn ret = GST_FLOW_ERROR;
GstFlowReturn ret;
qtdemux = GST_QTDEMUX (gst_pad_get_parent (pad));
cur_offset = qtdemux->offset;
GST_LOG_OBJECT (qtdemux, "loop at position %" G_GUINT64_FORMAT ", state %d",
@ -1037,21 +1060,58 @@ gst_qtdemux_loop (GstPad * pad)
ret = gst_qtdemux_loop_state_movie (qtdemux);
break;
default:
/* oh crap */
g_error ("State=%d", qtdemux->state);
/* ouch */
goto invalid_state;
}
if ((ret != GST_FLOW_OK) && (ret != GST_FLOW_NOT_LINKED)) {
GST_LOG_OBJECT (qtdemux, "pausing task, reason %s",
gst_flow_get_name (ret));
/* if all is fine, continue */
if (G_LIKELY (ret == GST_FLOW_OK))
goto done;
/* we don't care about unlinked pads */
if (ret == GST_FLOW_NOT_LINKED)
goto done;
/* other errors make us stop */
GST_LOG_OBJECT (qtdemux, "pausing task, reason %s", gst_flow_get_name (ret));
qtdemux->segment_running = FALSE;
gst_pad_pause_task (qtdemux->sinkpad);
/* fatal errors need special actions */
if (GST_FLOW_IS_FATAL (ret)) {
/* check EOS */
if (ret == GST_FLOW_UNEXPECTED) {
if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
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));
} else {
GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment");
gst_qtdemux_push_event (qtdemux, gst_event_new_eos ());
}
} else {
gst_qtdemux_push_event (qtdemux, gst_event_new_eos ());
GST_ELEMENT_ERROR (qtdemux, STREAM, FAILED,
(NULL), ("stream stopped, reason %s", gst_flow_get_name (ret)));
(NULL), ("streaming stopped, reason %s", gst_flow_get_name (ret)));
}
}
done:
gst_object_unref (qtdemux);
return;
/* ERRORS */
invalid_state:
{
GST_ELEMENT_ERROR (qtdemux, STREAM, FAILED,
(NULL), ("streaming stopped, invalid state"));
qtdemux->segment_running = FALSE;
gst_pad_pause_task (qtdemux->sinkpad);
gst_qtdemux_push_event (qtdemux, gst_event_new_eos ());
goto done;
}
}
/*
@ -1121,9 +1181,11 @@ gst_qtdemux_post_buffering (GstQTDemux * demux, gint num, gint denom)
static GstFlowReturn
gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
{
GstQTDemux *demux = GST_QTDEMUX (GST_PAD_PARENT (sinkpad));
GstQTDemux *demux;
GstFlowReturn ret = GST_FLOW_OK;
demux = GST_QTDEMUX (gst_pad_get_parent (sinkpad));
gst_adapter_push (demux->adapter, inbuf);
GST_DEBUG_OBJECT (demux, "pushing in inbuf %p, neededbytes:%u, available:%u",
@ -1162,9 +1224,8 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
demux->neededbytes = size;
demux->state = QTDEMUX_STATE_HEADER;
}
}
break;
}
case QTDEMUX_STATE_HEADER:{
guint8 *data;
guint32 fourcc;
@ -1211,9 +1272,9 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
demux->neededbytes = 16;
demux->state = QTDEMUX_STATE_INITIAL;
}
}
break;
break;
}
case QTDEMUX_STATE_BUFFER_MDAT:{
GST_DEBUG_OBJECT (demux, "Got our buffer at offset %lld",
demux->mdatoffset);
@ -1229,9 +1290,9 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
demux->neededbytes = 16;
demux->state = QTDEMUX_STATE_INITIAL;
gst_qtdemux_post_buffering (demux, 1, 1);
}
break;
break;
}
case QTDEMUX_STATE_MOVIE:{
guint8 *data;
GstBuffer *outbuf;
@ -1256,15 +1317,13 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
stream->samples[stream->sample_index].offset,
stream->samples[stream->sample_index].size,
stream->samples[stream->sample_index].chunk);
if (stream->samples[stream->sample_index].offset == demux->offset)
break;
}
if (stream == NULL) {
GST_WARNING_OBJECT (demux, "No stream found.");
ret = GST_FLOW_ERROR;
break;
}
if (stream == NULL)
goto unknown_stream;
/* first buffer? */
/* FIXME : this should be handled in sink_event */
@ -1312,12 +1371,11 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
GST_LOG_OBJECT (demux, "offset is now %lld", demux->offset);
if ((demux->neededbytes = next_entry_size (demux)) == -1)
ret = GST_FLOW_ERROR;
}
goto eos;
break;
}
default:
g_warning ("This line should never be reached\n");
ret = GST_FLOW_ERROR;
goto invalid_state;
}
}
@ -1328,7 +1386,31 @@ gst_qtdemux_chain (GstPad * sinkpad, GstBuffer * inbuf)
demux->neededbytes);
}
done:
gst_object_unref (demux);
return ret;
/* ERRORS */
unknown_stream:
{
GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL), ("unknown stream found"));
ret = GST_FLOW_ERROR;
goto done;
}
eos:
{
GST_DEBUG_OBJECT (demux, "no next entry, EOS");
ret = GST_FLOW_UNEXPECTED;
goto done;
}
invalid_state:
{
GST_ELEMENT_ERROR (demux, STREAM, FAILED,
(NULL), ("qtdemuxer invalid state %d", demux->state));
ret = GST_FLOW_ERROR;
goto done;
}
}
static gboolean
@ -2508,8 +2590,17 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
qtdemux->timescale = QTDEMUX_GUINT32_GET (mvhd->data + 20);
qtdemux->duration = QTDEMUX_GUINT32_GET (mvhd->data + 24);
GST_INFO ("timescale: %d", qtdemux->timescale);
GST_INFO ("duration: %d", qtdemux->duration);
GST_INFO_OBJECT (qtdemux, "timescale: %d", qtdemux->timescale);
GST_INFO_OBJECT (qtdemux, "duration: %d", qtdemux->duration);
if (qtdemux->timescale != 0 && qtdemux->duration != 0) {
gint64 duration;
duration = gst_util_uint64_scale_int (qtdemux->duration,
GST_SECOND, qtdemux->timescale);
gst_segment_set_duration (&qtdemux->segment, GST_FORMAT_TIME, duration);
}
trak = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_trak);
qtdemux_parse_trak (qtdemux, trak);
@ -2587,7 +2678,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mdhd);
g_assert (mdhd);
/* new streams always need a discont */
stream = g_new0 (QtDemuxStream, 1);
stream->discont = TRUE;
stream->timescale = QTDEMUX_GUINT32_GET (mdhd->data + 20);
GST_LOG ("track timescale: %d", stream->timescale);
@ -2720,7 +2813,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
}
}
GST_INFO ("type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT,
GST_INFO_OBJECT (qtdemux,
"type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT,
GST_FOURCC_ARGS (QTDEMUX_FOURCC_GET (stsd->data + offset + 4)),
stream->caps);
} else if (stream->subtype == FOURCC_soun) {
@ -2860,11 +2954,12 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
"samplesize", G_TYPE_INT, samplesize, NULL);
}
}
GST_INFO ("type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT,
GST_INFO_OBJECT (qtdemux,
"type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT,
GST_FOURCC_ARGS (QTDEMUX_FOURCC_GET (stsd->data + 16 + 4)),
stream->caps);
} else {
GST_INFO ("unknown subtype %" GST_FOURCC_FORMAT,
GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (stream->subtype));
g_free (stream);
return;
@ -3053,9 +3148,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GST_SECOND, stream->rate);
}
#if 0
GST_INFO ("moo samples_per_chunk=%d rate=%d dur=%lld %lld",
(int) samples_per_chunk,
(int) stream->rate,
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