From 25896b3a93da2cc7a8b2c5418d874bdb0065c5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 31 Aug 2008 14:39:57 +0000 Subject: [PATCH] ext/speex/gstspeexdec.c: Correctly take the granulepos from upstream if possible and correctly handle the granulepos ... Original commit message from CVS: * ext/speex/gstspeexdec.c: (speex_dec_chain_parse_data): Correctly take the granulepos from upstream if possible and correctly handle the granulepos in various calculations: the granulepos is the sample number of the _last_ sample in a frame, not the first. * ext/speex/gstspeexenc.c: (gst_speex_enc_sinkevent), (gst_speex_enc_encode), (gst_speex_enc_chain), (gst_speex_enc_change_state): * ext/speex/gstspeexenc.h: Handle non-zero start timestamps in the encoder and detect/handle stream discontinuities. Fixes bug #547075. --- ChangeLog | 15 +++ ext/speex/gstspeexdec.c | 18 +++- ext/speex/gstspeexenc.c | 228 ++++++++++++++++++++++++++++------------ ext/speex/gstspeexenc.h | 7 +- 4 files changed, 192 insertions(+), 76 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0e71c1652c..1c97466541 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2008-08-31 Sebastian Dröge + + * ext/speex/gstspeexdec.c: (speex_dec_chain_parse_data): + Correctly take the granulepos from upstream if possible and + correctly handle the granulepos in various calculations: the + granulepos is the sample number of the _last_ sample in a frame, not + the first. + + * ext/speex/gstspeexenc.c: (gst_speex_enc_sinkevent), + (gst_speex_enc_encode), (gst_speex_enc_chain), + (gst_speex_enc_change_state): + * ext/speex/gstspeexenc.h: + Handle non-zero start timestamps in the encoder and detect/handle + stream discontinuities. Fixes bug #547075. + 2008-08-31 Sebastian Dröge Patch by: Craig Keogh diff --git a/ext/speex/gstspeexdec.c b/ext/speex/gstspeexdec.c index a42de61858..a38814519a 100644 --- a/ext/speex/gstspeexdec.c +++ b/ext/speex/gstspeexdec.c @@ -664,6 +664,14 @@ speex_dec_chain_parse_data (GstSpeexDec * dec, GstBuffer * buf, GST_DEBUG_OBJECT (dec, "received buffer of size %u, fpp %d", size, fpp); + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf) + && GST_BUFFER_OFFSET_END_IS_VALID (buf)) { + dec->granulepos = GST_BUFFER_OFFSET_END (buf); + GST_DEBUG_OBJECT (dec, + "Taking granulepos from upstream: %" G_GUINT64_FORMAT, + dec->granulepos); + } + /* copy timestamp */ } else { /* concealment data, pass NULL as the bits parameters */ @@ -722,21 +730,21 @@ speex_dec_chain_parse_data (GstSpeexDec * dec, GstBuffer * buf, if (dec->granulepos == -1) { if (dec->segment.format != GST_FORMAT_TIME) { GST_WARNING_OBJECT (dec, "segment not initialized or not TIME format"); - dec->granulepos = 0; + dec->granulepos = dec->frame_size; } else { dec->granulepos = gst_util_uint64_scale_int (dec->segment.last_stop, - dec->header->rate, GST_SECOND); + dec->header->rate, GST_SECOND) + dec->frame_size; } GST_DEBUG_OBJECT (dec, "granulepos=%" G_GINT64_FORMAT, dec->granulepos); } if (timestamp == -1) { - timestamp = gst_util_uint64_scale_int (dec->granulepos, + timestamp = gst_util_uint64_scale_int (dec->granulepos - dec->frame_size, GST_SECOND, dec->header->rate); } - GST_BUFFER_OFFSET (outbuf) = dec->granulepos; - GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos + dec->frame_size; + GST_BUFFER_OFFSET (outbuf) = dec->granulepos - dec->frame_size; + GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos; GST_BUFFER_TIMESTAMP (outbuf) = timestamp; GST_BUFFER_DURATION (outbuf) = dec->frame_duration; diff --git a/ext/speex/gstspeexenc.c b/ext/speex/gstspeexenc.c index e0beb53d30..b2653b3a05 100644 --- a/ext/speex/gstspeexenc.c +++ b/ext/speex/gstspeexenc.c @@ -30,6 +30,7 @@ #include #include +#include #include "gstspeexenc.h" GST_DEBUG_CATEGORY_STATIC (speexenc_debug); @@ -135,6 +136,8 @@ static void gst_speex_enc_set_property (GObject * object, guint prop_id, static GstStateChangeReturn gst_speex_enc_change_state (GstElement * element, GstStateChange transition); +static GstFlowReturn gst_speex_enc_encode (GstSpeexEnc * enc, gboolean flush); + static void gst_speex_enc_setup_interfaces (GType speexenc_type) { @@ -822,7 +825,7 @@ gst_speex_enc_sinkevent (GstPad * pad, GstEvent * event) switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: - enc->eos = TRUE; + gst_speex_enc_encode (enc, TRUE); res = gst_pad_event_default (pad, event); break; case GST_EVENT_TAG: @@ -847,6 +850,85 @@ gst_speex_enc_sinkevent (GstPad * pad, GstEvent * event) return res; } +static GstFlowReturn +gst_speex_enc_encode (GstSpeexEnc * enc, gboolean flush) +{ + gint frame_size = enc->frame_size; + gint bytes = frame_size * 2 * enc->channels; + GstFlowReturn ret = GST_FLOW_OK; + + if (flush && gst_adapter_available (enc->adapter) % bytes != 0) { + guint diff = gst_adapter_available (enc->adapter) % bytes; + GstBuffer *buf = gst_buffer_new_and_alloc (diff); + + memset (GST_BUFFER_DATA (buf), 0, diff); + gst_adapter_push (enc->adapter, buf); + } + + while (gst_adapter_available (enc->adapter) >= bytes) { + gint16 *data; + gint i; + gint outsize, written; + GstBuffer *outbuf; + + data = (gint16 *) gst_adapter_peek (enc->adapter, bytes); + + for (i = 0; i < frame_size * enc->channels; i++) { + enc->input[i] = (gfloat) data[i]; + } + gst_adapter_flush (enc->adapter, bytes); + + enc->samples_in += frame_size; + + GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size, bytes); + + if (enc->channels == 2) { + speex_encode_stereo (enc->input, frame_size, &enc->bits); + } + speex_encode (enc->state, enc->input, &enc->bits); + + enc->frameno++; + enc->frameno_out++; + + if ((enc->frameno % enc->nframes) != 0) + continue; + + speex_bits_insert_terminator (&enc->bits); + outsize = speex_bits_nbytes (&enc->bits); + + ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad, + GST_BUFFER_OFFSET_NONE, outsize, GST_PAD_CAPS (enc->srcpad), &outbuf); + + if ((GST_FLOW_OK != ret)) + goto done; + + written = speex_bits_write (&enc->bits, + (gchar *) GST_BUFFER_DATA (outbuf), outsize); + g_assert (written == outsize); + speex_bits_reset (&enc->bits); + + GST_BUFFER_TIMESTAMP (outbuf) = enc->start_ts + + gst_util_uint64_scale_int (enc->frameno_out * frame_size - + enc->lookahead, GST_SECOND, enc->rate); + GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (frame_size, + GST_SECOND, enc->rate); + /* set gp time and granulepos; see gst-plugins-base/ext/ogg/README */ + GST_BUFFER_OFFSET_END (outbuf) = enc->granulepos_offset + + ((enc->frameno_out + 1) * frame_size - enc->lookahead); + GST_BUFFER_OFFSET (outbuf) = + gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND, + enc->rate); + + ret = gst_speex_enc_push_buffer (enc, outbuf); + + if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret)) + goto done; + } + +done: + + return ret; +} static GstFlowReturn gst_speex_enc_chain (GstPad * pad, GstBuffer * buf) @@ -913,78 +995,80 @@ gst_speex_enc_chain (GstPad * pad, GstBuffer * buf) enc->header_sent = TRUE; } - { - gint frame_size = enc->frame_size; - gint bytes = frame_size * 2 * enc->channels; - - GST_DEBUG_OBJECT (enc, "received buffer of %u bytes", - GST_BUFFER_SIZE (buf)); - - /* push buffer to adapter */ - gst_adapter_push (enc->adapter, buf); - buf = NULL; - - while (gst_adapter_available (enc->adapter) >= bytes) { - gint16 *data; - gint i; - gint outsize, written; - GstBuffer *outbuf; - - data = (gint16 *) gst_adapter_peek (enc->adapter, bytes); - - for (i = 0; i < frame_size * enc->channels; i++) { - enc->input[i] = (gfloat) data[i]; - } - gst_adapter_flush (enc->adapter, bytes); - - enc->samples_in += frame_size; - - GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size, - bytes); - - if (enc->channels == 2) { - speex_encode_stereo (enc->input, frame_size, &enc->bits); - } - speex_encode (enc->state, enc->input, &enc->bits); - - enc->frameno++; - - if ((enc->frameno % enc->nframes) != 0) - continue; - - speex_bits_insert_terminator (&enc->bits); - outsize = speex_bits_nbytes (&enc->bits); - - ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad, - GST_BUFFER_OFFSET_NONE, outsize, GST_PAD_CAPS (enc->srcpad), &outbuf); - - if ((GST_FLOW_OK != ret)) - goto done; - - written = speex_bits_write (&enc->bits, - (gchar *) GST_BUFFER_DATA (outbuf), outsize); - g_assert (written == outsize); - speex_bits_reset (&enc->bits); - - GST_BUFFER_TIMESTAMP (outbuf) = - gst_util_uint64_scale_int (enc->frameno * frame_size - - enc->lookahead, GST_SECOND, enc->rate); - GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (frame_size, - GST_SECOND, enc->rate); - /* set gp time and granulepos; see gst-plugins-base/ext/ogg/README */ - GST_BUFFER_OFFSET_END (outbuf) = - ((enc->frameno + 1) * frame_size - enc->lookahead); - GST_BUFFER_OFFSET (outbuf) = - gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND, - enc->rate); - - ret = gst_speex_enc_push_buffer (enc, outbuf); - - if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret)) - goto done; + /* Save the timestamp of the first buffer. This will be later + * used as offset for all following buffers */ + if (enc->start_ts == GST_CLOCK_TIME_NONE) { + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + enc->start_ts = GST_BUFFER_TIMESTAMP (buf); + enc->granulepos_offset = gst_util_uint64_scale + (GST_BUFFER_TIMESTAMP (buf), enc->rate, GST_SECOND); + } else { + enc->start_ts = 0; + enc->granulepos_offset = 0; } } + /* Check if we have a continous stream, if not drop some samples or the buffer or + * insert some silence samples */ + if (enc->next_ts != GST_CLOCK_TIME_NONE && + GST_BUFFER_TIMESTAMP (buf) < enc->next_ts) { + guint64 diff = enc->next_ts - GST_BUFFER_TIMESTAMP (buf); + guint64 diff_bytes; + + GST_WARNING_OBJECT (enc, "Buffer is older than previous " + "timestamp + duration (%" GST_TIME_FORMAT "< %" GST_TIME_FORMAT + "), cannot handle. Clipping buffer.", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (enc->next_ts)); + + diff_bytes = GST_CLOCK_TIME_TO_FRAMES (diff, enc->rate) * enc->channels * 2; + if (diff_bytes >= GST_BUFFER_SIZE (buf)) { + gst_buffer_unref (buf); + return GST_FLOW_OK; + } + buf = gst_buffer_make_metadata_writable (buf); + GST_BUFFER_DATA (buf) += diff_bytes; + GST_BUFFER_SIZE (buf) -= diff_bytes; + + GST_BUFFER_TIMESTAMP (buf) += diff; + if (GST_BUFFER_DURATION_IS_VALID (buf)) + GST_BUFFER_DURATION (buf) -= diff; + } + + if (enc->next_ts != GST_CLOCK_TIME_NONE + && GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + guint64 max_diff = + gst_util_uint64_scale (enc->frame_size, GST_SECOND, enc->rate); + + if (GST_BUFFER_TIMESTAMP (buf) != enc->next_ts && + GST_BUFFER_TIMESTAMP (buf) - enc->next_ts > max_diff) { + GST_WARNING_OBJECT (enc, + "Discontinuity detected: %" G_GUINT64_FORMAT " > %" G_GUINT64_FORMAT, + GST_BUFFER_TIMESTAMP (buf) - enc->next_ts, max_diff); + + gst_speex_enc_encode (enc, TRUE); + + enc->frameno_out = 0; + enc->start_ts = GST_BUFFER_TIMESTAMP (buf); + enc->granulepos_offset = gst_util_uint64_scale + (GST_BUFFER_TIMESTAMP (buf), enc->rate, GST_SECOND); + } + } + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf) + && GST_BUFFER_DURATION_IS_VALID (buf)) + enc->next_ts = GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf); + else + enc->next_ts = GST_CLOCK_TIME_NONE; + + GST_DEBUG_OBJECT (enc, "received buffer of %u bytes", GST_BUFFER_SIZE (buf)); + + /* push buffer to adapter */ + gst_adapter_push (enc->adapter, buf); + buf = NULL; + + ret = gst_speex_enc_encode (enc, FALSE); + done: if (buf) @@ -1104,7 +1188,11 @@ gst_speex_enc_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_READY_TO_PAUSED: speex_bits_init (&enc->bits); enc->frameno = 0; + enc->frameno_out = 0; enc->samples_in = 0; + enc->start_ts = GST_CLOCK_TIME_NONE; + enc->next_ts = GST_CLOCK_TIME_NONE; + enc->granulepos_offset = 0; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: /* fall through */ diff --git a/ext/speex/gstspeexenc.h b/ext/speex/gstspeexenc.h index a5de51105c..a5bfb36607 100644 --- a/ext/speex/gstspeexenc.h +++ b/ext/speex/gstspeexenc.h @@ -92,7 +92,6 @@ struct _GstSpeexEnc { gboolean setup; gboolean header_sent; - gboolean eos; guint64 samples_in; guint64 bytes_out; @@ -103,11 +102,17 @@ struct _GstSpeexEnc { gint frame_size; guint64 frameno; + guint64 frameno_out; guint8 *comments; gint comment_len; gfloat input[MAX_FRAME_SIZE]; + + /* Timestamp and granulepos tracking */ + GstClockTime start_ts; + GstClockTime next_ts; + guint64 granulepos_offset; }; struct _GstSpeexEncClass {