gst_nokia_speech/gstframedaudioenc.c
changeset 8 4a7fac7dd34a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gst_nokia_speech/gstframedaudioenc.c	Fri Apr 16 15:15:52 2010 +0300
@@ -0,0 +1,439 @@
+/* GStreamer Framed Audio Encoder
+ * Copyright 2009 Collabora Ltd,
+ * Copyright 2009 Nokia Corporation
+ *  @author: Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include <string.h>
+
+#include "gstframedaudioenc.h"
+
+/* generic part */
+#ifndef RAW_FRAME_SIZE
+
+/* this will reference caller's debug category;
+ * there is a copy of this per plugin lib (= debug category) */
+GST_DEBUG_CATEGORY_STATIC (framedaudioenc_debug);
+#define GST_CAT_DEFAULT framedaudioenc_debug
+
+void
+gst_framed_audio_enc_reset (GstFramedAudioEnc * enc)
+{
+  gst_adapter_clear (enc->adapter);
+  enc->next_ts = GST_CLOCK_TIME_NONE;
+}
+
+/* installs @enc as element private for @element's pad,
+ * and possibly some event and query handler.
+ * if these need overriding, chain up to them
+ * chain and setcaps still need to be set by @element */
+void
+gst_framed_audio_enc_init (GstFramedAudioEnc * enc, GstElement * element,
+    GstDebugCategory * cat)
+{
+  enc->element = element;
+#ifndef GST_DISABLE_GST_DEBUG
+  framedaudioenc_debug = cat;
+#endif
+
+  enc->adapter = gst_adapter_new ();
+
+  /* hook some */
+  enc->sinkpad = gst_element_get_pad (enc->element, "sink");
+  g_assert (enc->sinkpad);
+  gst_pad_set_element_private (enc->sinkpad, enc);
+
+  /* watch downstream events */
+  gst_pad_set_event_function (enc->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_framed_audio_enc_sink_event));
+
+  gst_framed_audio_enc_reset (enc);
+}
+
+void
+gst_framed_audio_enc_finalize (GstFramedAudioEnc * enc)
+{
+  gst_object_unref (enc->adapter);
+
+  gst_pad_set_element_private (enc->sinkpad, NULL);
+  gst_object_unref (enc->sinkpad);
+}
+
+GstPad *
+gst_framed_audio_enc_request_new_pad (GstFramedAudioEnc * enc,
+    GstPadTemplate * templ, const gchar * req_name, GstPad ** pad_p)
+{
+  GstElement *element;
+  GstPad *newpad;
+  GstElementClass *klass;
+  GstCaps *caps;
+
+  g_return_val_if_fail (templ != NULL, NULL);
+
+  element = enc->element;
+  klass = GST_ELEMENT_GET_CLASS (element);
+
+  if (templ != gst_element_class_get_pad_template (klass, "cn"))
+    goto wrong_template;
+
+  GST_DEBUG_OBJECT (enc->element, "adding cn pad");
+  newpad = gst_pad_new_from_template (templ, "cn");
+  /* set template caps */
+  caps = gst_caps_copy (gst_pad_get_pad_template_caps (newpad));
+  gst_pad_set_caps (newpad, caps);
+  gst_caps_unref (caps);
+  gst_pad_use_fixed_caps (newpad);
+  gst_pad_set_active (newpad, TRUE);
+  /* only 1 pad by name can be added */
+  if (gst_element_add_pad (element, newpad)) {
+    GST_OBJECT_LOCK (element);
+    gst_object_replace ((GstObject **) pad_p, GST_OBJECT_CAST (newpad));
+    GST_OBJECT_UNLOCK (element);
+    GST_DEBUG_OBJECT (enc->element, "cn pad added");
+  } else {
+    gst_object_unref (newpad);
+    goto already_requested;
+  }
+
+  return newpad;
+
+  /* ERRORS */
+wrong_template:
+  {
+    GST_ERROR_OBJECT (element, "not our template!");
+    return NULL;
+  }
+already_requested:
+  {
+    GST_ERROR_OBJECT (element, "only 1 instance of a pad can be requested");
+    return NULL;
+  }
+}
+
+void
+gst_framed_audio_enc_release_pad (GstFramedAudioEnc * enc, GstPad * pad,
+    GstPad ** pad_p, void (disable_cn) (GstElement *))
+{
+  GstElement *element = enc->element;
+
+  GST_DEBUG_OBJECT (enc->element, "releasing cn pad");
+
+  GST_OBJECT_LOCK (element);
+  if (pad != *pad_p)
+    goto wrong_pad;
+  GST_OBJECT_UNLOCK (element);
+
+  /* reconfigure encoder */
+  disable_cn (element);
+
+  if (gst_element_remove_pad (element, pad)) {
+    GST_OBJECT_LOCK (element);
+    gst_object_replace ((GstObject **) pad_p, NULL);
+    GST_OBJECT_UNLOCK (element);
+    GST_DEBUG_OBJECT (enc->element, "cn pad released");
+  }
+
+  /* ERRORS */
+wrong_pad:
+  {
+    GST_OBJECT_UNLOCK (element);
+    GST_ERROR_OBJECT (element, "pad not requested; can not be released!");
+    return;
+  }
+}
+
+gboolean
+gst_framed_audio_enc_sink_event (GstPad * pad, GstEvent * event)
+{
+  GstFramedAudioEnc *enc;
+
+  enc = gst_pad_get_element_private (pad);
+  g_return_val_if_fail (enc, FALSE);
+
+  GST_LOG_OBJECT (enc->element, "received %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_FLUSH_STOP:
+      /* fall-through */
+    case GST_EVENT_EOS:
+      gst_adapter_clear (enc->adapter);
+      enc->next_ts = GST_CLOCK_TIME_NONE;
+      break;
+    default:
+      break;
+  }
+
+  return gst_pad_event_default (pad, event);
+}
+
+#else
+/* included part */
+
+/* parameters:
+     RAW_FRAME_SIZE
+     CODEC_FRAME_SIZE
+     FRAME_DURATION
+     AUDIO_SAMPLE_RATE (optional)
+   callback:
+     codec_get_data(enc, in, out, dtx)
+
+   If one does not mind a few cycles, include'ing can also be replaced by
+   a regular include & call, at the expense of some additional parameters
+   passed some way or another.
+*/
+
+#ifndef AUDIO_SAMPLE_RATE
+#define AUDIO_SAMPLE_RATE   (8000)
+#endif
+
+/* quite some conditional stuff;
+ * the (ugly?) cost of trying to stay inner loop optimal */
+
+static GstFlowReturn
+gst_framed_audio_enc_chain (GstFramedAudioEnc * enc, GstBuffer * buf,
+    GstPad * srcpad, GstPad ** _cnpad)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  GstBuffer *obuf = NULL;
+#ifdef CN_PAD
+  GstBuffer *cnbuf = NULL;
+  GstPad *cnpad = NULL;
+#endif
+  gboolean discont = FALSE;
+  const guint8 *data;
+  guint8 *odata;
+  gint av, flush, osize;
+
+#ifdef CN_PAD
+  GST_OBJECT_LOCK (enc->element);
+  if (_cnpad)
+    cnpad = *_cnpad;
+  if (cnpad)
+    gst_object_ref (cnpad);
+  GST_OBJECT_UNLOCK (enc->element);
+#endif
+
+  if (G_LIKELY (buf)) {
+    GST_LOG_OBJECT (enc->element, "input buffer of size %d with ts: %"
+        GST_TIME_FORMAT, GST_BUFFER_SIZE (buf),
+        GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
+    discont = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT);
+
+    /* reposition to the new buffer's timestamp,
+     * while correcting for some minor left-over */
+    if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+      if (GST_CLOCK_TIME_IS_VALID (enc->next_ts)) {
+        GstClockTimeDiff diff, limit;
+        GstClockTime tleft;
+
+        tleft = GST_FRAMES_TO_CLOCK_TIME
+            (gst_adapter_available (enc->adapter) / 2, AUDIO_SAMPLE_RATE);
+        diff =
+            GST_CLOCK_DIFF (enc->next_ts + tleft, GST_BUFFER_TIMESTAMP (buf));
+        limit = GST_SECOND / AUDIO_SAMPLE_RATE / 2;
+        /* try for a perfect stream if possible, do not act on rounding errors */
+        if (diff > limit || diff < -limit) {
+          enc->next_ts = GST_BUFFER_TIMESTAMP (buf);
+          if (enc->next_ts > tleft)
+            enc->next_ts -= tleft;
+          GST_LOG_OBJECT (enc->element, "marking discont based on timestamps");
+          discont = TRUE;
+        }
+      } else
+        enc->next_ts = GST_BUFFER_TIMESTAMP (buf);
+    }
+
+    gst_adapter_push (enc->adapter, buf);
+    buf = NULL;
+  }
+
+  av = gst_adapter_available (enc->adapter);
+  if (G_UNLIKELY (av < RAW_FRAME_SIZE))
+    goto done;
+
+  data = gst_adapter_peek (enc->adapter, av);
+  obuf = gst_buffer_new_and_alloc (av / RAW_FRAME_SIZE * CODEC_FRAME_SIZE);
+  odata = GST_BUFFER_DATA (obuf);
+  osize = 0;
+  flush = 0;
+
+  while (TRUE) {
+    gint esize;
+#ifdef CN_PAD
+    GstDtxDecision dtx;
+
+    /* safe default to start with, should get set */
+    dtx = GST_DTX_DECISION_VOICE;
+    esize = codec_get_data (enc->element, data + flush, odata, &dtx);
+#else
+    esize = codec_get_data (enc->element, data + flush, odata, NULL);
+#endif
+
+    if (G_UNLIKELY (esize < 0))
+      goto encode_failed;
+
+#ifdef CN_PAD
+    /* cn in a separate stream */
+    switch (dtx) {
+      case GST_DTX_DECISION_VOICE:
+#endif
+        flush += RAW_FRAME_SIZE;
+        av -= RAW_FRAME_SIZE;
+
+        odata += esize;
+        osize += esize;
+#ifdef CN_PAD
+        break;
+      case GST_DTX_DECISION_SID_UPDATE:
+        GST_LOG_OBJECT (enc->element, "dtx: SID_UPDATE %d", esize);
+        /* if already data before, need to put SID data separately */
+        if (G_UNLIKELY (osize)) {
+          cnbuf = gst_buffer_new_and_alloc (esize);
+          memcpy (GST_BUFFER_DATA (cnbuf), data + osize, esize);
+        } else {
+          cnbuf = obuf;
+          obuf = NULL;
+        }
+        /* and send one or both */
+        goto send;
+        break;
+      case GST_DTX_DECISION_SID_NONE:
+        GST_LOG_OBJECT (enc->element, "dtx: SID_NONE %d", esize);
+        /* maybe send preceding voice, if any */
+        goto send;
+        break;
+    }
+#endif
+
+#ifdef CODEC_FRAME_VARIABLE
+    /* flush output after insufficient data */
+    if (av >= RAW_FRAME_SIZE)
+      continue;
+#else
+    /* ... or some reduced (e.g. silence) frame */
+    if (esize >= CODEC_FRAME_SIZE && av >= RAW_FRAME_SIZE)
+      continue;
+#endif
+
+#ifdef CN_PAD
+  send:
+#endif
+    /* maybe a silent discarded frame */
+    if (G_LIKELY (osize)) {
+      GST_BUFFER_SIZE (obuf) = osize;
+      GST_BUFFER_DURATION (obuf)
+          = FRAME_DURATION * (flush / RAW_FRAME_SIZE);
+      GST_BUFFER_TIMESTAMP (obuf) = enc->next_ts;
+      if (G_UNLIKELY (discont)) {
+        GST_BUFFER_FLAG_SET (obuf, GST_BUFFER_FLAG_DISCONT);
+        discont = FALSE;
+      }
+
+      GST_LOG_OBJECT (enc->element,
+          "pushing buffer of size %d with ts: %" GST_TIME_FORMAT,
+          GST_BUFFER_SIZE (obuf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (obuf)));
+      gst_buffer_set_caps (obuf, GST_PAD_CAPS (srcpad));
+      ret = gst_pad_push (srcpad, obuf);
+      obuf = NULL;
+    } else {
+      ret = GST_FLOW_OK;
+    }
+
+#ifdef CN_PAD
+    /* check for stuff to send on cn pad */
+    if (cnbuf && cnpad) {
+      /* only at most 1 SID update per buffer */
+      GST_BUFFER_SIZE (cnbuf) = esize;
+      GST_BUFFER_DURATION (cnbuf) = FRAME_DURATION;
+      GST_BUFFER_TIMESTAMP (cnbuf) = enc->next_ts;
+
+      GST_LOG_OBJECT (enc->element,
+          "pushing cn buffer of size %d with ts: %" GST_TIME_FORMAT,
+          GST_BUFFER_SIZE (cnbuf),
+          GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (cnbuf)));
+      gst_buffer_set_caps (cnbuf, GST_PAD_CAPS (cnpad));
+      if (G_LIKELY (ret == GST_FLOW_OK)) {
+        ret = gst_pad_push (cnpad, cnbuf);
+        /* cn pad may not be linked */
+        if (G_UNLIKELY (ret == GST_FLOW_NOT_LINKED))
+          ret = GST_FLOW_OK;
+      } else
+        gst_pad_push (cnpad, cnbuf);
+      cnbuf = NULL;
+    } else if (G_UNLIKELY (cnbuf)) {
+      /* should not occur */
+      gst_buffer_unref (cnbuf);
+      cnbuf = NULL;
+    }
+
+    if (dtx != GST_DTX_DECISION_VOICE) {
+      /* still need to count non-voice encoded frame */
+      flush += RAW_FRAME_SIZE;
+      av -= RAW_FRAME_SIZE;
+    }
+#endif /* CN_PAD */
+
+    /* remove used part */
+    gst_adapter_flush (enc->adapter, flush);
+    if (GST_CLOCK_TIME_IS_VALID (enc->next_ts))
+      enc->next_ts += FRAME_DURATION * (flush / RAW_FRAME_SIZE);
+
+    /* end if insufficient left or error */
+    if (av < RAW_FRAME_SIZE || ret != GST_FLOW_OK)
+      break;
+
+    /* allocate new buffer */
+    if (!obuf) {
+      obuf = gst_buffer_new_and_alloc (av / RAW_FRAME_SIZE * CODEC_FRAME_SIZE);
+      odata = GST_BUFFER_DATA (obuf);
+      osize = 0;
+    }
+    /* and prepare to consume again */
+    data = gst_adapter_peek (enc->adapter, av);
+    flush = 0;
+  }
+
+  if (!av) {
+    enc->next_ts = GST_CLOCK_TIME_NONE;
+  }
+
+done:
+#ifdef CN_PAD
+  GST_OBJECT_LOCK (enc->element);
+  if (cnpad)
+    gst_object_unref (cnpad);
+  GST_OBJECT_UNLOCK (enc->element);
+#endif
+
+  return ret;
+
+  /* ERRORS */
+encode_failed:
+  {
+    GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), (NULL));
+    ret = GST_FLOW_ERROR;
+    gst_buffer_unref (obuf);
+    goto done;
+  }
+}
+#endif