ext/speex/: Rewrote speex encoder, make sure it can be embedded in ogg.
authorWim Taymans <wim.taymans@gmail.com>
Tue, 28 Sep 2004 16:44:12 +0000 (16:44 +0000)
committerWim Taymans <wim.taymans@gmail.com>
Tue, 28 Sep 2004 16:44:12 +0000 (16:44 +0000)
Original commit message from CVS:
* ext/speex/gstspeex.c: (plugin_init):
* ext/speex/gstspeexdec.c: (gst_speex_dec_base_init),
(gst_speex_dec_class_init), (speex_dec_get_formats),
(speex_get_event_masks), (speex_get_query_types),
(gst_speex_dec_init), (speex_dec_convert), (speex_dec_src_query),
(speex_dec_src_event), (speex_dec_event), (speex_dec_chain),
(gst_speexdec_get_property), (gst_speexdec_set_property),
(speex_dec_change_state):
* ext/speex/gstspeexdec.h:
* ext/speex/gstspeexenc.c: (gst_speexenc_get_formats),
(gst_speexenc_get_type), (speex_caps_factory), (raw_caps_factory),
(gst_speexenc_base_init), (gst_speexenc_class_init),
(gst_speexenc_sinkconnect), (gst_speexenc_convert_src),
(gst_speexenc_convert_sink), (gst_speexenc_get_query_types),
(gst_speexenc_src_query), (gst_speexenc_init),
(gst_speexenc_get_tag_value), (comment_init), (comment_add),
(gst_speexenc_metadata_set1), (gst_speexenc_set_metadata),
(gst_speexenc_setup), (gst_speexenc_buffer_from_data),
(gst_speexenc_push_buffer), (gst_speexenc_set_header_on_caps),
(gst_speexenc_chain), (gst_speexenc_get_property),
(gst_speexenc_set_property), (gst_speexenc_change_state):
* ext/speex/gstspeexenc.h:
Rewrote speex encoder, make sure it can be embedded in ogg.
Implemented speex decoder.

ChangeLog
ext/speex/gstspeex.c
ext/speex/gstspeexdec.c
ext/speex/gstspeexdec.h
ext/speex/gstspeexenc.c
ext/speex/gstspeexenc.h

index a9abcab..75b76db 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,30 @@
+2004-09-28  Wim Taymans  <wim@fluendo.com>
+
+       * ext/speex/gstspeex.c: (plugin_init):
+       * ext/speex/gstspeexdec.c: (gst_speex_dec_base_init),
+       (gst_speex_dec_class_init), (speex_dec_get_formats),
+       (speex_get_event_masks), (speex_get_query_types),
+       (gst_speex_dec_init), (speex_dec_convert), (speex_dec_src_query),
+       (speex_dec_src_event), (speex_dec_event), (speex_dec_chain),
+       (gst_speexdec_get_property), (gst_speexdec_set_property),
+       (speex_dec_change_state):
+       * ext/speex/gstspeexdec.h:
+       * ext/speex/gstspeexenc.c: (gst_speexenc_get_formats),
+       (gst_speexenc_get_type), (speex_caps_factory), (raw_caps_factory),
+       (gst_speexenc_base_init), (gst_speexenc_class_init),
+       (gst_speexenc_sinkconnect), (gst_speexenc_convert_src),
+       (gst_speexenc_convert_sink), (gst_speexenc_get_query_types),
+       (gst_speexenc_src_query), (gst_speexenc_init),
+       (gst_speexenc_get_tag_value), (comment_init), (comment_add),
+       (gst_speexenc_metadata_set1), (gst_speexenc_set_metadata),
+       (gst_speexenc_setup), (gst_speexenc_buffer_from_data),
+       (gst_speexenc_push_buffer), (gst_speexenc_set_header_on_caps),
+       (gst_speexenc_chain), (gst_speexenc_get_property),
+       (gst_speexenc_set_property), (gst_speexenc_change_state):
+       * ext/speex/gstspeexenc.h:
+       Rewrote speex encoder, make sure it can be embedded in ogg.
+       Implemented speex decoder.
+
 2004-09-28  Christian Schaller <christian@fluendo.com>
 
        * configure.ac:
index cde7457..c80211c 100644 (file)
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
+  if (!gst_library_load ("gstbytestream"))
+    return FALSE;
+
+  if (!gst_library_load ("gsttags"))
+    return FALSE;
+
   if (!gst_element_register (plugin, "speexenc", GST_RANK_NONE,
           GST_TYPE_SPEEXENC))
     return FALSE;
index 660f332..da246aa 100644 (file)
@@ -1,5 +1,5 @@
 /* GStreamer
- * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  * Boston, MA 02111-1307, USA.
  */
 
-
 #ifdef HAVE_CONFIG_H
-#include "config.h"
+#  include "config.h"
 #endif
-#include <string.h>
 
 #include "gstspeexdec.h"
+#include <string.h>
+#include <gst/tag/tag.h>
 
-/* elementfactory information */
-GstElementDetails gst_speexdec_details = {
-  "speex audio decoder",
+GST_DEBUG_CATEGORY (speexdec_debug);
+#define GST_CAT_DEFAULT speexdec_debug
+
+static GstElementDetails speex_dec_details = {
+  "SpeexDec",
   "Codec/Decoder/Audio",
-  ".speex",
-  "Wim Taymans <wim.taymans@chello.be>",
+  "decode speex streams to audio",
+  "Wim Taymans <wim@fluendo.com>",
 };
 
-/* SpeexDec signals and args */
+/* Filter signals and args */
 enum
 {
   /* FILL ME */
   LAST_SIGNAL
 };
 
+#define DEFAULT_ENH             TRUE
+
 enum
 {
-  ARG_0
-      /* FILL ME */
+  ARG_0,
+  ARG_ENH
 };
 
-static void gst_speexdec_base_init (gpointer g_class);
-static void gst_speexdec_class_init (GstSpeexDec * klass);
-static void gst_speexdec_init (GstSpeexDec * speexdec);
-
-static void gst_speexdec_chain (GstPad * pad, GstData * _data);
-static GstPadLinkReturn gst_speexdec_sinkconnect (GstPad * pad,
-    const GstCaps * caps);
-
-static GstElementClass *parent_class = NULL;
-
-/*static guint gst_speexdec_signals[LAST_SIGNAL] = { 0 }; */
-
-GType
-gst_speexdec_get_type (void)
-{
-  static GType speexdec_type = 0;
-
-  if (!speexdec_type) {
-    static const GTypeInfo speexdec_info = {
-      sizeof (GstSpeexDecClass),
-      gst_speexdec_base_init,
-      NULL,
-      (GClassInitFunc) gst_speexdec_class_init,
-      NULL,
-      NULL,
-      sizeof (GstSpeexDec),
-      0,
-      (GInstanceInitFunc) gst_speexdec_init,
-    };
-
-    speexdec_type =
-        g_type_register_static (GST_TYPE_ELEMENT, "GstSpeexDec", &speexdec_info,
-        0);
-  }
-  return speexdec_type;
-}
-
-static GstStaticPadTemplate speexdec_sink_template =
-GST_STATIC_PAD_TEMPLATE ("sink",
-    GST_PAD_SINK,
-    GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("audio/x-speex, "
-        "rate = (int) [ 1000, 48000 ], " "channels = (int) 1")
-    );
-
-static GstStaticPadTemplate speexdec_src_template =
+static GstStaticPadTemplate speex_dec_src_factory =
 GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("audio/x-raw-int, "
+        "rate = (int) [ 6000, 48000 ], "
+        "channels = (int) [ 1, 2 ], "
         "endianness = (int) BYTE_ORDER, "
-        "signed = (boolean) true, "
-        "width = (int) 16, "
-        "depth = (int) 16, "
-        "rate = (int) [ 1000, 48000 ], " "channels = (int) 1")
+        "signed = (boolean) true, " "width = (int) 16, " "depth = (int) 16")
+    );
+
+static GstStaticPadTemplate speex_dec_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/x-speex")
     );
 
+GST_BOILERPLATE (GstSpeexDec, gst_speex_dec, GstElement, GST_TYPE_ELEMENT);
+
+static void speex_dec_chain (GstPad * pad, GstData * data);
+static GstElementStateReturn speex_dec_change_state (GstElement * element);
+static const GstFormat *speex_dec_get_formats (GstPad * pad);
+
+static gboolean speex_dec_src_event (GstPad * pad, GstEvent * event);
+static gboolean speex_dec_src_query (GstPad * pad,
+    GstQueryType query, GstFormat * format, gint64 * value);
+static gboolean speex_dec_convert (GstPad * pad,
+    GstFormat src_format, gint64 src_value,
+    GstFormat * dest_format, gint64 * dest_value);
+
+static void gst_speexdec_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+static void gst_speexdec_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+
 static void
-gst_speexdec_base_init (gpointer g_class)
+gst_speex_dec_base_init (gpointer g_class)
 {
   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
 
   gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&speexdec_src_template));
+      gst_static_pad_template_get (&speex_dec_src_factory));
   gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&speexdec_sink_template));
-
-  gst_element_class_set_details (element_class, &gst_speexdec_details);
+      gst_static_pad_template_get (&speex_dec_sink_factory));
+  gst_element_class_set_details (element_class, &speex_dec_details);
 }
 
 static void
