gst/mpegaudioparse/gstmpegaudioparse.*: Implement accurate seeking in mpegaudioparse. Fixes #308312.

Original commit message from CVS:
* gst/mpegaudioparse/gstmpegaudioparse.c: (gst_mp3parse_reset),
(gst_mp3parse_init), (gst_mp3parse_dispose),
(gst_mp3parse_sink_event), (mp3parse_seek_table_last_entry),
(gst_mp3parse_emit_frame), (gst_mp3parse_chain),
(mp3parse_handle_seek), (mp3parse_src_query):
* gst/mpegaudioparse/gstmpegaudioparse.h:
Implement accurate seeking in mpegaudioparse. Fixes #308312.
Also implement segment seeks.
This commit is contained in:
Sebastian Dröge 2007-07-13 16:27:56 +00:00
parent 68bbfd4fd3
commit 712a416ecd
3 changed files with 319 additions and 8 deletions

View File

@ -1,3 +1,14 @@
2007-07-13 Sebastian Dröge <slomo@circular-chaos.org>
* gst/mpegaudioparse/gstmpegaudioparse.c: (gst_mp3parse_reset),
(gst_mp3parse_init), (gst_mp3parse_dispose),
(gst_mp3parse_sink_event), (mp3parse_seek_table_last_entry),
(gst_mp3parse_emit_frame), (gst_mp3parse_chain),
(mp3parse_handle_seek), (mp3parse_src_query):
* gst/mpegaudioparse/gstmpegaudioparse.h:
Implement accurate seeking in mpegaudioparse. Fixes #308312.
Also implement segment seeks.
2007-07-13 Sebastian Dröge <slomo@circular-chaos.org>
* ext/mad/gstmad.c: (_do_init), (gst_mad_init), (index_seek),

View File

