--- /dev/null
+/* GStreamer
+ * Copyright (C) 2009 Pioneers of the Inevitable <songbird@songbirdnest.com>
+ *
+ * Authors: Peter van Hardenberg <pvh@songbirdnest.com>
+ *
+ * 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.
+ */
+
+/* Based on ADPCM encoders in libsndfile,
+ Copyright (C) 1999-2002 Erik de Castro Lopo <erikd@zip.com.au
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+#define GST_TYPE_ADPCM_ENC \
+ (adpcmenc_get_type ())
+
+#define GST_TYPE_ADPCMENC_LAYOUT \
+ (adpcmenc_layout_get_type ())
+
+#define GST_ADPCM_ENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_ADPCM_ENC, ADPCMEnc))
+
+#define GST_CAT_DEFAULT adpcmenc_debug
+GST_DEBUG_CATEGORY_STATIC (adpcmenc_debug);
+
+static const GstElementDetails adpcmenc_details =
+GST_ELEMENT_DETAILS ("ADPCM encoder",
+ "Codec/Encoder/Audio",
+ "Encode ADPCM audio",
+ "Pioneers of the Inevitable <songbird@songbirdnest.com");
+
+static GstStaticPadTemplate adpcmenc_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw-int, "
+ "depth = (int)16, "
+ "width = (int)16, " "channels = (int) [1,2], " "rate = (int)[1, MAX]")
+ );
+
+static GstStaticPadTemplate adpcmenc_src_template =
+ GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-adpcm, "
+ " layout=(string){dvi}, "
+ " block_align = (int) [64, 8192], "
+ " rate = (int)[ 1, MAX ], " "channels = (int)[1,2];")
+ );
+
+#define MIN_ADPCM_BLOCK_SIZE 64
+#define MAX_ADPCM_BLOCK_SIZE 8192
+#define DEFAULT_ADPCM_BLOCK_SIZE 1024
+#define DEFAULT_ADPCM_LAYOUT LAYOUT_ADPCM_DVI
+
+static int ima_indx_adjust[16] = {
+ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8,
+};
+
+static int ima_step_size[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+ 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230,
+ 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963,
+ 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327,
+ 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442,
+ 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
+ 32767
+};
+
+
+enum adpcm_properties
+{
+ ARG_0,
+ ARG_BLOCK_SIZE,
+ ARG_LAYOUT
+};
+
+enum adpcm_layout
+{
+ LAYOUT_ADPCM_DVI
+};
+
+static GType
+adpcmenc_layout_get_type (void)
+{
+ static GType adpcmenc_layout_type = 0;
+
+ if (!adpcmenc_layout_type) {
+ static GEnumValue layout_types[] = {
+ {LAYOUT_ADPCM_DVI, "DVI/IMA APDCM", "dvi"},
+ {0, NULL, NULL},
+ };
+
+ adpcmenc_layout_type = g_enum_register_static ("GstADPCMEncLayout",
+ layout_types);
+ }
+
+ return adpcmenc_layout_type;
+}
+
+typedef struct _ADPCMEncClass
+{
+ GstElementClass parent_class;
+} ADPCMEncClass;
+
+typedef struct _ADPCMEnc
+{
+ GstElement parent;
+
+ GstPad *sinkpad;
+ GstPad *srcpad;
+
+ GstCaps *output_caps;
+
+ enum adpcm_layout layout;
+ int rate;
+ int channels;
+ int blocksize;
+ int samples_per_block;
+
+ guint8 step_index[2];
+
+ gboolean is_setup;
+
+ GstClockTime timestamp;
+ GstClockTime base_timestamp;
+
+ guint64 out_samples;
+
+ GstAdapter *adapter;
+
+} ADPCMEnc;
+
+GST_BOILERPLATE (ADPCMEnc, adpcmenc, GstElement, GST_TYPE_ELEMENT);
+static gboolean
+adpcmenc_setup (ADPCMEnc * enc)
+{
+ const int DVI_IMA_HEADER_SIZE = 4;
+ const int ADPCM_SAMPLES_PER_BYTE = 2;
+ guint64 sample_bytes;
+
+ char *layout;
+ switch (enc->layout) {
+ case LAYOUT_ADPCM_DVI:
+ layout = "dvi";
+ /* IMA ADPCM includes a 4-byte header per channel, */
+ sample_bytes = enc->blocksize - (DVI_IMA_HEADER_SIZE * enc->channels);
+ /* two samples per byte, plus a single sample in the header. */
+ enc->samples_per_block =
+ ((sample_bytes * ADPCM_SAMPLES_PER_BYTE) / enc->channels) + 1;
+ break;
+ default:
+ GST_WARNING_OBJECT (enc, "Invalid layout");
+ return FALSE;
+ }
+
+ enc->output_caps = gst_caps_new_simple ("audio/x-adpcm",
+ "rate", G_TYPE_INT, enc->rate,
+ "channels", G_TYPE_INT, enc->channels,
+ "layout", G_TYPE_STRING, layout,
+ "block_align", G_TYPE_INT, enc->blocksize, NULL);
+
+ if (enc->output_caps) {
+ gst_pad_set_caps (enc->srcpad, enc->output_caps);
+ }
+
+ enc->is_setup = TRUE;
+ enc->timestamp = GST_CLOCK_TIME_NONE;
+ enc->base_timestamp = GST_CLOCK_TIME_NONE;
+ enc->adapter = gst_adapter_new ();
+ enc->out_samples = 0;
+
+ /* Step index state is carried between blocks. */
+ enc->step_index[0] = 0;
+ enc->step_index[1] = 0;
+
+ return TRUE;
+}
+
+static void
+adpcmenc_teardown (ADPCMEnc * enc)
+{
+ if (enc->output_caps) {
+ gst_caps_unref (enc->output_caps);
+ enc->output_caps = NULL;
+ }
+ if (enc->adapter) {
+ g_object_unref (enc->adapter);
+ enc->adapter = NULL;
+ }
+ enc->is_setup = FALSE;
+}
+
+static gboolean
+adpcmenc_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+ ADPCMEnc *enc = (ADPCMEnc *) gst_pad_get_parent (pad);
+ GstStructure *structure = gst_caps_get_structure (caps, 0);
+
+ if (!gst_structure_get_int (structure, "rate", &enc->rate))
+ return FALSE;
+ if (!gst_structure_get_int (structure, "channels", &enc->channels))
+ return FALSE;
+
+ if (enc->is_setup) {
+ adpcmenc_teardown (enc);
+ }
+ adpcmenc_setup (enc);
+
+ gst_object_unref (enc);
+
+ return TRUE;
+}
+
+static void
+adpcmenc_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ ADPCMEnc *enc = GST_ADPCM_ENC (object);
+
+ switch (prop_id) {
+ case ARG_BLOCK_SIZE:
+ enc->blocksize = g_value_get_int (value);
+ break;
+ case ARG_LAYOUT:
+ enc->layout = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+adpcmenc_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ ADPCMEnc *enc = GST_ADPCM_ENC (object);
+
+ switch (prop_id) {
+ case ARG_BLOCK_SIZE:
+ g_value_set_int (value, enc->blocksize);
+ break;
+ case ARG_LAYOUT:
+ g_value_set_enum (value, enc->layout);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static guint8
+adpcmenc_encode_ima_sample (gint16 sample, gint16 * prev_sample,
+ guint8 * stepindex)
+{
+ const int NEGATIVE_SIGN_BIT = 0x8;
+ int diff, vpdiff, mask, step;
+ int bytecode = 0x0;
+ diff = sample - *prev_sample;
+ step = ima_step_size[*stepindex];
+ vpdiff = step >> 3;
+
+ if (diff < 0) {
+ diff = -diff;
+ bytecode = NEGATIVE_SIGN_BIT;
+ }
+
+ mask = 0x4;
+ while (mask > 0) {
+ if (diff >= step) {
+ bytecode |= mask;
+ diff -= step;
+ vpdiff += step;
+ }
+ step >>= 1;
+ mask >>= 1;
+ }
+
+ if (bytecode & 8) {
+ vpdiff = -vpdiff;
+ }
+
+ *prev_sample = CLAMP (*prev_sample + vpdiff, G_MININT16, G_MAXINT16);
+ *stepindex = CLAMP (*stepindex + ima_indx_adjust[bytecode], 0, 88);
+
+ return bytecode;
+}
+
+static gboolean
+adpcmenc_encode_ima_block (ADPCMEnc * enc, gint16 * samples, guint8 * outbuf)
+{
+ const int HEADER_SIZE = 4;
+ gint16 prev_sample[2] = { 0, 0 };
+ guint32 write_pos = 0;
+ guint32 read_pos = 0;
+ guint8 channel = 0;
+
+ /* Write a header for each channel.
+ * The header consists of a sixteen-bit predicted sound value,
+ * and an eight bit step_index, carried forward from any previous block.
+ * These allow seeking within the file.
+ */
+ for (channel = 0; channel < enc->channels; channel++) {
+ write_pos = channel * HEADER_SIZE;
+ outbuf[write_pos + 0] = (samples[channel] & 0xFF);
+ outbuf[write_pos + 1] = (samples[channel] >> 8) & 0xFF;
+ outbuf[write_pos + 2] = enc->step_index[channel];
+ outbuf[write_pos + 3] = 0;
+ prev_sample[channel] = samples[channel];
+ }
+
+ /* raw-audio looks like this for a stereo stream:
+ * [ L, R, L, R, L, R ... ]
+ * encoded audio is in eight-sample blocks, two samples to a byte thusly:
+ * [ LL, LL, LL, LL, RR, RR, RR, RR ... ]
+ */
+ write_pos = HEADER_SIZE * enc->channels;
+ read_pos = enc->channels; /* the first sample is in the header. */
+ while (write_pos < enc->blocksize) {
+ gint8 CHANNEL_CHUNK_SIZE = 8;
+ for (channel = 0; channel < enc->channels; channel++) {
+ /* convert eight samples (four bytes) per channel, then swap */
+ guint32 channel_chunk_base = read_pos + channel;
+ gint8 chunk;
+ for (chunk = 0; chunk < CHANNEL_CHUNK_SIZE; chunk++) {
+ guint8 packed_byte = 0, encoded_sample;
+ encoded_sample =
+ adpcmenc_encode_ima_sample (samples[channel_chunk_base +
+ (chunk * enc->channels)], &prev_sample[channel],
+ &enc->step_index[channel]);
+ packed_byte |= encoded_sample & 0x0F;
+
+ chunk++;
+
+ encoded_sample =
+ adpcmenc_encode_ima_sample (samples[channel_chunk_base +
+ (chunk * enc->channels)], &prev_sample[channel],
+ &enc->step_index[channel]);
+ packed_byte |= encoded_sample << 4 & 0xF0;
+
+ outbuf[write_pos++] = packed_byte;
+ }
+ }
+ /* advance to the next block of 8 samples per channel */
+ read_pos += CHANNEL_CHUNK_SIZE * enc->channels;
+ if (read_pos > enc->samples_per_block * enc->channels) {
+ GST_LOG ("Ran past the end. (Reading %i of %i.)", read_pos,
+ enc->samples_per_block);
+ }
+ }
+
+ return TRUE;
+}
+
+static GstFlowReturn
+adpcmenc_encode_block (ADPCMEnc * enc, gint16 * samples, int blocksize)
+{
+ gboolean res;
+ GstBuffer *outbuf = NULL;
+
+ if (enc->layout == LAYOUT_ADPCM_DVI) {
+ outbuf = gst_buffer_new_and_alloc (enc->blocksize);
+ res = adpcmenc_encode_ima_block (enc, samples, GST_BUFFER_DATA (outbuf));
+ } else {
+ GST_WARNING_OBJECT (enc, "Unknown layout");
+ return GST_FLOW_ERROR;
+ }
+
+ if (!res) {
+ gst_buffer_unref (outbuf);
+ GST_WARNING_OBJECT (enc, "Encode of block failed");
+ return GST_FLOW_ERROR;
+ }
+
+ gst_buffer_set_caps (outbuf, enc->output_caps);
+ GST_BUFFER_TIMESTAMP (outbuf) = enc->timestamp;
+
+ enc->out_samples += enc->samples_per_block;
+ enc->timestamp = enc->base_timestamp +
+ gst_util_uint64_scale_int (enc->out_samples, GST_SECOND, enc->rate);
+ GST_BUFFER_DURATION (outbuf) = enc->timestamp - GST_BUFFER_TIMESTAMP (outbuf);
+
+ return gst_pad_push (enc->srcpad, outbuf);
+}
+
+static GstFlowReturn
+adpcmenc_chain (GstPad * pad, GstBuffer * buf)
+{
+ ADPCMEnc *enc = (ADPCMEnc *) gst_pad_get_parent (pad);
+ GstFlowReturn ret = GST_FLOW_OK;
+ gint16 *samples;
+ GstBuffer *databuf = NULL;
+ int input_bytes_per_block;
+ const int BYTES_PER_SAMPLE = 2;
+
+ if (enc->base_timestamp == GST_CLOCK_TIME_NONE) {
+ enc->base_timestamp = GST_BUFFER_TIMESTAMP (buf);
+ if (enc->base_timestamp == GST_CLOCK_TIME_NONE)
+ enc->base_timestamp = 0;
+ enc->timestamp = enc->base_timestamp;
+ }
+
+ gst_adapter_push (enc->adapter, buf);
+
+ input_bytes_per_block =
+ enc->samples_per_block * BYTES_PER_SAMPLE * enc->channels;
+
+ while (gst_adapter_available (enc->adapter) >= input_bytes_per_block) {
+ databuf = gst_adapter_take_buffer (enc->adapter, input_bytes_per_block);
+ samples = (gint16 *) GST_BUFFER_DATA (databuf);
+ ret = adpcmenc_encode_block (enc, samples, enc->blocksize);
+ gst_buffer_unref (databuf);
+ if (ret != GST_FLOW_OK)
+ goto done;
+ }
+
+done:
+ gst_object_unref (enc);
+ return ret;
+}
+
+static gboolean
+adpcmenc_sink_event (GstPad * pad, GstEvent * event)
+{
+ ADPCMEnc *enc = (ADPCMEnc *) gst_pad_get_parent (pad);
+ gboolean res;
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_STOP:
+ enc->out_samples = 0;
+ enc->timestamp = GST_CLOCK_TIME_NONE;
+ enc->base_timestamp = GST_CLOCK_TIME_NONE;
+ gst_adapter_clear (enc->adapter);
+ /* Fall through */
+ default:
+ res = gst_pad_push_event (enc->srcpad, event);
+ break;
+ }
+ gst_object_unref (enc);
+ return res;
+}
+
+static GstStateChangeReturn
+adpcmenc_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ ADPCMEnc *enc = (ADPCMEnc *) element;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ adpcmenc_teardown (enc);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void
+adpcmenc_dispose (GObject * obj)
+{
+ G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+static void
+adpcmenc_init (ADPCMEnc * enc, ADPCMEncClass * klass)
+{
+ enc->sinkpad =
+ gst_pad_new_from_static_template (&adpcmenc_sink_template, "sink");
+ gst_pad_set_setcaps_function (enc->sinkpad,
+ GST_DEBUG_FUNCPTR (adpcmenc_sink_setcaps));
+ gst_pad_set_chain_function (enc->sinkpad, GST_DEBUG_FUNCPTR (adpcmenc_chain));
+ gst_pad_set_event_function (enc->sinkpad,
+ GST_DEBUG_FUNCPTR (adpcmenc_sink_event));
+ gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad);
+
+ enc->srcpad =
+ gst_pad_new_from_static_template (&adpcmenc_src_template, "src");
+ gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad);
+
+ /* Set defaults. */
+ enc->blocksize = DEFAULT_ADPCM_BLOCK_SIZE;
+ enc->layout = DEFAULT_ADPCM_LAYOUT;
+}
+
+static void
+adpcmenc_class_init (ADPCMEncClass * klass)
+{
+ GObjectClass *gobjectclass = (GObjectClass *) klass;
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+
+ gobjectclass->set_property = adpcmenc_set_property;
+ gobjectclass->get_property = adpcmenc_get_property;
+
+ g_object_class_install_property (gobjectclass, ARG_LAYOUT,
+ g_param_spec_enum ("layout", "Layout",
+ "Layout for output stream",
+ GST_TYPE_ADPCMENC_LAYOUT, DEFAULT_ADPCM_LAYOUT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobjectclass, ARG_BLOCK_SIZE,
+ g_param_spec_int ("blockalign", "Block Align",
+ "Block size for output stream",
+ MIN_ADPCM_BLOCK_SIZE, MAX_ADPCM_BLOCK_SIZE,
+ DEFAULT_ADPCM_BLOCK_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gobjectclass->dispose = adpcmenc_dispose;
+ gstelement_class->change_state = adpcmenc_change_state;
+} static void
+
+adpcmenc_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&adpcmenc_sink_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&adpcmenc_src_template));
+ gst_element_class_set_details (element_class, &adpcmenc_details);
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (adpcmenc_debug, "adpcmenc", 0, "ADPCM Encoders");
+ if (!gst_element_register (plugin, "adpcmenc", GST_RANK_PRIMARY,
+ GST_TYPE_ADPCM_ENC)) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "adpcmenc",
+ "ADPCM encoder", plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN);