diff options
-rw-r--r-- | ChangeLog | 15 | ||||
-rw-r--r-- | ext/celt/gstceltdec.c | 20 | ||||
-rw-r--r-- | ext/celt/gstceltenc.c | 194 | ||||
-rw-r--r-- | ext/celt/gstceltenc.h | 5 |
4 files changed, 177 insertions, 57 deletions
@@ -1,5 +1,20 @@ 2008-08-31 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * ext/celt/gstceltdec.c: (celt_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/celt/gstceltenc.c: (gst_celt_enc_sinkevent), + (gst_celt_enc_encode), (gst_celt_enc_chain), + (gst_celt_enc_change_state): + * ext/celt/gstceltenc.h: + Handle non-zero start timestamps in the encoder and detect/handle + stream discontinuities. Fixes bug #547075. + +2008-08-31 Sebastian Dröge <sebastian.droege@collabora.co.uk> + Patch by: Rov Juvano <rovjuvano at users dot sourceforge dot net> * configure.ac: diff --git a/ext/celt/gstceltdec.c b/ext/celt/gstceltdec.c index c14f787d..fe1914b8 100644 --- a/ext/celt/gstceltdec.c +++ b/ext/celt/gstceltdec.c @@ -618,6 +618,13 @@ celt_dec_chain_parse_data (GstCeltDec * dec, GstBuffer * buf, size = GST_BUFFER_SIZE (buf); GST_DEBUG_OBJECT (dec, "received buffer of size %u", size); + 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 { @@ -649,18 +656,19 @@ celt_dec_chain_parse_data (GstCeltDec * 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.sample_rate, GST_SECOND); + dec->header.sample_rate, GST_SECOND) + dec->frame_size; } GST_DEBUG_OBJECT (dec, "granulepos=%" G_GINT64_FORMAT, dec->granulepos); } - GST_BUFFER_OFFSET (outbuf) = dec->granulepos; - GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos + dec->frame_size; - GST_BUFFER_TIMESTAMP (outbuf) = gst_util_uint64_scale_int (dec->granulepos, - GST_SECOND, dec->header.sample_rate); + GST_BUFFER_OFFSET (outbuf) = dec->granulepos - dec->frame_size; + GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos; + GST_BUFFER_TIMESTAMP (outbuf) = + gst_util_uint64_scale_int (dec->granulepos - dec->frame_size, GST_SECOND, + dec->header.sample_rate); GST_BUFFER_DURATION (outbuf) = dec->frame_duration; dec->granulepos += dec->frame_size; diff --git a/ext/celt/gstceltenc.c b/ext/celt/gstceltenc.c index 0154a26c..58b3b299 100644 --- a/ext/celt/gstceltenc.c +++ b/ext/celt/gstceltenc.c @@ -48,6 +48,7 @@ #include <gst/gsttagsetter.h> #include <gst/tag/tag.h> +#include <gst/audio/audio.h> #include "gstceltenc.h" GST_DEBUG_CATEGORY_STATIC (celtenc_debug); @@ -99,6 +100,8 @@ static void gst_celt_enc_set_property (GObject * object, guint prop_id, static GstStateChangeReturn gst_celt_enc_change_state (GstElement * element, GstStateChange transition); +static GstFlowReturn gst_celt_enc_encode (GstCeltEnc * enc, gboolean flush); + static void gst_celt_enc_setup_interfaces (GType celtenc_type) { @@ -676,7 +679,7 @@ gst_celt_enc_sinkevent (GstPad * pad, GstEvent * event) switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: - enc->eos = TRUE; + gst_celt_enc_encode (enc, TRUE); res = gst_pad_event_default (pad, event); break; case GST_EVENT_TAG: @@ -701,6 +704,79 @@ gst_celt_enc_sinkevent (GstPad * pad, GstEvent * event) return res; } +static GstFlowReturn +gst_celt_enc_encode (GstCeltEnc * enc, gboolean flush) +{ + + GstFlowReturn ret = GST_FLOW_OK; + gint frame_size = enc->frame_size; + gint bytes = frame_size * 2 * enc->channels; + gint bytes_per_packet = + (enc->bitrate * 1000 * enc->frame_size / enc->rate + 4) / 8; + + 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 outsize; + GstBuffer *outbuf; + + ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad, + GST_BUFFER_OFFSET_NONE, bytes_per_packet, GST_PAD_CAPS (enc->srcpad), + &outbuf); + + if (GST_FLOW_OK != ret) + goto done; + + data = (gint16 *) gst_adapter_take (enc->adapter, bytes); + enc->samples_in += frame_size; + + GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size, bytes); + + outsize = + celt_encode (enc->state, data, GST_BUFFER_DATA (outbuf), + bytes_per_packet); + + g_free (data); + + if (outsize < 0) { + GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize); + ret = GST_FLOW_ERROR; + goto done; + } + + GST_BUFFER_TIMESTAMP (outbuf) = enc->start_ts + + gst_util_uint64_scale_int (enc->frameno_out * frame_size, 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 + 1) * frame_size); + GST_BUFFER_OFFSET (outbuf) = + gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND, + enc->rate); + + enc->frameno++; + enc->frameno_out++; + + ret = gst_celt_enc_push_buffer (enc, outbuf); + + if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret)) + goto done; + } + +done: + + return ret; +} static GstFlowReturn gst_celt_enc_chain (GstPad * pad, GstBuffer * buf) @@ -763,68 +839,80 @@ gst_celt_enc_chain (GstPad * pad, GstBuffer * buf) enc->header_sent = TRUE; } - { - gint frame_size = enc->frame_size; - gint bytes = frame_size * 2 * enc->channels; - gint bytes_per_packet = - (enc->bitrate * 1000 * enc->frame_size / enc->rate + 4) / 8; - - GST_DEBUG_OBJECT (enc, "received buffer of %u bytes", - GST_BUFFER_SIZE (buf)); + GST_DEBUG_OBJECT (enc, "received buffer of %u bytes", GST_BUFFER_SIZE (buf)); + + /* 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; + } + } - /* push buffer to adapter */ - gst_adapter_push (enc->adapter, buf); - buf = NULL; - while (gst_adapter_available (enc->adapter) >= bytes) { - gint16 *data; - gint outsize; - GstBuffer *outbuf; + /* 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; - ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad, - GST_BUFFER_OFFSET_NONE, bytes_per_packet, GST_PAD_CAPS (enc->srcpad), - &outbuf); + 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)); - if (GST_FLOW_OK != ret) - goto done; + 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; - data = (gint16 *) gst_adapter_take (enc->adapter, bytes); - enc->samples_in += frame_size; + GST_BUFFER_TIMESTAMP (buf) += diff; + if (GST_BUFFER_DURATION_IS_VALID (buf)) + GST_BUFFER_DURATION (buf) -= diff; + } - GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size, - bytes); + 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); - outsize = - celt_encode (enc->state, data, GST_BUFFER_DATA (outbuf), - bytes_per_packet); + 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); - g_free (data); + gst_celt_enc_encode (enc, TRUE); - if (outsize < 0) { - GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize); - ret = GST_FLOW_ERROR; - goto done; - } - - enc->frameno++; + 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); + } + } - GST_BUFFER_TIMESTAMP (outbuf) = - gst_util_uint64_scale_int (enc->frameno * frame_size, 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); - GST_BUFFER_OFFSET (outbuf) = - gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND, - enc->rate); + 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; - ret = gst_celt_enc_push_buffer (enc, outbuf); + /* push buffer to adapter */ + gst_adapter_push (enc->adapter, buf); + buf = NULL; - if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret)) - goto done; - } - } + ret = gst_celt_enc_encode (enc, FALSE); done: @@ -900,6 +988,10 @@ gst_celt_enc_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_READY_TO_PAUSED: enc->frameno = 0; enc->samples_in = 0; + enc->frameno_out = 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/celt/gstceltenc.h b/ext/celt/gstceltenc.h index cbb62440..6331f31c 100644 --- a/ext/celt/gstceltenc.h +++ b/ext/celt/gstceltenc.h @@ -76,6 +76,11 @@ struct _GstCeltEnc { GstTagList *tags; guint64 frameno; + guint64 frameno_out; + + GstClockTime start_ts; + GstClockTime next_ts; + guint64 granulepos_offset; }; struct _GstCeltEncClass { |