-gst_speexdec_class_init (GstSpeexDec * klass)
+gst_speex_dec_class_init (GstSpeexDecClass * klass)
 {
+  GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
 
+  gobject_class = (GObjectClass *) klass;
   gstelement_class = (GstElementClass *) klass;
 
-  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ENH,
+      g_param_spec_boolean ("enh", "Enh", "Enable perceptual enhancement",
+          DEFAULT_ENH, G_PARAM_READWRITE));
+
+  gstelement_class->change_state = speex_dec_change_state;
+
+  gobject_class->set_property = gst_speexdec_set_property;
+  gobject_class->get_property = gst_speexdec_get_property;
+
+  GST_DEBUG_CATEGORY_INIT (speexdec_debug, "speexdec", 0,
+      "speex decoding element");
 }
 
-static void
-gst_speexdec_init (GstSpeexDec * speexdec)
+static const GstFormat *
+speex_dec_get_formats (GstPad * pad)
+{
+  static GstFormat src_formats[] = {
+    GST_FORMAT_BYTES,
+    GST_FORMAT_DEFAULT,         /* samples in the audio case */
+    GST_FORMAT_TIME,
+    0
+  };
+  static GstFormat sink_formats[] = {
+    GST_FORMAT_BYTES,
+    GST_FORMAT_TIME,
+    GST_FORMAT_DEFAULT,         /* samples */
+    0
+  };
+
+  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
+}
+
+static const GstEventMask *
+speex_get_event_masks (GstPad * pad)
 {
-  GST_DEBUG ("gst_speexdec_init: initializing");
+  static const GstEventMask speex_dec_src_event_masks[] = {
+    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH},
+    {0,}
+  };
 
-  /* create the sink and src pads */
-  speexdec->sinkpad =
+  return speex_dec_src_event_masks;
+}
+
+static const GstQueryType *
+speex_get_query_types (GstPad * pad)
+{
+  static const GstQueryType speex_dec_src_query_types[] = {
+    GST_QUERY_TOTAL,
+    GST_QUERY_POSITION,
+    0
+  };
+
+  return speex_dec_src_query_types;
+}
+
+static void
+gst_speex_dec_init (GstSpeexDec * dec)
+{
+  dec->sinkpad =
       gst_pad_new_from_template (gst_static_pad_template_get
-      (&speexdec_sink_template), "sink");
-  gst_element_add_pad (GST_ELEMENT (speexdec), speexdec->sinkpad);
-  gst_pad_set_chain_function (speexdec->sinkpad, gst_speexdec_chain);
-  gst_pad_set_link_function (speexdec->sinkpad, gst_speexdec_sinkconnect);
+      (&speex_dec_sink_factory), "sink");
+  gst_pad_set_chain_function (dec->sinkpad, speex_dec_chain);
+  gst_pad_set_formats_function (dec->sinkpad, speex_dec_get_formats);
+  gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
 
-  speexdec->srcpad =
+  dec->srcpad =
       gst_pad_new_from_template (gst_static_pad_template_get
-      (&speexdec_src_template), "src");
-  gst_pad_use_explicit_caps (speexdec->srcpad);
-  gst_element_add_pad (GST_ELEMENT (speexdec), speexdec->srcpad);
+      (&speex_dec_src_factory), "src");
+  gst_pad_use_explicit_caps (dec->srcpad);
+  gst_pad_set_event_mask_function (dec->srcpad, speex_get_event_masks);
+  gst_pad_set_event_function (dec->srcpad, speex_dec_src_event);
+  gst_pad_set_query_type_function (dec->srcpad, speex_get_query_types);
+  gst_pad_set_query_function (dec->srcpad, speex_dec_src_query);
+  gst_pad_set_formats_function (dec->srcpad, speex_dec_get_formats);
+  gst_pad_set_convert_function (dec->srcpad, speex_dec_convert);
+  gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
+
+  dec->enh = DEFAULT_ENH;
+
+  GST_FLAG_SET (dec, GST_ELEMENT_EVENT_AWARE);
+}
 