@ -243,8 +243,8 @@ gst_mp3parse_reset (GstMPEGAudioParse * mp3parse)
{
mp3parse->skip = 0;
mp3parse->resyncing = TRUE;
mp3parse->cur_offset = -1;
mp3parse->next_ts = GST_CLOCK_TIME_NONE;
mp3parse->cur_offset = -1;
mp3parse->tracked_offset = 0;
mp3parse->pending_ts = GST_CLOCK_TIME_NONE;
@ -254,6 +254,7 @@ gst_mp3parse_reset (GstMPEGAudioParse * mp3parse)
mp3parse->rate = mp3parse->channels = mp3parse->layer = -1;
mp3parse->version = 1;
mp3parse->max_bitreservoir = GST_CLOCK_TIME_NONE;
mp3parse->avg_bitrate = 0;
mp3parse->bitrate_sum = 0;
@ -263,6 +264,21 @@ gst_mp3parse_reset (GstMPEGAudioParse * mp3parse)
mp3parse->xing_flags = 0;
mp3parse->xing_bitrate = 0;
if (mp3parse->seek_table) {
g_slist_foreach (mp3parse->seek_table, (GFunc) g_free, NULL);
mp3parse->seek_table = NULL;
}
g_mutex_lock (mp3parse->pending_accurate_seeks_lock);
if (mp3parse->pending_accurate_seeks) {
g_slist_foreach (mp3parse->pending_accurate_seeks, (GFunc) g_free, NULL);
mp3parse->pending_accurate_seeks = NULL;
}
g_mutex_unlock (mp3parse->pending_accurate_seeks_lock);
mp3parse->exact_position = FALSE;
gst_segment_init (&mp3parse->segment, GST_FORMAT_TIME);
}
static void
@ -283,6 +299,7 @@ gst_mp3parse_init (GstMPEGAudioParse * mp3parse, GstMPEGAudioParseClass * klass)
gst_element_add_pad (GST_ELEMENT (mp3parse), mp3parse->srcpad);
mp3parse->adapter = gst_adapter_new ();
mp3parse->pending_accurate_seeks_lock = g_mutex_new ();
gst_mp3parse_reset (mp3parse);
}
@ -296,6 +313,8 @@ gst_mp3parse_dispose (GObject * object)
g_object_unref (mp3parse->adapter);
mp3parse->adapter = NULL;
}
g_mutex_free (mp3parse->pending_accurate_seeks_lock);
mp3parse->pending_accurate_seeks_lock = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);
}
@ -319,6 +338,64 @@ gst_mp3parse_sink_event (GstPad * pad, GstEvent * event)
gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
&format, &start, &stop, &pos);
g_mutex_lock (mp3parse->pending_accurate_seeks_lock);
if (format == GST_FORMAT_BYTES && mp3parse->pending_accurate_seeks) {
MPEGAudioPendingAccurateSeek *seek = NULL;
GSList *node;
for (node = mp3parse->pending_accurate_seeks; node; node = node->next) {
MPEGAudioPendingAccurateSeek *tmp = node->data;
if (tmp->upstream_start == pos) {
seek = tmp;
break;
}
}
if (seek) {
GstSegment *s = &seek->segment;
event =
gst_event_new_new_segment_full (FALSE, s->rate, s->applied_rate,
GST_FORMAT_TIME, s->start, s->stop, s->last_stop);
mp3parse->segment = seek->segment;
mp3parse->resyncing = FALSE;
mp3parse->cur_offset = pos;
mp3parse->next_ts = seek->timestamp_start;
mp3parse->pending_ts = GST_CLOCK_TIME_NONE;
mp3parse->tracked_offset = 0;
gst_event_parse_new_segment_full (event, &update, &rate,
&applied_rate, &format, &start, &stop, &pos);
GST_DEBUG_OBJECT (mp3parse,
"Pushing accurate newseg rate %g, applied rate %g, "
"format %d, start %lld, stop %lld, pos %lld\n", rate,
applied_rate, format, start, stop, pos);
g_free (seek);
mp3parse->pending_accurate_seeks =
g_slist_delete_link (mp3parse->pending_accurate_seeks, node);
g_mutex_unlock (mp3parse->pending_accurate_seeks_lock);
if (s->flags & GST_SEEK_FLAG_SEGMENT) {
gst_element_post_message (GST_ELEMENT_CAST (mp3parse),
gst_message_new_segment_start (GST_OBJECT_CAST (mp3parse),
s->format, s->last_stop));
}
res = gst_pad_push_event (mp3parse->srcpad, event);
return res;
} else {
GST_WARNING_OBJECT (mp3parse,
"Accurate seek not possible, didn't get an appropiate upstream segment");
}
}
g_mutex_unlock (mp3parse->pending_accurate_seeks_lock);
mp3parse->exact_position = FALSE;
if (format == GST_FORMAT_BYTES) {
GstClockTime seg_start, seg_stop, seg_pos;
@ -332,8 +409,9 @@ gst_mp3parse_sink_event (GstPad * pad, GstEvent * event)
GST_FORMAT_TIME, seg_start, seg_stop, seg_pos);
format = GST_FORMAT_TIME;
GST_DEBUG_OBJECT (mp3parse, "Converted incoming segment to TIME. "
"start = %" G_GINT64_FORMAT ", stop = %" G_GINT64_FORMAT
"pos = %" G_GINT64_FORMAT, seg_start, seg_stop, seg_pos);
"start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT
", pos = %" GST_TIME_FORMAT, GST_TIME_ARGS (seg_start),
GST_TIME_ARGS (seg_stop), GST_TIME_ARGS (seg_pos));
}
}
@ -355,6 +433,10 @@ gst_mp3parse_sink_event (GstPad * pad, GstEvent * event)
GST_DEBUG_OBJECT (mp3parse, "Pushing newseg rate %g, applied rate %g, "
"format %d, start %lld, stop %lld, pos %lld\n",
rate, applied_rate, format, start, stop, pos);
gst_segment_set_newsegment_full (&mp3parse->segment, update, rate,
applied_rate, format, start, stop, pos);
res = gst_pad_push_event (mp3parse->srcpad, event);
break;
}
@ -373,14 +455,26 @@ gst_mp3parse_sink_event (GstPad * pad, GstEvent * event)
return res;
}
static MPEGAudioSeekEntry *
mp3parse_seek_table_last_entry (GstMPEGAudioParse * mp3parse)
{
MPEGAudioSeekEntry *ret = NULL;
if (mp3parse->seek_table) {
ret = mp3parse->seek_table->data;
}
return ret;
}
/* Prepare a buffer of the indicated size, timestamp it and output */
static GstFlowReturn
gst_mp3parse_emit_frame (GstMPEGAudioParse * mp3parse, guint size)
{
GstBuffer *outbuf;
guint bitrate;
GST_DEBUG_OBJECT (mp3parse, "pushing buffer of %d bytes", size);
GstFlowReturn ret = GST_FLOW_OK;
GstClockTime push_start;
outbuf = gst_adapter_take_buffer (mp3parse->adapter, size);
@ -432,6 +526,26 @@ gst_mp3parse_emit_frame (GstMPEGAudioParse * mp3parse, guint size)
}
}
if (GST_BUFFER_TIMESTAMP (outbuf) == 0)
mp3parse->exact_position = TRUE;
if (mp3parse->exact_position && (!mp3parse->seek_table ||
(mp3parse_seek_table_last_entry (mp3parse))->byte <
GST_BUFFER_OFFSET (outbuf))) {
MPEGAudioSeekEntry *entry = g_new0 (MPEGAudioSeekEntry, 1);
entry->byte = mp3parse->cur_offset;
entry->byte_end = mp3parse->cur_offset + size;
entry->timestamp = GST_BUFFER_TIMESTAMP (outbuf);
entry->timestamp_end =
GST_BUFFER_TIMESTAMP (outbuf) + GST_BUFFER_DURATION (outbuf);
mp3parse->seek_table = g_slist_prepend (mp3parse->seek_table, entry);
GST_DEBUG_OBJECT (mp3parse, "Adding index entry %" GST_TIME_FORMAT " - %"
GST_TIME_FORMAT " @ offset 0x%08" G_GINT64_MODIFIER "x",
GST_TIME_ARGS (entry->timestamp), GST_TIME_ARGS (entry->timestamp_end),
entry->byte);
}
/* Update our byte offset tracking */
if (mp3parse->cur_offset != -1) {
mp3parse->cur_offset += size;
@ -459,7 +573,62 @@ gst_mp3parse_emit_frame (GstMPEGAudioParse * mp3parse, guint size)
mp3parse->srcpad, taglist);
}
return gst_pad_push (mp3parse->srcpad, outbuf);
/* We start pushing 9 frames earlier (29 frames for MPEG2) than
* segment start to be able to decode the first frame we want.
* 9 (29) frames are the theoretical maximum of frames that contain
* data for the current frame (bit reservoir).
*/
if (mp3parse->segment.start == 0) {
push_start = 0;
} else if (GST_CLOCK_TIME_IS_VALID (mp3parse->max_bitreservoir)) {
if (mp3parse->segment.start - mp3parse->max_bitreservoir >= 0)
push_start = mp3parse->segment.start - mp3parse->max_bitreservoir;
else
push_start = 0;
} else {
push_start = mp3parse->segment.start;
}
if (G_UNLIKELY ((GST_CLOCK_TIME_IS_VALID (mp3parse->segment.start) &&
GST_BUFFER_TIMESTAMP (outbuf) + GST_BUFFER_DURATION (outbuf)
< push_start)
|| (GST_CLOCK_TIME_IS_VALID (mp3parse->segment.stop)
&& GST_BUFFER_TIMESTAMP (outbuf) >= mp3parse->segment.stop))) {
GST_DEBUG_OBJECT (mp3parse,
"Buffer outside of configured segment, dropping, timestamp %"
GST_TIME_FORMAT ", offset 0x%08" G_GINT64_MODIFIER "x",
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
GST_BUFFER_OFFSET (outbuf));
gst_buffer_unref (outbuf);
ret = GST_FLOW_OK;
} else {
GST_DEBUG_OBJECT (mp3parse,
"pushing buffer of %d bytes, timestamp %" GST_TIME_FORMAT
", offset 0x%08" G_GINT64_MODIFIER "x", size,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
GST_BUFFER_OFFSET (outbuf));
mp3parse->segment.last_stop = GST_BUFFER_TIMESTAMP (outbuf);
ret = gst_pad_push (mp3parse->srcpad, outbuf);
}
if (ret == GST_FLOW_UNEXPECTED
&& mp3parse->segment.flags & GST_SEEK_FLAG_SEGMENT) {
GstClockTime stop;
GST_LOG_OBJECT (mp3parse, "Sending segment done");
if ((stop = mp3parse->segment.stop) == -1)
stop = mp3parse->segment.duration;
gst_element_post_message (GST_ELEMENT_CAST (mp3parse),
gst_message_new_segment_done (GST_OBJECT_CAST (mp3parse),
mp3parse->segment.format, stop));
} else if (GST_CLOCK_TIME_IS_VALID (mp3parse->segment.stop)
&& mp3parse->next_ts >= mp3parse->segment.stop) {
GST_DEBUG_OBJECT (mp3parse, "Sending EOS");
gst_pad_push_event (mp3parse->srcpad, gst_event_new_eos ());
}
return ret;
}
#define XING_FRAMES_FLAG 0x0001
@ -780,6 +949,9 @@ gst_mp3parse_chain (GstPad * pad, GstBuffer * buf)
mp3parse->spf = 1152;
}
mp3parse->max_bitreservoir = gst_util_uint64_scale (GST_SECOND,
((version == 1) ? 10 : 30) * mp3parse->spf, mp3parse->rate);
/* Check the first frame for a Xing header to get our total length */
if (mp3parse->frame_count == 0) {
/* For the first frame in the file, look for a Xing frame after
@ -1077,11 +1249,12 @@ mp3parse_handle_seek (GstMPEGAudioParse * mp3parse, GstEvent * event)
gint64 cur, stop;
gint64 byte_cur, byte_stop;
/* FIXME: Use GstSegment for tracking our position */
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
&stop_type, &stop);
GST_DEBUG_OBJECT (mp3parse, "Performing seek to %" GST_TIME_FORMAT,
GST_TIME_ARGS (cur));
/* For any format other than TIME, see if upstream handles
* it directly or fail. For TIME, try upstream, but do it ourselves if
* it fails upstream */
@ -1096,6 +1269,103 @@ mp3parse_handle_seek (GstMPEGAudioParse * mp3parse, GstEvent * event)
/* Handle TIME based seeks by converting to a BYTE position */
/* For accurate seeking get the frame 9 (MPEG1) or 29 (MPEG2) frames
* before the one we want to seek to and push them all to the decoder.
*
* This is necessary because of the bit reservoir. See
* http://www.mars.org/mailman/public/mad-dev/2002-May/000634.html
*
*/
if (flags & GST_SEEK_FLAG_ACCURATE) {
MPEGAudioPendingAccurateSeek *seek =
g_new0 (MPEGAudioPendingAccurateSeek, 1);
gint frame_offset = (mp3parse->version == 1) ? 9 : 29;
GstClockTime start;
seek->segment = mp3parse->segment;
gst_segment_set_seek (&seek->segment, rate, GST_FORMAT_TIME,
flags, cur_type, cur, stop_type, stop, NULL);
if (!mp3parse->seek_table) {
byte_cur = 0;
byte_stop = -1;
start = 0;
} else {
MPEGAudioSeekEntry *entry = NULL, *start_entry = NULL, *stop_entry = NULL;
GSList *start_node, *stop_node;
for (start_node = mp3parse->seek_table; start_node;
start_node = start_node->next) {
entry = start_node->data;
if (cur >= entry->timestamp && cur < entry->timestamp_end) {
start_entry = entry;
break;
}
if (cur >= entry->timestamp_end)
break;
}
if (!start_entry) {
start_entry = mp3parse->seek_table->data;
start = start_entry->timestamp;
byte_cur = start_entry->byte;
} else {
int i = frame_offset;
while (start_node->next && i > 1) {
start_node = start_node->next;
i--;
}
start_entry = start_node->data;
start = start_entry->timestamp;
byte_cur = start_entry->byte;
}
for (stop_node = mp3parse->seek_table; stop_node;
stop_node = stop_node->next) {
entry = stop_node->data;
if (stop >= entry->timestamp && stop < entry->timestamp_end) {
stop_entry = entry;
break;
}
if (stop >= entry->timestamp_end)
break;
}
if (!stop_entry) {
byte_stop = -1;
} else {
byte_stop = stop_entry->byte_end;
}
}
g_mutex_lock (mp3parse->pending_accurate_seeks_lock);
event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type,
byte_cur, stop_type, byte_stop);
if (gst_pad_push_event (mp3parse->sinkpad, event)) {
mp3parse->exact_position = TRUE;
seek->upstream_start = byte_cur;
seek->timestamp_start = start;
mp3parse->pending_accurate_seeks =
g_slist_prepend (mp3parse->pending_accurate_seeks, seek);
g_mutex_unlock (mp3parse->pending_accurate_seeks_lock);
return TRUE;
} else {
g_mutex_unlock (mp3parse->pending_accurate_seeks_lock);
mp3parse->exact_position = TRUE;
g_free (seek);
return TRUE;
}
}
mp3parse->exact_position = FALSE;
/* Convert the TIME to the appropriate BYTE position at which to resume
* decoding. */
if (!mp3parse_time_to_bytepos (mp3parse, (GstClockTime) cur, &byte_cur))
@ -1110,6 +1380,11 @@ mp3parse_handle_seek (GstMPEGAudioParse * mp3parse, GstEvent * event)
event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type,
byte_cur, stop_type, byte_stop);
if (flags & GST_SEEK_FLAG_SEGMENT) {
gst_element_post_message (GST_ELEMENT_CAST (mp3parse),
gst_message_new_segment_start (GST_OBJECT_CAST (mp3parse),
GST_FORMAT_TIME, cur));
}
return gst_pad_push_event (mp3parse->sinkpad, event);
no_pos:
GST_DEBUG_OBJECT (mp3parse,

View File

@ -40,13 +40,31 @@ G_BEGIN_DECLS
typedef struct _GstMPEGAudioParse GstMPEGAudioParse;
typedef struct _GstMPEGAudioParseClass GstMPEGAudioParseClass;
typedef struct _MPEGAudioSeekEntry MPEGAudioSeekEntry;
typedef struct _MPEGAudioPendingAccurateSeek MPEGAudioPendingAccurateSeek;
struct _MPEGAudioSeekEntry {
gint64 byte;
gint64 byte_end;
GstClockTime timestamp;
GstClockTime timestamp_end;
};
struct _MPEGAudioPendingAccurateSeek {
GstSegment segment;
gint64 upstream_start;
GstClockTime timestamp_start;
};
struct _GstMPEGAudioParse {
GstElement element;
GstPad *sinkpad, *srcpad;
GstSegment segment;
GstClockTime next_ts;
/* Offset as supplied by incoming buffers */
gint64 cur_offset;
@ -62,6 +80,7 @@ struct _GstMPEGAudioParse {
guint skip; /* number of frames to skip */
guint bit_rate; /* in kbps */
gint channels, rate, layer, version;
GstClockTime max_bitreservoir;
gint spf; /* Samples per frame */
gboolean resyncing; /* True when attempting to resync (stricter checks are
@ -85,6 +104,12 @@ struct _GstMPEGAudioParse {
guint16 xing_seek_table_inverse[256];
guint32 xing_vbr_scale;
guint xing_bitrate;
/* Accurate seeking */
GSList *seek_table;
GMutex *pending_accurate_seeks_lock;
GSList *pending_accurate_seeks;
gboolean exact_position;
};
struct _GstMPEGAudioParseClass {