sbc: sbcdec: make decoder more performant

Use an adapter to accumulate input buffers.
Decode all input in one output buffer when possible to reduce the amount of push
operations.
This commit is contained in:
Wim Taymans 2013-01-08 10:19:39 +01:00 committed by Tim-Philipp Müller
parent dcb57780ef
commit 91f4b15490
2 changed files with 145 additions and 89 deletions

View file

@ -31,9 +31,13 @@
#include "gstsbcutil.h"
#include "gstsbcdec.h"
#define BUF_SIZE 8192
GST_DEBUG_CATEGORY_STATIC (sbc_dec_debug);
#define GST_CAT_DEFAULT sbc_dec_debug
static void gst_sbc_dec_finalize (GObject * obj);
GST_BOILERPLATE (GstSbcDec, gst_sbc_dec, GstElement, GST_TYPE_ELEMENT);
static const GstElementDetails sbc_dec_details =
@ -55,97 +59,16 @@ GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
"signed = (boolean) true, " "width = (int) 16, " "depth = (int) 16"));
static GstFlowReturn
sbc_dec_chain (GstPad * pad, GstBuffer * buffer)
gst_sbc_dec_flush (GstSbcDec * dec, GstBuffer * outbuf,
gint outoffset, gint channels, gint rate)
{
GstSbcDec *dec = GST_SBC_DEC (gst_pad_get_parent (pad));
GstFlowReturn res = GST_FLOW_OK;
guint size, codesize, offset = 0;
guint8 *data;
GstClockTime timestamp;
gboolean discont;
codesize = sbc_get_codesize (&dec->sbc);
discont = GST_BUFFER_IS_DISCONT (buffer);
if (discont) {
/* reset previous buffer */
gst_buffer_unref (dec->buffer);
dec->buffer = NULL;
/* we need a new timestamp to lock onto */
dec->next_sample = -1;
}
if (dec->buffer) {
GstBuffer *temp = buffer;
buffer = gst_buffer_span (dec->buffer, 0, buffer,
GST_BUFFER_SIZE (dec->buffer) + GST_BUFFER_SIZE (buffer));
gst_buffer_unref (temp);
gst_buffer_unref (dec->buffer);
dec->buffer = NULL;
}
data = GST_BUFFER_DATA (buffer);
size = GST_BUFFER_SIZE (buffer);
timestamp = GST_BUFFER_TIMESTAMP (buffer);
while (offset < size) {
GstBuffer *output;
GstPadTemplate *template;
GstCaps *caps;
int consumed;
GstClockTime duration;
gint rate, channels;
res = gst_pad_alloc_buffer_and_set_caps (dec->srcpad,
GST_BUFFER_OFFSET_NONE, codesize, NULL, &output);
if (res != GST_FLOW_OK)
goto done;
consumed = sbc_decode (&dec->sbc, data + offset, size - offset,
GST_BUFFER_DATA (output), codesize, NULL);
GST_INFO_OBJECT (dec, "consumed %d bytes", consumed);
if (consumed <= 0) {
offset += sbc_get_frame_length (&dec->sbc);
continue;
}
rate = gst_sbc_parse_rate_from_sbc (dec->sbc.frequency);
channels = gst_sbc_get_channel_number (dec->sbc.mode);
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
/* lock onto timestamp when we have one */
dec->next_sample = gst_util_uint64_scale_int (timestamp,
rate, GST_SECOND);
}
if (dec->next_sample != (guint64) - 1) {
/* reconstruct timestamp from our sample counter otherwise */
timestamp = gst_util_uint64_scale_int (dec->next_sample,
GST_SECOND, rate);
}
GST_BUFFER_TIMESTAMP (output) = timestamp;
/* calculate the next sample */
if (dec->next_sample != (guint64) - 1) {
/* we ave a valid sample, counter, increment it. */
dec->next_sample += codesize / (2 * channels);
duration = gst_util_uint64_scale_int (dec->next_sample,
GST_SECOND, rate) - timestamp;
} else {
/* otherwise calculate duration based on output size */
duration = gst_util_uint64_scale_int (codesize / (2 * channels),
GST_SECOND, rate) - timestamp;
}
GST_BUFFER_DURATION (output) = duration;
/* reset timestamp for next round */
timestamp = GST_CLOCK_TIME_NONE;
GstClockTime outtime, duration;
/* we will reuse the same caps object */
if (dec->outcaps == NULL) {
GstCaps *caps;
GstPadTemplate *template;
caps = gst_caps_new_simple ("audio/x-raw-int",
"rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, channels, NULL);
@ -158,32 +81,155 @@ sbc_dec_chain (GstPad * pad, GstBuffer * buffer)
gst_object_unref (template);
}
gst_buffer_set_caps (output, dec->outcaps);
gst_buffer_set_caps (outbuf, dec->outcaps);
/* calculate duration */
outtime = GST_BUFFER_TIMESTAMP (outbuf);
if (dec->next_timestamp != (guint64) - 1 && outtime != (guint64) - 1) {
duration = dec->next_timestamp - outtime;
} else if (outtime != (guint64) - 1) {
/* otherwise calculate duration based on outbuf size */
duration = gst_util_uint64_scale_int (outoffset / (2 * channels),
GST_SECOND, rate) - outtime;
} else {
duration = GST_CLOCK_TIME_NONE;
}
GST_BUFFER_DURATION (outbuf) = duration;
GST_BUFFER_SIZE (outbuf) = outoffset;
return gst_pad_push (dec->srcpad, outbuf);
if (discont) {
GST_BUFFER_FLAG_SET (output, GST_BUFFER_FLAG_DISCONT);
discont = FALSE;
}
res = gst_pad_push (dec->srcpad, output);
static GstFlowReturn
sbc_dec_chain (GstPad * pad, GstBuffer * buffer)
{
GstSbcDec *dec = GST_SBC_DEC (gst_pad_get_parent (pad));
GstFlowReturn res = GST_FLOW_OK;
const guint8 *indata;
guint insize;
GstClockTime timestamp;
gboolean discont;
GstBuffer *outbuf;
guint8 *outdata;
guint inoffset, outoffset;
gint rate, channels;
discont = GST_BUFFER_IS_DISCONT (buffer);
if (discont) {
/* reset previous buffer */
gst_adapter_clear (dec->adapter);
/* we need a new timestamp to lock onto */
dec->next_sample = -1;
}
gst_adapter_push (dec->adapter, buffer);
timestamp = GST_BUFFER_TIMESTAMP (buffer);
if (GST_CLOCK_TIME_IS_VALID (timestamp))
dec->next_timestamp = timestamp;
insize = gst_adapter_available (dec->adapter);
indata = gst_adapter_peek (dec->adapter, insize);
inoffset = 0;
outbuf = NULL;
channels = rate = 0;
while (insize > 0) {
gint inconsumed, outlen;
gint outsize;
size_t outconsumed;
if (outbuf == NULL) {
res = gst_pad_alloc_buffer_and_set_caps (dec->srcpad,
GST_BUFFER_OFFSET_NONE, BUF_SIZE, NULL, &outbuf);
if (res != GST_FLOW_OK)
goto done;
offset += consumed;
if (discont) {
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
discont = FALSE;
}
if (offset < size)
dec->buffer = gst_buffer_create_sub (buffer, offset, size - offset);
GST_BUFFER_TIMESTAMP (outbuf) = dec->next_timestamp;
outdata = GST_BUFFER_DATA (outbuf);
outsize = GST_BUFFER_SIZE (outbuf);
outoffset = 0;
}
GST_INFO_OBJECT (dec, "inoffset %d/%d, outoffset %d/%d", inoffset,
insize, outoffset, outsize);
inconsumed = sbc_decode (&dec->sbc, indata + inoffset, insize,
outdata + outoffset, outsize, &outconsumed);
GST_INFO_OBJECT (dec, "consumed %d, produced %d", inconsumed, outconsumed);
if (inconsumed <= 0) {
guint frame_len = sbc_get_frame_length (&dec->sbc);
/* skip a frame */
if (insize > frame_len) {
insize -= frame_len;
inoffset += frame_len;
} else {
insize = 0;
}
continue;
}
inoffset += inconsumed;
if ((gint) insize > inconsumed)
insize -= inconsumed;
else
insize = 0;
outoffset += outconsumed;
outsize -= outconsumed;
rate = gst_sbc_parse_rate_from_sbc (dec->sbc.frequency);
channels = gst_sbc_get_channel_number (dec->sbc.mode);
/* calculate timestamp either from the incomming buffers or
* from our sample counter */
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
/* lock onto timestamp when we have one */
dec->next_sample = gst_util_uint64_scale_int (timestamp,
rate, GST_SECOND);
timestamp = GST_CLOCK_TIME_NONE;
}
if (dec->next_sample != (guint64) - 1) {
/* calculate the next sample */
dec->next_sample += outconsumed / (2 * channels);
dec->next_timestamp = gst_util_uint64_scale_int (dec->next_sample,
GST_SECOND, rate);
}
/* check for space, push outbuf buffer */
outlen = sbc_get_codesize (&dec->sbc);
if (outsize < outlen) {
res = gst_sbc_dec_flush (dec, outbuf, outoffset, channels, rate);
if (res != GST_FLOW_OK)
goto done;
outbuf = NULL;
}
}
if (outbuf)
res = gst_sbc_dec_flush (dec, outbuf, outoffset, channels, rate);
gst_adapter_flush (dec->adapter, inoffset);
done:
gst_buffer_unref (buffer);
gst_object_unref (dec);
return res;
}
static GstStateChangeReturn
sbc_dec_change_state (GstElement * element, GstStateChange transition)
gst_sbc_dec_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn result;
GstSbcDec *dec = GST_SBC_DEC (element);
@ -191,10 +237,6 @@ sbc_dec_change_state (GstElement * element, GstStateChange transition)
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
GST_DEBUG ("Setup subband codec");
if (dec->buffer) {
gst_buffer_unref (dec->buffer);
dec->buffer = NULL;
}
sbc_init (&dec->sbc, 0);
dec->outcaps = NULL;
dec->next_sample = -1;
@ -208,10 +250,7 @@ sbc_dec_change_state (GstElement * element, GstStateChange transition)
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_DEBUG ("Finish subband codec");
if (dec->buffer) {
gst_buffer_unref (dec->buffer);
dec->buffer = NULL;
}
gst_adapter_clear (dec->adapter);
sbc_finish (&dec->sbc);
if (dec->outcaps) {
gst_caps_unref (dec->outcaps);
@ -243,11 +282,14 @@ gst_sbc_dec_base_init (gpointer g_class)
static void
gst_sbc_dec_class_init (GstSbcDecClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
element_class->change_state = GST_DEBUG_FUNCPTR (sbc_dec_change_state);
object_class->finalize = GST_DEBUG_FUNCPTR (gst_sbc_dec_finalize);
element_class->change_state = GST_DEBUG_FUNCPTR (gst_sbc_dec_change_state);
GST_DEBUG_CATEGORY_INIT (sbc_dec_debug, "sbcdec", 0, "SBC decoding element");
}
@ -263,9 +305,21 @@ gst_sbc_dec_init (GstSbcDec * self, GstSbcDecClass * klass)
self->srcpad = gst_pad_new_from_static_template (&sbc_dec_src_factory, "src");
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
self->adapter = gst_adapter_new ();
self->outcaps = NULL;
}
static void
gst_sbc_dec_finalize (GObject * obj)
{
GstSbcDec *self = GST_SBC_DEC (obj);
g_object_unref (self->adapter);
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
gboolean
gst_sbc_dec_plugin_init (GstPlugin * plugin)
{

View file

@ -22,6 +22,7 @@
*/
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include "sbc.h"
@ -47,13 +48,14 @@ struct _GstSbcDec {
GstPad *sinkpad;
GstPad *srcpad;
GstBuffer *buffer;
GstAdapter *adapter;
/* caps for outgoing buffers */
GstCaps *outcaps;
sbc_t sbc;
guint64 next_sample;
guint64 next_timestamp;
};
struct _GstSbcDecClass {