+static gboolean
+speex_dec_convert (GstPad * pad,
+    GstFormat src_format, gint64 src_value,
+    GstFormat * dest_format, gint64 * dest_value)
+{
+  gboolean res = TRUE;
+  GstSpeexDec *dec;
+  guint64 scale = 1;
+
+  dec = GST_SPEEXDEC (gst_pad_get_parent (pad));
+
+  if (dec->packetno < 1)
+    return FALSE;
+
+  switch (src_format) {
+    case GST_FORMAT_TIME:
+      switch (*dest_format) {
+        case GST_FORMAT_BYTES:
+          scale = sizeof (float) * dec->header->nb_channels;
+        case GST_FORMAT_DEFAULT:
+          *dest_value = scale * (src_value * dec->header->rate / GST_SECOND);
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    case GST_FORMAT_DEFAULT:
+      switch (*dest_format) {
+        case GST_FORMAT_BYTES:
+          *dest_value = src_value * sizeof (float) * dec->header->nb_channels;
+          break;
+        case GST_FORMAT_TIME:
+          *dest_value = src_value * GST_SECOND / dec->header->rate;
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    case GST_FORMAT_BYTES:
+      switch (*dest_format) {
+        case GST_FORMAT_DEFAULT:
+          *dest_value = src_value / (sizeof (float) * dec->header->nb_channels);
+          break;
+        case GST_FORMAT_TIME:
+          *dest_value = src_value * GST_SECOND /
+              (dec->header->rate * sizeof (float) * dec->header->nb_channels);
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    default:
+      res = FALSE;
+  }
+
+  return res;
 }
 
-static GstPadLinkReturn
-gst_speexdec_sinkconnect (GstPad * pad, const GstCaps * caps)
+static gboolean
+speex_dec_src_query (GstPad * pad, GstQueryType query, GstFormat * format,
+    gint64 * value)
+{
+  gint64 samples_out = 0;
+  GstSpeexDec *dec = GST_SPEEXDEC (gst_pad_get_parent (pad));
+  GstFormat my_format = GST_FORMAT_DEFAULT;
+
+  if (query == GST_QUERY_POSITION) {
+    samples_out = dec->samples_out;
+  } else {
+    /* query peer in default format */
+    if (!gst_pad_query (GST_PAD_PEER (dec->sinkpad), query, &my_format,
+            &samples_out))
+      return FALSE;
+  }
+
+  /* and convert to the final format */
+  if (!gst_pad_convert (pad, GST_FORMAT_DEFAULT, samples_out, format, value))
+    return FALSE;
+
+  GST_LOG_OBJECT (dec,
+      "query %u: peer returned samples_out: %llu - we return %llu (format %u)\n",
+      query, samples_out, *value, *format);
+  return TRUE;
+}
+
+static gboolean
+speex_dec_src_event (GstPad * pad, GstEvent * event)
+{
+  gboolean res = TRUE;
+  GstSpeexDec *dec = GST_SPEEXDEC (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:{
+      guint64 value;
+      GstFormat my_format = GST_FORMAT_DEFAULT;
+
+      /* convert to samples_out */
+      res = speex_dec_convert (pad, GST_EVENT_SEEK_FORMAT (event),
+          GST_EVENT_SEEK_OFFSET (event), &my_format, &value);
+      if (res) {
+        GstEvent *real_seek = gst_event_new_seek (
+            (GST_EVENT_SEEK_TYPE (event) & ~GST_SEEK_FORMAT_MASK) |
+            GST_FORMAT_DEFAULT,
+            value);
+
+        res = gst_pad_send_event (GST_PAD_PEER (dec->sinkpad), real_seek);
+      }
+      gst_event_unref (event);
+      break;
+    }
+    default:
+      res = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  return res;
+}
+
+static void
+speex_dec_event (GstSpeexDec * dec, GstEvent * event)
+{
+  guint64 value, time, bytes;
+
+  GST_LOG_OBJECT (dec, "handling event");
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_DISCONTINUOUS:
+      if (gst_event_discont_get_value (event, GST_FORMAT_DEFAULT,
+              (gint64 *) & value)) {
+        dec->samples_out = value;
+        GST_DEBUG_OBJECT (dec,
+            "setting samples_out to %" G_GUINT64_FORMAT " after discont",
+            value);
+      } else {
+        GST_WARNING_OBJECT (dec,
+            "discont event didn't include offset, we might set it wrong now");
+      }
+      if (dec->packetno < 2) {
+        if (dec->samples_out != 0)
+          GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL),
+              ("can't handle discont before parsing first 2 packets"));
+        dec->packetno = 0;
+        gst_pad_push (dec->srcpad, GST_DATA (gst_event_new_discontinuous (FALSE,
+                    GST_FORMAT_TIME, (guint64) 0, GST_FORMAT_DEFAULT,
+                    (guint64) 0, GST_FORMAT_BYTES, (guint64) 0, 0)));
+      } else {
+        GstFormat time_format, default_format, bytes_format;
+
+        time_format = GST_FORMAT_TIME;
+        default_format = GST_FORMAT_DEFAULT;
+        bytes_format = GST_FORMAT_BYTES;
+
+        dec->packetno = 2;
+        /* if one of them works, all of them work */
+        if (speex_dec_convert (dec->srcpad, GST_FORMAT_DEFAULT,
+                dec->samples_out, &time_format, &time)
+            && speex_dec_convert (dec->srcpad, GST_FORMAT_DEFAULT,
+                dec->samples_out, &bytes_format, &bytes)) {
+          gst_pad_push (dec->srcpad,
+              GST_DATA (gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME,
+                      time, GST_FORMAT_DEFAULT, dec->samples_out,
+                      GST_FORMAT_BYTES, bytes, 0)));
+        } else {
+          GST_ERROR_OBJECT (dec,
+              "failed to parse data for DISCONT event, not sending any");
+        }
+      }
+      gst_data_unref (GST_DATA (event));
+      break;
+    default:
+      gst_pad_event_default (dec->sinkpad, event);
+      break;
+  }
+}
+
+static void
+speex_dec_chain (GstPad * pad, GstData * data)
+{
+  GstBuffer *buf;
+  GstSpeexDec *dec;
+
+  dec = GST_SPEEXDEC (gst_pad_get_parent (pad));
+  if (GST_IS_EVENT (data)) {
+    speex_dec_event (dec, GST_EVENT (data));
+    return;
+  }
+
+  buf = GST_BUFFER (data);
+
+  if (dec->packetno == 0) {
+    GstCaps *caps;
+
+    /* get the header */
+    dec->header =
+        speex_packet_to_header (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+    gst_data_unref (data);
+    if (!dec->header) {
+      GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
+          (NULL), ("couldn't read header"));
+      return;
+    }
+    if (dec->header->mode >= SPEEX_NB_MODES) {
+      GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
+          (NULL),
+          ("Mode number %d does not (yet/any longer) exist in this version",
+              dec->header->mode));
+      return;
+    }
+
+    dec->mode = speex_mode_list[dec->header->mode];
+
+    /* initialize the decoder */
+    dec->state = speex_decoder_init (dec->mode);
+    if (!dec->state) {
+      GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
+          (NULL), ("couldn't initialize decoder"));
+      gst_data_unref (data);
+      return;
+    }
+
+    speex_decoder_ctl (dec->state, SPEEX_SET_ENH, &dec->enh);
+    speex_decoder_ctl (dec->state, SPEEX_GET_FRAME_SIZE, &dec->frame_size);
+
+    if (dec->header->nb_channels != 1) {
+      dec->callback.callback_id = SPEEX_INBAND_STEREO;
+      dec->callback.func = speex_std_stereo_request_handler;
+      dec->callback.data = &dec->stereo;
+      speex_decoder_ctl (dec->state, SPEEX_SET_HANDLER, &dec->callback);
+    }
+
+    speex_decoder_ctl (dec->state, SPEEX_SET_SAMPLING_RATE, &dec->header->rate);
+
+    speex_bits_init (&dec->bits);
+
+    /* set caps */
+    caps = gst_caps_new_simple ("audio/x-raw-int",
+        "rate", G_TYPE_INT, dec->header->rate,
+        "channels", G_TYPE_INT, dec->header->nb_channels,
+        "signed", G_TYPE_BOOLEAN, TRUE,
+        "endianness", G_TYPE_INT, G_BYTE_ORDER,
+        "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16, NULL);
+
+    if (!gst_pad_set_explicit_caps (dec->srcpad, caps)) {
+      gst_caps_free (caps);
+      return;
+    }
+    gst_caps_free (caps);
+  } else if (dec->packetno == 1) {
+    gchar *encoder = NULL;
+
+    /* FIXME parse comments */
+    GstTagList *list = gst_tag_list_from_vorbiscomment_buffer (buf, "", 1,
+        &encoder);
+
+    gst_data_unref (data);
+
+    if (!list) {
+      GST_WARNING_OBJECT (dec, "couldn't decode comments");
+      list = gst_tag_list_new ();
+    }
+    if (encoder) {
+      gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+          GST_TAG_ENCODER, encoder, NULL);
+      g_free (encoder);
+    }
+    /*
+       gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+       GST_TAG_ENCODER_VERSION, dec->vi.version, NULL);
+
+       if (dec->vi.bitrate_upper > 0)
+       gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+       GST_TAG_MAXIMUM_BITRATE, (guint) vd->vi.bitrate_upper, NULL);
+       if (vd->vi.bitrate_nominal > 0)
+       gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+       GST_TAG_NOMINAL_BITRATE, (guint) vd->vi.bitrate_nominal, NULL);
+       if (vd->vi.bitrate_lower > 0)
+       gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+       GST_TAG_MINIMUM_BITRATE, (guint) vd->vi.bitrate_lower, NULL);
+     */
+    gst_element_found_tags_for_pad (GST_ELEMENT (dec), dec->srcpad, 0, list);
+  } else {
+    gint i;
+
+    /* send data to the bitstream */
+    speex_bits_read_from (&dec->bits, GST_BUFFER_DATA (buf),
+        GST_BUFFER_SIZE (buf));
+    gst_data_unref (data);
+
+    /* now decode each frame */
+    for (i = 0; i < dec->header->frames_per_packet; i++) {
+      gint ret;
+      GstBuffer *outbuf;
+      gint16 *out_data;
+
+      ret = speex_decode (dec->state, &dec->bits, dec->output);
+      if (ret == -1) {
+        /* FIXME emit warning */
+        break;
+      } else if (ret == -2) {
+        /* FIXME emit warning */
+        break;
+      }
+      if (speex_bits_remaining (&dec->bits) < 0) {
+        fprintf (stderr, "Decoding overflow: corrupted stream?\n");
+        break;
+      }
+      if (dec->header->nb_channels == 2)
+        speex_decode_stereo (dec->output, dec->frame_size, &dec->stereo);
+
+      outbuf = gst_pad_alloc_buffer (dec->srcpad, GST_BUFFER_OFFSET_NONE,
+          dec->frame_size * dec->header->nb_channels * 2);
+      out_data = (gint16 *) GST_BUFFER_DATA (outbuf);
+
+      /*PCM saturation (just in case) */
+      for (i = 0; i < dec->frame_size * dec->header->nb_channels; i++) {
+        if (dec->output[i] > 32000.0)
+          out_data[i] = 32000;
+        else if (dec->output[i] < -32000.0)
+          out_data[i] = -32000;
+        else
+          out_data[i] = (gint16) dec->output[i];
+      }
+
+      GST_BUFFER_OFFSET (outbuf) = dec->samples_out;
+      GST_BUFFER_OFFSET_END (outbuf) = dec->samples_out + dec->frame_size;
+      GST_BUFFER_TIMESTAMP (outbuf) =
+          dec->samples_out * GST_SECOND / dec->header->rate;
+      GST_BUFFER_DURATION (outbuf) =
+          dec->frame_size * GST_SECOND / dec->header->rate;
+      gst_pad_push (dec->srcpad, GST_DATA (outbuf));
+      dec->samples_out += dec->frame_size;
+    }
+  }
+  dec->packetno++;
+}
+
+static void
+gst_speexdec_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
 {
   GstSpeexDec *speexdec;
-  gint rate;
-  GstStructure *structure;
-
-  speexdec = GST_SPEEXDEC (gst_pad_get_parent (pad));
-
-  structure = gst_caps_get_structure (caps, 0);
-  gst_structure_get_int (structure, "rate", &rate);
-
-  if (gst_pad_set_explicit_caps (speexdec->srcpad,
-          gst_caps_new_simple ("audio/x-raw-int",
-              "endianness", G_TYPE_INT, G_BYTE_ORDER,
-              "signed", G_TYPE_BOOLEAN, TRUE,
-              "width", G_TYPE_INT, 16,
-              "depth", G_TYPE_INT, 16,
-              "rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, 1, NULL))) {
-    return GST_PAD_LINK_OK;
+
+  /* it's not null if we got it, but it might not be ours */
+  g_return_if_fail (GST_IS_SPEEXDEC (object));
+
+  speexdec = GST_SPEEXDEC (object);
+
+  switch (prop_id) {
+    case ARG_ENH:
+      g_value_set_boolean (value, speexdec->enh);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
   }
-  return GST_PAD_LINK_REFUSED;
 }
 
 static void
-gst_speexdec_chain (GstPad * pad, GstData * _data)
+gst_speexdec_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
 {
-  GstBuffer *buf = GST_BUFFER (_data);
   GstSpeexDec *speexdec;
-  gchar *data;
-  guint size;
 
-  g_return_if_fail (pad != NULL);
-  g_return_if_fail (GST_IS_PAD (pad));
-  g_return_if_fail (buf != NULL);
-  /*g_return_if_fail(GST_IS_BUFFER(buf)); */
+  /* it's not null if we got it, but it might not be ours */
+  g_return_if_fail (GST_IS_SPEEXDEC (object));
+
+  speexdec = GST_SPEEXDEC (object);
+
+  switch (prop_id) {
+    case ARG_ENH:
+      speexdec->enh = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
 
-  speexdec = GST_SPEEXDEC (gst_pad_get_parent (pad));
 
-  data = GST_BUFFER_DATA (buf);
-  size = GST_BUFFER_SIZE (buf);
+static GstElementStateReturn
+speex_dec_change_state (GstElement * element)
+{
+  GstSpeexDec *vd = GST_SPEEXDEC (element);
+
+  switch (GST_STATE_TRANSITION (element)) {
+    case GST_STATE_NULL_TO_READY:
+      break;
+    case GST_STATE_READY_TO_PAUSED:
+      break;
+    case GST_STATE_PAUSED_TO_PLAYING:
+      break;
+    case GST_STATE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_PAUSED_TO_READY:
+      vd->packetno = 0;
+      vd->samples_out = 0;
+      break;
+    case GST_STATE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
 
-  gst_buffer_unref (buf);
+  return parent_class->change_state (element);
 }
index c679dc1..dcb04a2 100644 (file)
 
 
 #include <gst/gst.h>
-#include <speex.h>
+#include <speex/speex.h>
+#include <speex/speex_callbacks.h>
+#include <speex/speex_header.h>
+#include <speex/speex_stereo.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -31,7 +34,7 @@ extern "C" {
 
 
 #define GST_TYPE_SPEEXDEC \
-  (gst_speexdec_get_type())
+  (gst_speex_dec_get_type())
 #define GST_SPEEXDEC(obj) \
   (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SPEEXDEC,GstSpeexDec))
 #define GST_SPEEXDEC_CLASS(klass) \
@@ -44,18 +47,35 @@ extern "C" {
 typedef struct _GstSpeexDec GstSpeexDec;
 typedef struct _GstSpeexDecClass GstSpeexDecClass;
 
+#define DEC_MAX_FRAME_SIZE 2000
+
 struct _GstSpeexDec {
   GstElement element;
 
   /* pads */
   GstPad *sinkpad,*srcpad;
+
+  void                 *state;
+  SpeexStereoState stereo;
+  SpeexMode    *mode;
+  SpeexHeader  *header;
+  SpeexCallback  callback;
+  SpeexBits      bits;
+     
+  gfloat         output[DEC_MAX_FRAME_SIZE];
+
+  gboolean      enh;
+  
+  gint          frame_size;
+  guint64       samples_out;
+  guint64       packetno;
 };
 
 struct _GstSpeexDecClass {
   GstElementClass parent_class;
 };
 
-GType gst_speexdec_get_type(void);
+GType gst_speex_dec_get_type(void);
 
 
 #ifdef __cplusplus
index 2552c92..530989c 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#include <stdlib.h>
 #include <string.h>
+#include <time.h>
+#include <math.h>
+#include <speex/speex.h>
+#include <speex/speex_stereo.h>
 
+#include <gst/gsttaginterface.h>
+#include <gst/tag/tag.h>
 #include "gstspeexenc.h"
 
+GST_DEBUG_CATEGORY (speexenc_debug);
+#define GST_CAT_DEFAULT speexenc_debug
+
+static GstPadTemplate *gst_speexenc_src_template, *gst_speexenc_sink_template;
+
 /* elementfactory information */
-GstElementDetails gst_speexenc_details = {
-  "speex audio encoder",
+GstElementDetails speexenc_details = {
+  "Speex encoder",
   "Codec/Encoder/Audio",
-  ".speex",
-  "Wim Taymans <wim.taymans@chello.be>",
+  "Encodes audio in Speex format",
+  "Wim Taymans <wim@fluendo.com>",
 };
 
-/* SpeexEnc signals and args */
+/* GstSpeexEnc signals and args */
 enum
 {
-  FRAME_ENCODED,
   /* FILL ME */
   LAST_SIGNAL
 };
 
+#define DEFAULT_QUALITY                8.0
+#define DEFAULT_BITRATE                0
+#define DEFAULT_VBR            FALSE
+#define DEFAULT_ABR            0
+#define DEFAULT_VAD            FALSE
+#define DEFAULT_DTX            FALSE
+#define DEFAULT_COMPLEXITY     3
+#define DEFAULT_NFRAMES                1
+
 enum
 {
-  ARG_0
-      /* FILL ME */
+  ARG_0,
+  ARG_QUALITY,
+  ARG_BITRATE,
+  ARG_VBR,
+  ARG_ABR,
+  ARG_VAD,
+  ARG_DTX,
+  ARG_COMPLEXITY,
+  ARG_NFRAMES,
+  ARG_LAST_MESSAGE
 };
 
+static const GstFormat *
+gst_speexenc_get_formats (GstPad * pad)
+{
+  static const GstFormat src_formats[] = {
+    GST_FORMAT_BYTES,
+    GST_FORMAT_TIME,
+    0
+  };
+  static const GstFormat sink_formats[] = {
+    GST_FORMAT_BYTES,
+    GST_FORMAT_DEFAULT,
+    GST_FORMAT_TIME,
+    0
+  };
+
+  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
+}
+
 static void gst_speexenc_base_init (gpointer g_class);
-static void gst_speexenc_class_init (GstSpeexEnc * klass);
+static void gst_speexenc_class_init (GstSpeexEncClass * klass);
 static void gst_speexenc_init (GstSpeexEnc * speexenc);
 
 static void gst_speexenc_chain (GstPad * pad, GstData * _data);
-static GstPadLinkReturn gst_speexenc_sinkconnect (GstPad * pad,
-    const GstCaps * caps);
+static gboolean gst_speexenc_setup (GstSpeexEnc * speexenc);
+
+static void gst_speexenc_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+static void gst_speexenc_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static GstElementStateReturn gst_speexenc_change_state (GstElement * element);
 
 static GstElementClass *parent_class = NULL;
-static guint gst_speexenc_signals[LAST_SIGNAL] = { 0 };
+
+/*static guint gst_speexenc_signals[LAST_SIGNAL] = { 0 }; */
 
 GType
 gst_speexenc_get_type (void)
@@ -75,49 +127,63 @@ gst_speexenc_get_type (void)
       0,
       (GInstanceInitFunc) gst_speexenc_init,
     };
+    static const GInterfaceInfo tag_setter_info = {
+      NULL,
+      NULL,
+      NULL
+    };
 
     speexenc_type =
         g_type_register_static (GST_TYPE_ELEMENT, "GstSpeexEnc", &speexenc_info,
         0);
+
+    g_type_add_interface_static (speexenc_type, GST_TYPE_TAG_SETTER,
+        &tag_setter_info);
+
+    GST_DEBUG_CATEGORY_INIT (speexenc_debug, "speexenc", 0, "Speex encoder");
   }
   return speexenc_type;
 }
 
-static GstStaticPadTemplate speexenc_sink_template =
-GST_STATIC_PAD_TEMPLATE ("sink",
-    GST_PAD_SINK,
-    GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("audio/x-raw-int, "
-        "endianness = (int) BYTE_ORDER, "
-        "signed = (boolean) true, "
-        "width = (int) 16, "
-        "depth = (int) 16, "
-        "rate = (int) [ 1000, 48000 ], " "channels = (int) 1")
-    );
-
-static GstStaticPadTemplate speexenc_src_template =
-GST_STATIC_PAD_TEMPLATE ("src",
-    GST_PAD_SRC,
-    GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("audio/x-speex, "
-        "rate = (int) [ 1000, 48000 ], " "channels = (int) 1")
-    );
+static GstCaps *
+speex_caps_factory (void)
+{
+  return gst_caps_new_simple ("audio/x-speex", NULL);
+}
+
+static GstCaps *
+raw_caps_factory (void)
+{
+  return
+      gst_caps_new_simple ("audio/x-raw-int",
+      "rate", GST_TYPE_INT_RANGE, 6000, 48000,
+      "channels", GST_TYPE_INT_RANGE, 1, 2,
+      "endianness", G_TYPE_INT, G_BYTE_ORDER,
+      "signed", G_TYPE_BOOLEAN, TRUE,
+      "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16, NULL);
+}
 
 static void
 gst_speexenc_base_init (gpointer g_class)
 {
   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+  GstCaps *raw_caps, *speex_caps;
 
-  gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&speexenc_sink_template));
-  gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&speexenc_src_template));
+  raw_caps = raw_caps_factory ();
+  speex_caps = speex_caps_factory ();
 
-  gst_element_class_set_details (element_class, &gst_speexenc_details);
+  gst_speexenc_sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
+      GST_PAD_ALWAYS, raw_caps);
+  gst_speexenc_src_template = gst_pad_template_new ("src", GST_PAD_SRC,
+      GST_PAD_ALWAYS, speex_caps);
+  gst_element_class_add_pad_template (element_class,
+      gst_speexenc_sink_template);
+  gst_element_class_add_pad_template (element_class, gst_speexenc_src_template);
+  gst_element_class_set_details (element_class, &speexenc_details);
 }
 
 static void
