sbc: sbcdec: make decoder more performant
authorWim Taymans <wim.taymans@collabora.co.uk>
Tue, 8 Jan 2013 09:19:39 +0000 (10:19 +0100)
committerTim-Philipp Müller <tim@centricular.net>
Wed, 27 Mar 2013 22:21:17 +0000 (22:21 +0000)
Use an adapter to accumulate input buffers.
Decode all input in one output buffer when possible to reduce the amount of push
operations.

ext/sbc/gstsbcdec.c
ext/sbc/gstsbcdec.h

index 808344617a51326d22adbbc2cf73f6a5f39e6859..12245f9d7a86a5633a04979b5804302cca72b331 100644 (file)
 #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 =
@@ -54,136 +58,178 @@ GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
         "endianness = (int) BYTE_ORDER, "
         "signed = (boolean) true, " "width = (int) 16, " "depth = (int) 16"));
 
+static GstFlowReturn
+gst_sbc_dec_flush (GstSbcDec * dec, GstBuffer * outbuf,
+    gint outoffset, gint channels, gint rate)
+{
+  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);
+
+    template = gst_static_pad_template_get (&sbc_dec_src_factory);
+
+    dec->outcaps = gst_caps_intersect (caps,
+        gst_pad_template_get_caps (template));
+
+    gst_caps_unref (caps);
+    gst_object_unref (template);
+  }
+
+  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);
+
+}
+
 static GstFlowReturn
 sbc_dec_chain (GstPad * pad, GstBuffer * buffer)
 {
   GstSbcDec *dec = GST_SBC_DEC (gst_pad_get_parent (pad));
   GstFlowReturn res = GST_FLOW_OK;
-  guint size, codesize, offset = 0;
-  guint8 *data;
+  const guint8 *indata;
+  guint insize;
   GstClockTime timestamp;
   gboolean discont;
-
-  codesize = sbc_get_codesize (&dec->sbc);
+  GstBuffer *outbuf;
+  guint8 *outdata;
+  guint inoffset, outoffset;
+  gint rate, channels;
 
   discont = GST_BUFFER_IS_DISCONT (buffer);
   if (discont) {
     /* reset previous buffer */
-    gst_buffer_unref (dec->buffer);
-    dec->buffer = NULL;
+    gst_adapter_clear (dec->adapter);
     /* 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);
+  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);
 
-  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);
+  inoffset = 0;
+  outbuf = NULL;
+  channels = rate = 0;
+
+  while (insize > 0) {
+    gint inconsumed, outlen;
+    gint outsize;
+    size_t outconsumed;
 
-    if (res != GST_FLOW_OK)
-      goto done;
+    if (outbuf == NULL) {
+      res = gst_pad_alloc_buffer_and_set_caps (dec->srcpad,
+          GST_BUFFER_OFFSET_NONE, BUF_SIZE, NULL, &outbuf);
 
-    consumed = sbc_decode (&dec->sbc, data + offset, size - offset,
-        GST_BUFFER_DATA (output), codesize, NULL);
-    GST_INFO_OBJECT (dec, "consumed %d bytes", consumed);
+      if (res != GST_FLOW_OK)
+        goto done;
 
-    if (consumed <= 0) {
-      offset += sbc_get_frame_length (&dec->sbc);
+      if (discont) {
+        GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
+        discont = FALSE;
+      }
+
+      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) {
-      /* reconstruct timestamp from our sample counter otherwise */
-      timestamp = gst_util_uint64_scale_int (dec->next_sample,
+      /* calculate the next sample */
+      dec->next_sample += outconsumed / (2 * channels);
+      dec->next_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;
-
-    /* we will reuse the same caps object */
-    if (dec->outcaps == NULL) {
-      caps = gst_caps_new_simple ("audio/x-raw-int",
-          "rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, channels, NULL);
-
-      template = gst_static_pad_template_get (&sbc_dec_src_factory);
-
-      dec->outcaps = gst_caps_intersect (caps,
-          gst_pad_template_get_caps (template));
-
-      gst_caps_unref (caps);
-      gst_object_unref (template);
-    }
 
-    gst_buffer_set_caps (output, dec->outcaps);
+    /* 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;
 
-    if (discont) {
-      GST_BUFFER_FLAG_SET (output, GST_BUFFER_FLAG_DISCONT);
-      discont = FALSE;
+      outbuf = NULL;
     }
 
-    res = gst_pad_push (dec->srcpad, output);
-    if (res != GST_FLOW_OK)
-      goto done;
-
-    offset += consumed;
   }
 
-  if (offset < size)
-    dec->buffer = gst_buffer_create_sub (buffer, offset, size - offset);
+  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)
 {
index a62e61b2f8601f01aa0db79ae31e49fc36df73e1..f5b9416a98a3fc91657975ac91b7c6245b546fa7 100644 (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 {