summaryrefslogtreecommitdiff
path: root/ext/sbc
diff options
context:
space:
mode:
authorTim-Philipp Müller <tim@centricular.net>2013-03-26 13:55:32 +0000
committerTim-Philipp Müller <tim@centricular.net>2013-03-27 22:40:56 +0000
commitca9daee444b0711a27e1a1c0ca9aa8d93dc37588 (patch)
tree5204685f6cdcd9a717fa27c17903fda8bb968e15 /ext/sbc
parentfecddde2c2de612bbeca5d3ee7d573fd929b949e (diff)
sbc: port decoder to GstAudioDecoder
https://bugzilla.gnome.org/show_bug.cgi?id=690582
Diffstat (limited to 'ext/sbc')
-rw-r--r--ext/sbc/gstsbcdec.c342
-rw-r--r--ext/sbc/gstsbcdec.h22
-rw-r--r--ext/sbc/sbc-plugin.c2
3 files changed, 150 insertions, 216 deletions
diff --git a/ext/sbc/gstsbcdec.c b/ext/sbc/gstsbcdec.c
index 1176a2cdf7..d1b6af9499 100644
--- a/ext/sbc/gstsbcdec.c
+++ b/ext/sbc/gstsbcdec.c
@@ -1,8 +1,7 @@
/* GStreamer SBC audio decoder
- * BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
- *
+ * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -20,15 +19,26 @@
*
*/
+/**
+ * SECTION:element-sbdec
+ *
+ * This element decodes a Bluetooth SBC audio streams to raw integer PCM audio
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch-1.0 -v filesrc location=audio.sbc ! sbcparse ! sbcdec ! audioconvert ! audioresample ! autoaudiosink
+ * ]| Decode a raw SBC file.
+ * </refsect2>
+ */
+
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
-#include "gstsbcutil.h"
#include "gstsbcdec.h"
-#include <gst/audio/audio.h>
/* FIXME: where does this come from? how is it derived? */
#define BUF_SIZE 8192
@@ -36,15 +46,14 @@
GST_DEBUG_CATEGORY_STATIC (sbc_dec_debug);
#define GST_CAT_DEFAULT sbc_dec_debug
-static void gst_sbc_dec_finalize (GObject * obj);
-
-/* FIXME: port to GstAudioDecoder base class */
#define parent_class gst_sbc_dec_parent_class
-G_DEFINE_TYPE (GstSbcDec, gst_sbc_dec, GST_TYPE_ELEMENT);
+G_DEFINE_TYPE (GstSbcDec, gst_sbc_dec, GST_TYPE_AUDIO_DECODER);
static GstStaticPadTemplate sbc_dec_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("audio/x-sbc"));
+ GST_STATIC_CAPS ("audio/x-sbc, channels = (int) [ 1, 2 ], "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "parsed = (boolean) true"));
static GstStaticPadTemplate sbc_dec_src_factory =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
@@ -53,215 +62,165 @@ GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
"channels = (int) [ 1, 2 ], layout=interleaved"));
static GstFlowReturn
-gst_sbc_dec_flush (GstSbcDec * dec, GstBuffer * outbuf,
- gint outoffset, gint channels, gint rate)
-{
- GstClockTime outtime, duration;
-
- /* we will reuse the same caps object */
- if (dec->send_caps) {
- GstCaps *caps;
-
- caps = gst_caps_new_simple ("audio/x-raw",
- "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
- "rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, channels,
- "layout", G_TYPE_STRING, "interleaved", NULL);
-
- gst_pad_push_event (dec->srcpad, gst_event_new_caps (caps));
-
- gst_caps_unref (caps);
- }
-
- /* calculate duration */
- outtime = GST_BUFFER_TIMESTAMP (outbuf);
- if (dec->next_timestamp != (guint64) - 1 && outtime != (guint64) - 1) {
- duration = dec->next_timestamp - outtime;
- } else if (outtime != (guint64) - 1) {
- /* otherwise calculate duration based on outbuf size */
- duration = gst_util_uint64_scale_int (outoffset / (2 * channels),
- GST_SECOND, rate) - outtime;
- } else {
- duration = GST_CLOCK_TIME_NONE;
- }
- GST_BUFFER_DURATION (outbuf) = duration;
- gst_buffer_resize (outbuf, 0, outoffset);
-
- return gst_pad_push (dec->srcpad, outbuf);
-
-}
-
-static GstFlowReturn
-sbc_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
+gst_sbc_dec_handle_frame (GstAudioDecoder * audio_dec, GstBuffer * buf)
{
- GstSbcDec *dec = GST_SBC_DEC (parent);
- GstFlowReturn res = GST_FLOW_OK;
- const guint8 *indata;
- guint insize;
- GstClockTime timestamp;
- gboolean discont;
+ GstSbcDec *dec = GST_SBC_DEC (audio_dec);
+ GstBuffer *outbuf = NULL;
GstMapInfo out_map;
- GstBuffer *outbuf;
- guint inoffset, outoffset;
- gint rate, channels;
-
- discont = GST_BUFFER_IS_DISCONT (buffer);
- if (discont) {
- /* reset previous buffer */
- gst_adapter_clear (dec->adapter);
- /* we need a new timestamp to lock onto */
- dec->next_sample = -1;
- }
-
- gst_adapter_push (dec->adapter, buffer);
+ GstMapInfo in_map;
+ gsize output_size;
+ guint num_frames, i;
- timestamp = GST_BUFFER_TIMESTAMP (buffer);
- if (GST_CLOCK_TIME_IS_VALID (timestamp))
- dec->next_timestamp = timestamp;
+ /* no fancy draining */
+ if (G_UNLIKELY (buf == NULL))
+ return GST_FLOW_OK;
- insize = gst_adapter_available (dec->adapter);
- indata = gst_adapter_map (dec->adapter, insize);
+ if (G_UNLIKELY (dec->frame_len == 0))
+ return GST_FLOW_NOT_NEGOTIATED;
- inoffset = 0;
- outbuf = NULL;
- channels = rate = 0;
+ gst_buffer_map (buf, &in_map, GST_MAP_READ);
- while (insize > 0) {
- gint inconsumed, outlen;
- gint outsize;
- size_t outconsumed;
+ if (G_UNLIKELY (in_map.size == 0))
+ goto done;
- if (outbuf == NULL) {
- outbuf = gst_buffer_new_and_alloc (BUF_SIZE);
+ /* we assume all frames are of the same size, this is implied by the
+ * input caps applying to the whole input buffer, and the parser should
+ * also have made sure of that */
+ if (G_UNLIKELY (in_map.size % dec->frame_len != 0))
+ goto mixed_frames;
- if (discont) {
- GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
- discont = FALSE;
- }
+ num_frames = in_map.size / dec->frame_len;
+ output_size = num_frames * dec->samples_per_frame * sizeof (gint16);
- GST_BUFFER_TIMESTAMP (outbuf) = dec->next_timestamp;
+ outbuf = gst_audio_decoder_allocate_output_buffer (audio_dec, output_size);
- gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE);
- outsize = out_map.size;
- outoffset = 0;
- }
+ if (outbuf == NULL)
+ goto no_buffer;
- GST_INFO_OBJECT (dec, "inoffset %d/%d, outoffset %d/%d", inoffset,
- insize, outoffset, outsize);
+ gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE);
- inconsumed = sbc_decode (&dec->sbc, indata + inoffset, insize,
- out_map.data + outoffset, outsize, &outconsumed);
+ for (i = 0; i < num_frames; ++i) {
+ gssize ret;
+ gsize written;
- GST_INFO_OBJECT (dec, "consumed %d, produced %d", inconsumed, outconsumed);
+ ret = sbc_decode (&dec->sbc, in_map.data + (i * dec->frame_len),
+ dec->frame_len, out_map.data + (i * dec->samples_per_frame * 2),
+ dec->samples_per_frame * 2, &written);
- if (inconsumed <= 0) {
- guint frame_len = sbc_get_frame_length (&dec->sbc);
- /* skip a frame */
- if (insize > frame_len) {
- insize -= frame_len;
- inoffset += frame_len;
- } else {
- insize = 0;
- }
- continue;
+ if (ret <= 0 || written != (dec->samples_per_frame * 2)) {
+ GST_WARNING_OBJECT (dec, "decoding error, ret = %" G_GSSIZE_FORMAT ", "
+ "written = %" G_GSSIZE_FORMAT, ret, written);
+ break;
}
+ }
- inoffset += inconsumed;
- if ((gint) insize > inconsumed)
- insize -= inconsumed;
- else
- insize = 0;
- outoffset += outconsumed;
- outsize -= outconsumed;
-
- rate = gst_sbc_parse_rate_from_sbc (dec->sbc.frequency);
- channels = gst_sbc_get_channel_number (dec->sbc.mode);
-
- /* calculate timestamp either from the incomming buffers or
- * from our sample counter */
- if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
- /* lock onto timestamp when we have one */
- dec->next_sample = gst_util_uint64_scale_int (timestamp,
- rate, GST_SECOND);
- timestamp = GST_CLOCK_TIME_NONE;
- }
- if (dec->next_sample != (guint64) - 1) {
- /* calculate the next sample */
- dec->next_sample += outconsumed / (2 * channels);
- dec->next_timestamp = gst_util_uint64_scale_int (dec->next_sample,
- GST_SECOND, rate);
- }
+ gst_buffer_unmap (outbuf, &out_map);
- /* check for space, push outbuf buffer */
- outlen = sbc_get_codesize (&dec->sbc);
- if (outsize < outlen) {
- gst_buffer_unmap (outbuf, &out_map);
+ if (i > 0)
+ gst_buffer_set_size (outbuf, i * dec->samples_per_frame * 2);
+ else
+ gst_buffer_replace (&outbuf, NULL);
- res = gst_sbc_dec_flush (dec, outbuf, outoffset, channels, rate);
+done:
- outbuf = NULL;
+ gst_buffer_unmap (buf, &in_map);
- if (res != GST_FLOW_OK)
- goto done;
- }
- }
+ return gst_audio_decoder_finish_frame (audio_dec, outbuf, 1);
- if (outbuf) {
- gst_buffer_unmap (outbuf, &out_map);
+/* ERRORS */
+mixed_frames:
+ {
+ GST_WARNING_OBJECT (dec, "inconsistent input data/frames, skipping");
+ goto done;
+ }
+no_buffer:
+ {
+ GST_ERROR_OBJECT (dec, "could not allocate output buffer");
+ goto done;
+ }
+}
- res = gst_sbc_dec_flush (dec, outbuf, outoffset, channels, rate);
+static gboolean
+gst_sbc_dec_set_format (GstAudioDecoder * audio_dec, GstCaps * caps)
+{
+ GstSbcDec *dec = GST_SBC_DEC (audio_dec);
+ const gchar *channel_mode;
+ GstAudioInfo info;
+ GstStructure *s;
+ gint channels, rate, subbands, blocks, bitpool;
+
+ s = gst_caps_get_structure (caps, 0);
+ gst_structure_get_int (s, "channels", &channels);
+ gst_structure_get_int (s, "rate", &rate);
+
+ /* save input format */
+ channel_mode = gst_structure_get_string (s, "channel-mode");
+ if (channel_mode == NULL ||
+ !gst_structure_get_int (s, "subbands", &subbands) ||
+ !gst_structure_get_int (s, "blocks", &blocks) ||
+ !gst_structure_get_int (s, "bitpool", &bitpool))
+ return FALSE;
+
+ if (strcmp (channel_mode, "mono") == 0) {
+ dec->frame_len = 4 + (subbands * 1) / 2 + (blocks * 1 * bitpool) / 8;
+ } else if (strcmp (channel_mode, "dual") == 0) {
+ dec->frame_len = 4 + (subbands * 2) / 2 + (blocks * 2 * bitpool) / 8;
+ } else if (strcmp (channel_mode, "stereo") == 0) {
+ dec->frame_len = 4 + (subbands * 2) / 2 + (blocks * bitpool) / 8;
+ } else if (strcmp (channel_mode, "joint") == 0) {
+ dec->frame_len = 4 + (subbands * 2) / 2 + (subbands + blocks * bitpool) / 8;
+ } else {
+ return FALSE;
}
-done:
+ dec->samples_per_frame = channels * blocks * subbands;
- gst_adapter_unmap (dec->adapter);
- gst_adapter_flush (dec->adapter, inoffset);
+ GST_INFO_OBJECT (dec, "frame len: %" G_GSIZE_FORMAT ", samples per frame "
+ "%" G_GSIZE_FORMAT, dec->frame_len, dec->samples_per_frame);
- return res;
+ /* set up output format */
+ gst_audio_info_init (&info);
+ gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, rate, channels, NULL);
+ gst_audio_decoder_set_output_format (audio_dec, &info);
+
+ return TRUE;
}
-static GstStateChangeReturn
-gst_sbc_dec_change_state (GstElement * element, GstStateChange transition)
+static gboolean
+gst_sbc_dec_start (GstAudioDecoder * dec)
{
- GstStateChangeReturn result;
- GstSbcDec *dec = GST_SBC_DEC (element);
-
- switch (transition) {
- case GST_STATE_CHANGE_READY_TO_PAUSED:
- GST_DEBUG ("Setup subband codec");
- sbc_init (&dec->sbc, 0);
- dec->send_caps = TRUE;
- dec->next_sample = -1;
- break;
- default:
- break;
- }
+ GstSbcDec *sbcdec = GST_SBC_DEC (dec);
- result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ GST_INFO_OBJECT (dec, "Setup subband codec");
+ sbc_init (&sbcdec->sbc, 0);
- switch (transition) {
- case GST_STATE_CHANGE_PAUSED_TO_READY:
- GST_DEBUG ("Finish subband codec");
- gst_adapter_clear (dec->adapter);
- sbc_finish (&dec->sbc);
- dec->send_caps = TRUE;
- break;
+ return TRUE;
+}
- default:
- break;
- }
+static gboolean
+gst_sbc_dec_stop (GstAudioDecoder * dec)
+{
+ GstSbcDec *sbcdec = GST_SBC_DEC (dec);
- return result;
+ GST_INFO_OBJECT (sbcdec, "Finish subband codec");
+ sbc_finish (&sbcdec->sbc);
+ sbcdec->samples_per_frame = 0;
+ sbcdec->frame_len = 0;
+
+ return TRUE;
}
static void
gst_sbc_dec_class_init (GstSbcDecClass * klass)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
-
- object_class->finalize = gst_sbc_dec_finalize;
+ GstAudioDecoderClass *audio_decoder_class = (GstAudioDecoderClass *) klass;
+ GstElementClass *element_class = (GstElementClass *) klass;
- element_class->change_state = GST_DEBUG_FUNCPTR (gst_sbc_dec_change_state);
+ audio_decoder_class->start = GST_DEBUG_FUNCPTR (gst_sbc_dec_start);
+ audio_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_sbc_dec_stop);
+ audio_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_sbc_dec_set_format);
+ audio_decoder_class->handle_frame =
+ GST_DEBUG_FUNCPTR (gst_sbc_dec_handle_frame);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&sbc_dec_sink_factory));
@@ -277,27 +236,8 @@ gst_sbc_dec_class_init (GstSbcDecClass * klass)
}
static void
-gst_sbc_dec_init (GstSbcDec * self)
-{
- self->sinkpad =
- gst_pad_new_from_static_template (&sbc_dec_sink_factory, "sink");
- gst_pad_set_chain_function (self->sinkpad, GST_DEBUG_FUNCPTR (sbc_dec_chain));
- gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
-
- self->srcpad = gst_pad_new_from_static_template (&sbc_dec_src_factory, "src");
- gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
-
- self->adapter = gst_adapter_new ();
- self->send_caps = TRUE;
-}
-
-static void
-gst_sbc_dec_finalize (GObject * obj)
+gst_sbc_dec_init (GstSbcDec * dec)
{
- GstSbcDec *self = GST_SBC_DEC (obj);
-
- g_object_unref (self->adapter);
- self->adapter = NULL;
-
- G_OBJECT_CLASS (parent_class)->finalize (obj);
+ dec->samples_per_frame = 0;
+ dec->frame_len = 0;
}
diff --git a/ext/sbc/gstsbcdec.h b/ext/sbc/gstsbcdec.h
index c7c43eea11..10a15ba5c1 100644
--- a/ext/sbc/gstsbcdec.h
+++ b/ext/sbc/gstsbcdec.h
@@ -1,8 +1,7 @@
/* GStreamer SBC audio decoder
- * BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
- *
+ * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,7 +20,7 @@
*/
#include <gst/gst.h>
-#include <gst/base/gstadapter.h>
+#include <gst/audio/audio.h>
#include <sbc/sbc.h>
@@ -42,22 +41,17 @@ typedef struct _GstSbcDec GstSbcDec;
typedef struct _GstSbcDecClass GstSbcDecClass;
struct _GstSbcDec {
- GstElement element;
-
- GstPad *sinkpad;
- GstPad *srcpad;
-
- GstAdapter *adapter;
+ GstAudioDecoder audio_decoder;
- gboolean send_caps;
+ /*< private >*/
+ sbc_t sbc;
- sbc_t sbc;
- guint64 next_sample;
- guint64 next_timestamp;
+ gsize frame_len;
+ gsize samples_per_frame; /* for all channels */
};
struct _GstSbcDecClass {
- GstElementClass parent_class;
+ GstAudioDecoderClass audio_decoder_class;
};
GType gst_sbc_dec_get_type (void);
diff --git a/ext/sbc/sbc-plugin.c b/ext/sbc/sbc-plugin.c
index 7020753ddf..08dd38736b 100644
--- a/ext/sbc/sbc-plugin.c
+++ b/ext/sbc/sbc-plugin.c
@@ -29,7 +29,7 @@
static gboolean
plugin_init (GstPlugin * plugin)
{
- gst_element_register (plugin, "sbcdec", GST_RANK_NONE, GST_TYPE_SBC_DEC);
+ gst_element_register (plugin, "sbcdec", GST_RANK_PRIMARY, GST_TYPE_SBC_DEC);
gst_element_register (plugin, "sbcenc", GST_RANK_NONE, GST_TYPE_SBC_ENC);
return TRUE;
}