-gst_speexenc_class_init (GstSpeexEnc * klass)
+gst_speexenc_class_init (GstSpeexEncClass * klass)
 {
   GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
@@ -125,63 +191,640 @@ gst_speexenc_class_init (GstSpeexEnc * klass)
   gobject_class = (GObjectClass *) klass;
   gstelement_class = (GstElementClass *) klass;
 
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_QUALITY,
+      g_param_spec_float ("quality", "Quality", "Encoding quality",
+          0.0, 10.0, DEFAULT_QUALITY, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BITRATE,
+      g_param_spec_int ("bitrate", "Encoding Bit-rate",
+          "Specify an encoding bit-rate (in bps). ",
+          0, G_MAXINT, DEFAULT_BITRATE, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VBR,
+      g_param_spec_boolean ("vbr", "VBR",
+          "Enable variable bit-rate", DEFAULT_VBR, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ABR,
+      g_param_spec_int ("abr", "ABR",
+          "Enable average bit-rate (0 = disabled)",
+          0, G_MAXINT, DEFAULT_ABR, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VAD,
+      g_param_spec_boolean ("vad", "VAD",
+          "Enable voice activity detection", DEFAULT_VAD, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DTX,
+      g_param_spec_boolean ("dtx", "DTX",
+          "Enable discontinuous transmission", DEFAULT_DTX, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_COMPLEXITY,
+      g_param_spec_int ("complexity", "Complexity",
+          "Set encoding complexity",
+          0, G_MAXINT, DEFAULT_COMPLEXITY, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NFRAMES,
+      g_param_spec_int ("nframes", "NFrames",
+          "Number of frames per buffer",
+          0, G_MAXINT, DEFAULT_NFRAMES, G_PARAM_READWRITE));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LAST_MESSAGE,
+      g_param_spec_string ("last-message", "last-message",
+          "The last status message", NULL, G_PARAM_READABLE));
+
   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
 
-  gst_speexenc_signals[FRAME_ENCODED] =
-      g_signal_new ("frame-encoded", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstSpeexEncClass, frame_encoded),
-      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+  gobject_class->set_property = gst_speexenc_set_property;
+  gobject_class->get_property = gst_speexenc_get_property;
+
+  gstelement_class->change_state = gst_speexenc_change_state;
+}
+
+static GstPadLinkReturn
+gst_speexenc_sinkconnect (GstPad * pad, const GstCaps * caps)
+{
+  GstSpeexEnc *speexenc;
+  GstStructure *structure;
+
+  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
+  speexenc->setup = FALSE;
+
+  structure = gst_caps_get_structure (caps, 0);
+  gst_structure_get_int (structure, "channels", &speexenc->channels);
+  gst_structure_get_int (structure, "rate", &speexenc->rate);
+
+  gst_speexenc_setup (speexenc);
+
+  if (speexenc->setup)
+    return GST_PAD_LINK_OK;
+
+  return GST_PAD_LINK_REFUSED;
+}
+
+static gboolean
+gst_speexenc_convert_src (GstPad * pad, GstFormat src_format, gint64 src_value,
+    GstFormat * dest_format, gint64 * dest_value)
+{
+  gboolean res = TRUE;
+  GstSpeexEnc *speexenc;
+  gint64 avg;
+
+  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
+
+  if (speexenc->samples_in == 0 ||
+      speexenc->bytes_out == 0 || speexenc->rate == 0)
+    return FALSE;
+
+  avg = (speexenc->bytes_out * speexenc->rate) / (speexenc->samples_in);
+
+  switch (src_format) {
+    case GST_FORMAT_BYTES:
+      switch (*dest_format) {
+        case GST_FORMAT_TIME:
+          *dest_value = src_value * GST_SECOND / avg;
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    case GST_FORMAT_TIME:
+      switch (*dest_format) {
+        case GST_FORMAT_BYTES:
+          *dest_value = src_value * avg / GST_SECOND;
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    default:
+      res = FALSE;
+  }
+  return res;
+}
+
+static gboolean
+gst_speexenc_convert_sink (GstPad * pad, GstFormat src_format,
+    gint64 src_value, GstFormat * dest_format, gint64 * dest_value)
+{
+  gboolean res = TRUE;
+  guint scale = 1;
+  gint bytes_per_sample;
+  GstSpeexEnc *speexenc;
+
+  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
+
+  bytes_per_sample = speexenc->channels * 2;
+
+  switch (src_format) {
+    case GST_FORMAT_BYTES:
+      switch (*dest_format) {
+        case GST_FORMAT_DEFAULT:
+          if (bytes_per_sample == 0)
+            return FALSE;
+          *dest_value = src_value / bytes_per_sample;
+          break;
+        case GST_FORMAT_TIME:
+        {
+          gint byterate = bytes_per_sample * speexenc->rate;
+
+          if (byterate == 0)
+            return FALSE;
+          *dest_value = src_value * GST_SECOND / byterate;
+          break;
+        }
+        default:
+          res = FALSE;
+      }
+      break;
+    case GST_FORMAT_DEFAULT:
+      switch (*dest_format) {
+        case GST_FORMAT_BYTES:
+          *dest_value = src_value * bytes_per_sample;
+          break;
+        case GST_FORMAT_TIME:
+          if (speexenc->rate == 0)
+            return FALSE;
+          *dest_value = src_value * GST_SECOND / speexenc->rate;
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    case GST_FORMAT_TIME:
+      switch (*dest_format) {
+        case GST_FORMAT_BYTES:
+          scale = bytes_per_sample;
+          /* fallthrough */
+        case GST_FORMAT_DEFAULT:
+          *dest_value = src_value * scale * speexenc->rate / GST_SECOND;
+          break;
+        default:
+          res = FALSE;
+      }
+      break;
+    default:
+      res = FALSE;
+  }
+  return res;
+}
+
+static const GstQueryType *
+gst_speexenc_get_query_types (GstPad * pad)
+{
+  static const GstQueryType gst_speexenc_src_query_types[] = {
+    GST_QUERY_TOTAL,
+    GST_QUERY_POSITION,
+    0
+  };
+
+  return gst_speexenc_src_query_types;
 }
 
+static gboolean
+gst_speexenc_src_query (GstPad * pad, GstQueryType type,
+    GstFormat * format, gint64 * value)
+{
+  gboolean res = TRUE;
+  GstSpeexEnc *speexenc;
+
+  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
+
+  switch (type) {
+    case GST_QUERY_TOTAL:
+    {
+      switch (*format) {
+        case GST_FORMAT_BYTES:
+        case GST_FORMAT_TIME:
+        {
+          gint64 peer_value;
+          const GstFormat *peer_formats;
+
+          res = FALSE;
+
+          peer_formats = gst_pad_get_formats (GST_PAD_PEER (speexenc->sinkpad));
+
+          while (peer_formats && *peer_formats && !res) {
+
+            GstFormat peer_format = *peer_formats;
+
+            /* do the probe */
+            if (gst_pad_query (GST_PAD_PEER (speexenc->sinkpad),
+                    GST_QUERY_TOTAL, &peer_format, &peer_value)) {
+              GstFormat conv_format;
+
+              /* convert to TIME */
+              conv_format = GST_FORMAT_TIME;
+              res = gst_pad_convert (speexenc->sinkpad,
+                  peer_format, peer_value, &conv_format, value);
+              /* and to final format */
+              res &= gst_pad_convert (pad,
+                  GST_FORMAT_TIME, *value, format, value);
+            }
+            peer_formats++;
+          }
+          break;
+        }
+        default:
+          res = FALSE;
+          break;
+      }
+      break;
+    }
+    case GST_QUERY_POSITION:
+      switch (*format) {
+        default:
+        {
+          /* we only know about our samples, convert to requested format */
+          res = gst_pad_convert (pad,
+              GST_FORMAT_BYTES, speexenc->bytes_out, format, value);
+          break;
+        }
+      }
+      break;
+    default:
+      res = FALSE;
+      break;
+  }
+  return res;
+}
 
 static void
 gst_speexenc_init (GstSpeexEnc * speexenc)
 {
-  /* create the sink and src pads */
   speexenc->sinkpad =
-      gst_pad_new_from_template (gst_static_pad_template_get
-      (&speexenc_sink_template), "sink");
+      gst_pad_new_from_template (gst_speexenc_sink_template, "sink");
   gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->sinkpad);
   gst_pad_set_chain_function (speexenc->sinkpad, gst_speexenc_chain);
   gst_pad_set_link_function (speexenc->sinkpad, gst_speexenc_sinkconnect);
+  gst_pad_set_convert_function (speexenc->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_speexenc_convert_sink));
+  gst_pad_set_formats_function (speexenc->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_speexenc_get_formats));
 
   speexenc->srcpad =
-      gst_pad_new_from_template (gst_static_pad_template_get
-      (&speexenc_src_template), "src");
+      gst_pad_new_from_template (gst_speexenc_src_template, "src");
+  gst_pad_set_query_function (speexenc->srcpad,
+      GST_DEBUG_FUNCPTR (gst_speexenc_src_query));
+  gst_pad_set_query_type_function (speexenc->srcpad,
+      GST_DEBUG_FUNCPTR (gst_speexenc_get_query_types));
+  gst_pad_set_convert_function (speexenc->srcpad,
+      GST_DEBUG_FUNCPTR (gst_speexenc_convert_src));
+  gst_pad_set_formats_function (speexenc->srcpad,
+      GST_DEBUG_FUNCPTR (gst_speexenc_get_formats));
   gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->srcpad);
 
-  speex_bits_init (&speexenc->bits);
-  speexenc->mode = (SpeexMode *) & speex_nb_mode;
-  speexenc->bufsize = 0;
-  speexenc->packet_count = 0;
-  speexenc->n_packets = 20;
+  speexenc->channels = -1;
+  speexenc->rate = -1;
+
+  speexenc->quality = DEFAULT_QUALITY;
+  speexenc->bitrate = DEFAULT_BITRATE;
+  speexenc->vbr = DEFAULT_VBR;
+  speexenc->abr = DEFAULT_ABR;
+  speexenc->vad = DEFAULT_VAD;
+  speexenc->dtx = DEFAULT_DTX;
+  speexenc->complexity = DEFAULT_COMPLEXITY;
+  speexenc->nframes = DEFAULT_NFRAMES;
+
+  speexenc->setup = FALSE;
+  speexenc->eos = FALSE;
+  speexenc->header_sent = FALSE;
+
+  speexenc->tags = gst_tag_list_new ();
+  speexenc->adapter = gst_adapter_new ();
+
+  /* we're chained and we can deal with events */
+  GST_FLAG_SET (speexenc, GST_ELEMENT_EVENT_AWARE);
 }
 
-static GstPadLinkReturn
-gst_speexenc_sinkconnect (GstPad * pad, const GstCaps * caps)
+
+static gchar *
+gst_speexenc_get_tag_value (const GstTagList * list, const gchar * tag,
+    int index)
 {
-  GstSpeexEnc *speexenc;
-  GstStructure *structure;
+  gchar *speexvalue = NULL;
 
-  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
+  if (tag == NULL) {
+    return NULL;
+  }
 
-  structure = gst_caps_get_structure (caps, 0);
-  gst_structure_get_int (structure, "rate", &speexenc->rate);
-  if (gst_pad_try_set_caps (speexenc->srcpad,
-          gst_caps_new_simple ("audio/x-speex",
-              "rate", G_TYPE_INT, speexenc->rate,
-              "channels", G_TYPE_INT, 1, NULL))) {
-    speex_init_header (&speexenc->header, speexenc->rate, 1, speexenc->mode);
-    speexenc->header.frames_per_packet = speexenc->n_packets;
+  /* get tag name right */
+  if ((strcmp (tag, GST_TAG_TRACK_NUMBER) == 0)
+      || (strcmp (tag, GST_TAG_ALBUM_VOLUME_NUMBER) == 0)
+      || (strcmp (tag, GST_TAG_TRACK_COUNT) == 0)
+      || (strcmp (tag, GST_TAG_ALBUM_VOLUME_COUNT) == 0)) {
+    guint track_no;
 
-    speexenc->state = speex_encoder_init (speexenc->mode);
-    speex_encoder_ctl (speexenc->state, SPEEX_GET_FRAME_SIZE,
-        &speexenc->frame_size);
+    if (!gst_tag_list_get_uint_index (list, tag, index, &track_no))
+      g_assert_not_reached ();
+    speexvalue = g_strdup_printf ("%u", track_no);
+  } else if (strcmp (tag, GST_TAG_DATE) == 0) {
+    /* FIXME: how are dates represented in speex files? */
+    GDate *date;
+    guint u;
 
-    return GST_PAD_LINK_OK;
+    if (!gst_tag_list_get_uint_index (list, tag, index, &u))
+      g_assert_not_reached ();
+    date = g_date_new_julian (u);
+    speexvalue =
+        g_strdup_printf ("%04d-%02d-%02d", (gint) g_date_get_year (date),
+        (gint) g_date_get_month (date), (gint) g_date_get_day (date));
+    g_date_free (date);
+  } else if (gst_tag_get_type (tag) == G_TYPE_STRING) {
+    if (!gst_tag_list_get_string_index (list, tag, index, &speexvalue))
+      g_assert_not_reached ();
   }
-  return GST_PAD_LINK_REFUSED;
 
+  return speexvalue;
+}
+
+/*
+ *  Comments will be stored in the Vorbis style.
+ *  It is describled in the "Structure" section of
+ *  http://www.xiph.org/ogg/vorbis/doc/v-comment.html
+ *
+ *  The comment header is decoded as follows:
+ *  1) [vendor_length] = read an unsigned integer of 32 bits
+ *  2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
+ *  3) [user_comment_list_length] = read an unsigned integer of 32 bits
+ *  4) iterate [user_comment_list_length] times {
+ *     5) [length] = read an unsigned integer of 32 bits
+ *     6) this iteration's user comment = read a UTF-8 vector as [length] octets
+ *     }
+ *  7) [framing_bit] = read a single bit as boolean
+ *  8) if ( [framing_bit]  unset or end of packet ) then ERROR
+ *  9) done.
+ *
+ *  If you have troubles, please write to ymnk@jcraft.com.
+ */
+#define readint(buf, base) (((buf[base+3]<<24) & 0xff000000)| \
+                           ((buf[base+2]<<16) & 0xff0000)|   \
+                           ((buf[base+1]<< 8) & 0xff00)|     \
+                            (buf[base  ]      & 0xff))
+#define writeint(buf, base, val) do{ buf[base+3] = ((val)>>24) & 0xff; \
+                                    buf[base+2] = ((val)>>16) & 0xff; \
+                                    buf[base+1] = ((val)>> 8) & 0xff; \
+                                    buf[base  ] =  (val)      & 0xff; \
+                                 }while(0)
+
+static void
+comment_init (char **comments, int *length, char *vendor_string)
+{
+  int vendor_length = strlen (vendor_string);
+  int user_comment_list_length = 0;
+  int len = 4 + vendor_length + 4;
+  char *p = (char *) malloc (len);
+
+  if (p == NULL) {
+  }
+  writeint (p, 0, vendor_length);
+  memcpy (p + 4, vendor_string, vendor_length);
+  writeint (p, 4 + vendor_length, user_comment_list_length);
+  *length = len;
+  *comments = p;
+}
+static void
+comment_add (char **comments, int *length, const char *tag, char *val)
+{
+  char *p = *comments;
+  int vendor_length = readint (p, 0);
+  int user_comment_list_length = readint (p, 4 + vendor_length);
+  int tag_len = (tag ? strlen (tag) : 0);
+  int val_len = strlen (val);
+  int len = (*length) + 4 + tag_len + val_len;
+
+  p = (char *) realloc (p, len);
+
+  writeint (p, *length, tag_len + val_len);     /* length of comment */
+  if (tag)
+    memcpy (p + *length + 4, tag, tag_len);     /* comment */
+  memcpy (p + *length + 4 + tag_len, val, val_len);     /* comment */
+  writeint (p, 4 + vendor_length, user_comment_list_length + 1);
+
+  *comments = p;
+  *length = len;
+}
+
+#undef readint
+#undef writeint
+
+static void
+gst_speexenc_metadata_set1 (const GstTagList * list, const gchar * tag,
+    gpointer speexenc)
+{
+  const gchar *speextag = NULL;
+  gchar *speexvalue = NULL;
+  guint i, count;
+  GstSpeexEnc *enc = GST_SPEEXENC (speexenc);
+
+  speextag = gst_tag_to_vorbis_tag (tag);
+  if (speextag == NULL) {
+    return;
+  }
+
+  count = gst_tag_list_get_tag_size (list, tag);
+  for (i = 0; i < count; i++) {
+    speexvalue = gst_speexenc_get_tag_value (list, tag, i);
+
+    if (speexvalue != NULL) {
+      comment_add (&enc->comments, &enc->comment_len, speextag, speexvalue);
+    }
+  }
+}
+
+static void
+gst_speexenc_set_metadata (GstSpeexEnc * speexenc)
+{
+  GstTagList *copy;
+  const GstTagList *user_tags;
+
+  user_tags = gst_tag_setter_get_list (GST_TAG_SETTER (speexenc));
+  if (!(speexenc->tags || user_tags))
+    return;
+
+  comment_init (&speexenc->comments, &speexenc->comment_len,
+      "Encoded with GStreamer Speexenc");
+  copy =
+      gst_tag_list_merge (user_tags, speexenc->tags,
+      gst_tag_setter_get_merge_mode (GST_TAG_SETTER (speexenc)));
+  gst_tag_list_foreach (copy, gst_speexenc_metadata_set1, speexenc);
+  gst_tag_list_free (copy);
+}
+
+static gboolean
+gst_speexenc_setup (GstSpeexEnc * speexenc)
+{
+  speexenc->setup = FALSE;
+
+  switch (speexenc->mode) {
+    case GST_SPEEXENC_MODE_UWB:
+      speexenc->speex_mode = &speex_uwb_mode;
+      break;
+    case GST_SPEEXENC_MODE_WB:
+      speexenc->speex_mode = &speex_wb_mode;
+      break;
+    case GST_SPEEXENC_MODE_NB:
+      speexenc->speex_mode = &speex_nb_mode;
+      break;
+    case GST_SPEEXENC_MODE_AUTO:
+    default:
+      break;
+  }
+
+  if (speexenc->rate > 25000) {
+    if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) {
+      speexenc->speex_mode = &speex_uwb_mode;
+    } else {
+      if (speexenc->speex_mode != &speex_uwb_mode) {
+        speexenc->last_message =
+            g_strdup_printf
+            ("Warning: suggest to use ultra wide band mode for this rate");
+        g_object_notify (G_OBJECT (speexenc), "last_message");
+      }
+    }
+  } else if (speexenc->rate > 12500) {
+    if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) {
+      speexenc->speex_mode = &speex_wb_mode;
+    } else {
+      if (speexenc->speex_mode != &speex_wb_mode) {
+        speexenc->last_message =
+            g_strdup_printf
+            ("Warning: suggest to use wide band mode for this rate");
+        g_object_notify (G_OBJECT (speexenc), "last_message");
+      }
+    }
+  } else {
+    if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) {
+      speexenc->speex_mode = &speex_nb_mode;
+    } else {
+      if (speexenc->speex_mode != &speex_nb_mode) {
+        speexenc->last_message =
+            g_strdup_printf
+            ("Warning: suggest to use narrow band mode for this rate");
+        g_object_notify (G_OBJECT (speexenc), "last_message");
+      }
+    }
+  }
+
+  if (speexenc->rate != 8000 && speexenc->rate != 16000
+      && speexenc->rate != 32000) {
+    speexenc->last_message =
+        g_strdup_printf ("Warning: speex is optimized for 8, 16 and 32 KHz");
+    g_object_notify (G_OBJECT (speexenc), "last_message");
+  }
+
+  speex_init_header (&speexenc->header, speexenc->rate, 1,
+      speexenc->speex_mode);
+  speexenc->header.frames_per_packet = speexenc->nframes;
+  speexenc->header.vbr = speexenc->vbr;
+  speexenc->header.nb_channels = speexenc->channels;
+
+  /*Initialize Speex encoder */
+  speexenc->state = speex_encoder_init (speexenc->speex_mode);
+
+  speex_encoder_ctl (speexenc->state, SPEEX_GET_FRAME_SIZE,
+      &speexenc->frame_size);
+  speex_encoder_ctl (speexenc->state, SPEEX_SET_COMPLEXITY,
+      &speexenc->complexity);
+  speex_encoder_ctl (speexenc->state, SPEEX_SET_SAMPLING_RATE, &speexenc->rate);
+
+  if (speexenc->vbr)
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_VBR_QUALITY,
+        &speexenc->quality);
+  else {
+    gint tmp = floor (speexenc->quality);
+
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_QUALITY, &tmp);
+  }
+  if (speexenc->bitrate) {
+    if (speexenc->quality >= 0.0 && speexenc->vbr) {
+      speexenc->last_message =
+          g_strdup_printf ("Warning: bitrate option is overriding quality");
+      g_object_notify (G_OBJECT (speexenc), "last_message");
+    }
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_BITRATE, &speexenc->bitrate);
+  }
+  if (speexenc->vbr) {
+    gint tmp = 1;
+
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_VBR, &tmp);
+  } else if (speexenc->vad) {
+    gint tmp = 1;
+
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_VAD, &tmp);
+  }
+
+  if (speexenc->dtx) {
+    gint tmp = 1;
+
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_DTX, &tmp);
+  }
+
+  if (speexenc->dtx && !(speexenc->vbr || speexenc->abr || speexenc->vad)) {
+    speexenc->last_message =
+        g_strdup_printf ("Warning: dtx is useless without vad, vbr or abr");
+    g_object_notify (G_OBJECT (speexenc), "last_message");
+  } else if ((speexenc->vbr || speexenc->abr) && (speexenc->vad)) {
+    speexenc->last_message =
+        g_strdup_printf ("Warning: vad is already implied by vbr or abr");
+    g_object_notify (G_OBJECT (speexenc), "last_message");
+  }
+
+  if (speexenc->abr) {
+    speex_encoder_ctl (speexenc->state, SPEEX_SET_ABR, &speexenc->abr);
+  }
+
+  speex_encoder_ctl (speexenc->state, SPEEX_GET_LOOKAHEAD,
+      &speexenc->lookahead);
+
+  speexenc->setup = TRUE;
+
+  return TRUE;
+}
+
+/* prepare a buffer for transmission */
+static GstBuffer *
+gst_speexenc_buffer_from_data (GstSpeexEnc * speexenc, guchar * data,
+    gint data_len, guint64 granulepos)
+{
+  GstBuffer *outbuf;
+
+  outbuf = gst_buffer_new_and_alloc (data_len);
+  memcpy (GST_BUFFER_DATA (outbuf), data, data_len);
+  GST_BUFFER_OFFSET (outbuf) = speexenc->bytes_out;
+  GST_BUFFER_OFFSET_END (outbuf) = granulepos;
+
+  GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf));
+  return outbuf;
+}
+
+/* push out the buffer and do internal bookkeeping */
+static void
+gst_speexenc_push_buffer (GstSpeexEnc * speexenc, GstBuffer * buffer)
+{
+  speexenc->bytes_out += GST_BUFFER_SIZE (buffer);
+
+  if (GST_PAD_IS_USABLE (speexenc->srcpad)) {
+    gst_pad_push (speexenc->srcpad, GST_DATA (buffer));
+  } else {
+    gst_buffer_unref (buffer);
+  }
+}
+
+static void
+gst_speexenc_set_header_on_caps (GstCaps * caps, GstBuffer * buf1,
+    GstBuffer * buf2)
+{
+  GstStructure *structure = gst_caps_get_structure (caps, 0);
+  GValue list = { 0 };
+  GValue value = { 0 };
+
+  /* mark buffers */
+  GST_BUFFER_FLAG_SET (buf1, GST_BUFFER_IN_CAPS);
+  GST_BUFFER_FLAG_SET (buf2, GST_BUFFER_IN_CAPS);
+
+  /* put buffers in a fixed list */
+  g_value_init (&list, GST_TYPE_FIXED_LIST);
+  g_value_init (&value, GST_TYPE_BUFFER);
+  g_value_set_boxed (&value, buf1);
+  gst_value_list_append_value (&list, &value);
+  g_value_unset (&value);
+  g_value_init (&value, GST_TYPE_BUFFER);
+  g_value_set_boxed (&value, buf2);
+  gst_value_list_append_value (&list, &value);
+  gst_structure_set_value (structure, "streamheader", &list);
+  g_value_unset (&value);
+  g_value_unset (&list);
 }
 
 static void
@@ -189,103 +832,258 @@ gst_speexenc_chain (GstPad * pad, GstData * _data)
 {
   GstBuffer *buf = GST_BUFFER (_data);
   GstSpeexEnc *speexenc;
-  GstBuffer *outbuf;
-  gint16 *data;
-  guint8 *header_data;
-  gint size;
-  float input[1000];
-  gint frame_size;
-  gint i;
 
   g_return_if_fail (pad != NULL);
   g_return_if_fail (GST_IS_PAD (pad));
   g_return_if_fail (buf != NULL);
 
-  speexenc = GST_SPEEXENC (GST_OBJECT_PARENT (pad));
+  speexenc = GST_SPEEXENC (gst_pad_get_parent (pad));
 
-  if (!GST_PAD_CAPS (speexenc->srcpad)) {
+  if (GST_IS_EVENT (buf)) {
+    GstEvent *event = GST_EVENT (buf);
 
-    if (!gst_pad_try_set_caps (speexenc->srcpad,
-            gst_caps_new_simple ("audio/x-speex",
-                "rate", G_TYPE_INT, speexenc->rate,
-                "channels", G_TYPE_INT, 1, NULL))) {
-      GST_ELEMENT_ERROR (speexenc, CORE, NEGOTIATION, (NULL), (NULL));
+    switch (GST_EVENT_TYPE (event)) {
+      case GST_EVENT_EOS:
+        speexenc->eos = TRUE;
+        gst_event_unref (event);
+        break;
+      case GST_EVENT_TAG:
+        if (speexenc->tags) {
+          gst_tag_list_insert (speexenc->tags, gst_event_tag_get_list (event),
+              gst_tag_setter_get_merge_mode (GST_TAG_SETTER (speexenc)));
+        } else {
+          g_assert_not_reached ();
+        }
+        gst_pad_event_default (pad, event);
+        return;
+      default:
+        gst_pad_event_default (pad, event);
+        return;
+    }
+  } else {
+    if (!speexenc->setup) {
+      gst_buffer_unref (buf);
+      GST_ELEMENT_ERROR (speexenc, CORE, NEGOTIATION, (NULL),
+          ("encoder not initialized (input is not audio?)"));
       return;
     }
-  }
 
-  if (speexenc->packet_count == 0) {
-    header_data = speex_header_to_packet (&speexenc->header, &size);
+    if (!speexenc->header_sent) {
+      /* Speex streams begin with two headers; the initial header (with
+         most of the codec setup parameters) which is mandated by the Ogg
+         bitstream spec.  The second header holds any comment fields.
+         We merely need to make the headers, then pass them to libspeex 
+         one at a time; libspeex handles the additional Ogg bitstream 
+         constraints */
+      GstBuffer *buf1, *buf2;
+      GstCaps *caps;
+      guchar *data;
+      gint data_len;
 
-    outbuf = gst_buffer_new ();
-    GST_BUFFER_DATA (outbuf) = header_data;
-    GST_BUFFER_SIZE (outbuf) = size;
+      gst_speexenc_set_metadata (speexenc);
 
-    gst_pad_push (speexenc->srcpad, GST_DATA (outbuf));
-  }
+      /* create header buffer */
+      data = speex_header_to_packet (&speexenc->header, &data_len);
+      buf1 = gst_speexenc_buffer_from_data (speexenc, data, data_len, 0);
 
-  data = (gint16 *) GST_BUFFER_DATA (buf);
-  size = GST_BUFFER_SIZE (buf) / sizeof (gint16);
+      /* create comment buffer */
+      buf2 =
+          gst_speexenc_buffer_from_data (speexenc, speexenc->comments,
+          speexenc->comment_len, 0);
 
-  frame_size = speexenc->frame_size;
+      /* mark and put on caps */
+      caps = gst_pad_get_caps (speexenc->srcpad);
+      gst_speexenc_set_header_on_caps (caps, buf1, buf2);
 
-  if (speexenc->bufsize && (speexenc->bufsize + size >= frame_size)) {
-    memcpy (speexenc->buffer + speexenc->bufsize, data,
-        (frame_size - speexenc->bufsize) * sizeof (gint16));
+      /* negotiate with these caps */
+      GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps);
+      gst_pad_try_set_caps (speexenc->srcpad, caps);
 
-    for (i = 0; i < frame_size; i++)
-      input[i] = speexenc->buffer[i];
+      /* push out buffers */
+      gst_speexenc_push_buffer (speexenc, buf1);
+      gst_speexenc_push_buffer (speexenc, buf2);
 
-    speex_encode (speexenc->state, input, &speexenc->bits);
-    speexenc->packet_count++;
-
-    if (speexenc->packet_count % speexenc->n_packets == 0) {
-      GstBuffer *outbuf;
-
-      outbuf = gst_buffer_new_and_alloc (frame_size * speexenc->n_packets);
-      GST_BUFFER_SIZE (outbuf) = speex_bits_write (&speexenc->bits,
-          GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf));
-      GST_BUFFER_TIMESTAMP (outbuf) = speexenc->next_ts;
+      speex_bits_init (&speexenc->bits);
       speex_bits_reset (&speexenc->bits);
 
-      gst_pad_push (speexenc->srcpad, GST_DATA (outbuf));
-      speexenc->next_ts += frame_size * GST_SECOND / speexenc->rate;
+      speexenc->header_sent = TRUE;
     }
 
-    size -= (speexenc->frame_size - speexenc->bufsize);
-    data += (speexenc->frame_size - speexenc->bufsize);
+    {
+      gint frame_size = speexenc->frame_size;
+      gint bytes = frame_size * 2 * speexenc->channels;
 
-    speexenc->bufsize = 0;
-  }
+      /* push buffer to adapter */
+      gst_adapter_push (speexenc->adapter, buf);
 
-  while (size >= frame_size) {
-    for (i = 0; i < frame_size; i++)
-      input[i] = data[i];
+      while (gst_adapter_available (speexenc->adapter) >= bytes) {
+        gint16 *data;
+        gint i;
+        gint outsize, written;
+        GstBuffer *outbuf;
 
-    speex_encode (speexenc->state, input, &speexenc->bits);
-    speexenc->packet_count++;
+        data = (gint16 *) gst_adapter_peek (speexenc->adapter, bytes);
 
-    if (speexenc->packet_count % speexenc->n_packets == 0) {
-      GstBuffer *outbuf;
+        for (i = 0; i < frame_size * speexenc->channels; i++) {
+          speexenc->input[i] = (gfloat) data[i];
+        }
+        gst_adapter_flush (speexenc->adapter, bytes);
 
-      outbuf = gst_buffer_new_and_alloc (frame_size * speexenc->n_packets);
-      GST_BUFFER_SIZE (outbuf) = speex_bits_write (&speexenc->bits,
-          GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf));
-      GST_BUFFER_TIMESTAMP (outbuf) = speexenc->next_ts;
-      speex_bits_reset (&speexenc->bits);
+        speexenc->samples_in += frame_size;
+
+        if (speexenc->channels == 2) {
+          speex_encode_stereo (speexenc->input, frame_size, &speexenc->bits);
+        }
+        speex_encode (speexenc->state, speexenc->input, &speexenc->bits);
+
+        speexenc->frameno++;
 
-      gst_pad_push (speexenc->srcpad, GST_DATA (outbuf));
-      speexenc->next_ts += frame_size * GST_SECOND / speexenc->rate;
+        if ((speexenc->frameno % speexenc->nframes) != 0)
+          continue;
+
+        speex_bits_insert_terminator (&speexenc->bits);
+        outsize = speex_bits_nbytes (&speexenc->bits);
+        outbuf =
+            gst_pad_alloc_buffer (speexenc->srcpad, GST_BUFFER_OFFSET_NONE,
+            outsize);
+        written =
+            speex_bits_write (&speexenc->bits, GST_BUFFER_DATA (outbuf),
+            outsize);
+        g_assert (written == outsize);
+        speex_bits_reset (&speexenc->bits);
+
+        GST_BUFFER_OFFSET (outbuf) = speexenc->bytes_out;
+        GST_BUFFER_OFFSET_END (outbuf) =
+            speexenc->frameno * frame_size - speexenc->lookahead;
+
+        gst_speexenc_push_buffer (speexenc, outbuf);
+      }
     }
+  }
+
+  if (speexenc->eos) {
+    /* clean up and exit. */
+    gst_pad_push (speexenc->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
+    gst_element_set_eos (GST_ELEMENT (speexenc));
+  }
+}
+
+static void
+gst_speexenc_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstSpeexEnc *speexenc;
+
+  /* it's not null if we got it, but it might not be ours */
+  g_return_if_fail (GST_IS_SPEEXENC (object));
+
+  speexenc = GST_SPEEXENC (object);
 
-    size -= frame_size;
-    data += frame_size;
+  switch (prop_id) {
+    case ARG_QUALITY:
+      g_value_set_float (value, speexenc->quality);
+      break;
+    case ARG_BITRATE:
+      g_value_set_int (value, speexenc->bitrate);
+      break;
+    case ARG_VBR:
+      g_value_set_boolean (value, speexenc->vbr);
+      break;
+    case ARG_ABR:
+      g_value_set_int (value, speexenc->abr);
+      break;
+    case ARG_VAD:
+      g_value_set_boolean (value, speexenc->vad);
+      break;
+    case ARG_DTX:
+      g_value_set_boolean (value, speexenc->dtx);
+      break;
+    case ARG_COMPLEXITY:
+      g_value_set_int (value, speexenc->complexity);
+      break;
+    case ARG_NFRAMES:
+      g_value_set_int (value, speexenc->nframes);
+      break;
+    case ARG_LAST_MESSAGE:
+      g_value_set_string (value, speexenc->last_message);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
   }
+}
+
+static void
+gst_speexenc_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstSpeexEnc *speexenc;
+
+  /* it's not null if we got it, but it might not be ours */
+  g_return_if_fail (GST_IS_SPEEXENC (object));
+
+  speexenc = GST_SPEEXENC (object);
 
-  if (size) {
-    memcpy (speexenc->buffer + speexenc->bufsize, data, size * sizeof (gint16));
-    speexenc->bufsize += size;
+  switch (prop_id) {
+    case ARG_QUALITY:
+      speexenc->quality = g_value_get_float (value);
+      break;
+    case ARG_BITRATE:
+      speexenc->bitrate = g_value_get_int (value);
+      break;
+    case ARG_VBR:
+      speexenc->vbr = g_value_get_boolean (value);
+      break;
+    case ARG_ABR:
+      speexenc->abr = g_value_get_int (value);
+      break;
+    case ARG_VAD:
+      speexenc->vad = g_value_get_boolean (value);
+      break;
+    case ARG_DTX:
+      speexenc->dtx = g_value_get_boolean (value);
+      break;
+    case ARG_COMPLEXITY:
+      speexenc->complexity = g_value_get_int (value);
+      break;
+    case ARG_NFRAMES:
+      speexenc->nframes = g_value_get_int (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
   }
+}
+
+static GstElementStateReturn
+gst_speexenc_change_state (GstElement * element)
+{
+  GstSpeexEnc *speexenc = GST_SPEEXENC (element);
+
+  switch (GST_STATE_TRANSITION (element)) {
+    case GST_STATE_NULL_TO_READY:
+      break;
+    case GST_STATE_READY_TO_PAUSED:
+      speexenc->eos = FALSE;
+      speexenc->frameno = 0;
+      speexenc->samples_in = 0;
+      break;
+    case GST_STATE_PAUSED_TO_PLAYING:
+    case GST_STATE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_PAUSED_TO_READY:
+      speexenc->setup = FALSE;
+      speexenc->header_sent = FALSE;
+      gst_tag_list_free (speexenc->tags);
+      speexenc->tags = gst_tag_list_new ();
+      break;
+    case GST_STATE_READY_TO_NULL:
+    default:
+      break;
+  }
+
+  if (GST_ELEMENT_CLASS (parent_class)->change_state)
+    return GST_ELEMENT_CLASS (parent_class)->change_state (element);
 
-  gst_buffer_unref (buf);
+  return GST_STATE_SUCCESS;
 }
index 4677f6a..a8a8380 100644 (file)
@@ -23,6 +23,7 @@
 
 
 #include <gst/gst.h>
+#include <gst/bytestream/adapter.h>
 
 #include <speex.h>
 #include <speex_header.h>
@@ -43,6 +44,17 @@ extern "C" {
 #define GST_IS_SPEEXENC_CLASS(obj) \
   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SPEEXENC))
 
+#define MAX_FRAME_SIZE 2000*2
+#define MAX_FRAME_BYTES 2000
+
+typedef enum
+{
+  GST_SPEEXENC_MODE_AUTO,
+  GST_SPEEXENC_MODE_UWB,
+  GST_SPEEXENC_MODE_WB,
+  GST_SPEEXENC_MODE_NB,
+} GstSpeexMode;
+
 typedef struct _GstSpeexEnc GstSpeexEnc;
 typedef struct _GstSpeexEncClass GstSpeexEncClass;
 
@@ -58,14 +70,43 @@ struct _GstSpeexEnc {
 
   SpeexBits     bits;
   SpeexHeader   header;
-  SpeexMode    *mode;
+  SpeexMode    *speex_mode;
   void                 *state;
+  GstSpeexMode  mode;
+  GstAdapter    *adapter;
+
+  gfloat        quality;
+  gint                  bitrate;
+  gboolean      vbr;
+  gint          abr;
+  gboolean      vad;
+  gboolean      dtx;
+  gint          complexity;
+  gint                  nframes;
+
+  gint                  lookahead;
+
+  gint           channels;
+  gint           rate;
+
+  gboolean      setup;
+  gboolean       header_sent;
+  gboolean      eos;
+
+  guint64        samples_in;
+  guint64        bytes_out;
+
+  GstTagList    *tags;
+
+  gchar         *last_message;
+
   gint          frame_size;
-  gint16        buffer[2000];
-  gint          bufsize;
-  guint64       next_ts;
+  guint64       frameno;
+
+  gchar                *comments;
+  gint          comment_len;
 
-  gint          rate;
+  gfloat        input[MAX_FRAME_SIZE];
 };
 
 struct _GstSpeexEncClass {