summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuiz Augusto von Dentz <luiz.dentz@openbossa.org>2008-01-23 13:14:02 +0000
committerLuiz Augusto von Dentz <luiz.dentz@openbossa.org>2008-01-23 13:14:02 +0000
commita0af7ee44534dad8f35a4142c6a22177e54ffc57 (patch)
treec6a475d035e40f617a213a9ad5327a7eb5d01388
parent1cacae6dd9f44d0e403aa29e45eb3d20e7127f68 (diff)
Make a2dpsink to act like a bin and split the payloader.
-rw-r--r--audio/Makefile.am4
-rw-r--r--audio/gsta2dpsendersink.c1013
-rw-r--r--audio/gsta2dpsendersink.h90
-rw-r--r--audio/gsta2dpsink.c1042
-rw-r--r--audio/gsta2dpsink.h29
-rw-r--r--audio/gstbluetooth.c20
-rw-r--r--audio/gstrtpsbcpay.c337
-rw-r--r--audio/gstrtpsbcpay.h65
-rw-r--r--audio/gstsbcdec.c17
-rw-r--r--audio/gstsbcdec.h2
-rw-r--r--audio/gstsbcenc.c131
-rw-r--r--audio/gstsbcenc.h2
-rw-r--r--audio/gstsbcparse.c223
-rw-r--r--audio/gstsbcparse.h5
-rw-r--r--audio/gstsbcutil.c100
-rw-r--r--audio/gstsbcutil.h19
16 files changed, 2037 insertions, 1062 deletions
diff --git a/audio/Makefile.am b/audio/Makefile.am
index f9284b05..5d838c85 100644
--- a/audio/Makefile.am
+++ b/audio/Makefile.am
@@ -43,11 +43,13 @@ libgstbluetooth_la_SOURCES = gstbluetooth.c \
gstsbcenc.h gstsbcenc.c \
gstsbcdec.h gstsbcdec.c \
gstsbcparse.h gstsbcparse.c \
+ gsta2dpsendersink.h gsta2dpsendersink.c \
gsta2dpsink.h gsta2dpsink.c \
gstsbcutil.h gstsbcutil.c \
+ gstrtpsbcpay.h gstrtpsbcpay.c \
rtp.h ipc.h ipc.c
libgstbluetooth_la_LDFLAGS = -module -avoid-version -export-symbols-regex gst_plugin_desc
-libgstbluetooth_la_LIBADD = @SBC_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10
+libgstbluetooth_la_LIBADD = @SBC_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
libgstbluetooth_la_CFLAGS = @GSTREAMER_CFLAGS@ @SBC_CFLAGS@
endif
endif
diff --git a/audio/gsta2dpsendersink.c b/audio/gsta2dpsendersink.c
new file mode 100644
index 00000000..cfb67b87
--- /dev/null
+++ b/audio/gsta2dpsendersink.c
@@ -0,0 +1,1013 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "ipc.h"
+#include "rtp.h"
+#include "gstsbcutil.h"
+
+#include "gsta2dpsendersink.h"
+
+GST_DEBUG_CATEGORY_STATIC(a2dp_sender_sink_debug);
+#define GST_CAT_DEFAULT a2dp_sender_sink_debug
+
+#define BUFFER_SIZE 2048
+#define TEMPLATE_MAX_BITPOOL 64
+
+#define GST_A2DP_SENDER_SINK_MUTEX_LOCK(s) G_STMT_START { \
+ g_mutex_lock (s->sink_lock); \
+ } G_STMT_END
+
+#define GST_A2DP_SENDER_SINK_MUTEX_UNLOCK(s) G_STMT_START { \
+ g_mutex_unlock (s->sink_lock); \
+ } G_STMT_END
+
+
+struct bluetooth_data {
+ struct bt_getcapabilities_rsp caps; /* Bluetooth device caps */
+ guint link_mtu;
+
+ gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */
+};
+
+#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0)
+#define IS_MPEG(n) (strcmp((n), "audio/mpeg") == 0)
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+};
+
+GST_BOILERPLATE(GstA2dpSenderSink, gst_a2dp_sender_sink, GstBaseSink,
+ GST_TYPE_BASE_SINK);
+
+static const GstElementDetails a2dp_sender_sink_details =
+ GST_ELEMENT_DETAILS("Bluetooth A2DP sink",
+ "Sink/Audio",
+ "Plays audio to an A2DP device",
+ "Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate a2dp_sender_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("application/x-rtp, "
+ "media = (string) \"audio\", "
+ "encoding-name = (string) \"SBC\""
+ ));
+
+static GIOError gst_a2dp_sender_sink_audioservice_send(GstA2dpSenderSink *self,
+ const bt_audio_msg_header_t *msg);
+static GIOError gst_a2dp_sender_sink_audioservice_expect(
+ GstA2dpSenderSink *self,
+ bt_audio_msg_header_t *outmsg,
+ int expected_type);
+
+
+static void gst_a2dp_sender_sink_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(&a2dp_sender_sink_factory));
+
+ gst_element_class_set_details(element_class, &a2dp_sender_sink_details);
+}
+
+static gboolean gst_a2dp_sender_sink_stop(GstBaseSink *basesink)
+{
+ GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink);
+
+ GST_INFO_OBJECT(self, "stop");
+
+ if (self->watch_id != 0) {
+ g_source_remove(self->watch_id);
+ self->watch_id = 0;
+ }
+
+ if (self->server) {
+ bt_audio_service_close(g_io_channel_unix_get_fd(self->server));
+ g_io_channel_unref(self->server);
+ self->server = NULL;
+ }
+
+ if (self->stream) {
+ g_io_channel_flush(self->stream, NULL);
+ g_io_channel_close(self->stream);
+ g_io_channel_unref(self->stream);
+ self->stream = NULL;
+ }
+
+ if (self->data) {
+ g_free(self->data);
+ self->data = NULL;
+ }
+
+ if (self->stream_caps) {
+ gst_caps_unref(self->stream_caps);
+ self->stream_caps = NULL;
+ }
+
+ if (self->dev_caps) {
+ gst_caps_unref(self->dev_caps);
+ self->dev_caps = NULL;
+ }
+
+ return TRUE;
+}
+
+static void gst_a2dp_sender_sink_finalize(GObject *object)
+{
+ GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(object);
+
+ if (self->data)
+ gst_a2dp_sender_sink_stop(GST_BASE_SINK(self));
+
+ if (self->device)
+ g_free(self->device);
+
+ g_mutex_free(self->sink_lock);
+
+ G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_a2dp_sender_sink_set_property(GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GstA2dpSenderSink *sink = GST_A2DP_SENDER_SINK(object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ if (sink->device)
+ g_free(sink->device);
+ sink->device = g_value_dup_string(value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_a2dp_sender_sink_get_property(GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GstA2dpSenderSink *sink = GST_A2DP_SENDER_SINK(object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ g_value_set_string(value, sink->device);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static gint gst_a2dp_sender_sink_bluetooth_recvmsg_fd(GstA2dpSenderSink *sink)
+{
+ int err, ret;
+
+ ret = bt_audio_service_get_data_fd(
+ g_io_channel_unix_get_fd(sink->server));
+
+ if (ret < 0) {
+ err = errno;
+ GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+
+ sink->stream = g_io_channel_unix_new(ret);
+ GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
+
+ return 0;
+}
+
+static gboolean gst_a2dp_sender_sink_init_pkt_conf(GstA2dpSenderSink *sink,
+ GstCaps *caps,
+ sbc_capabilities_t *pkt)
+{
+ sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities;
+ const GValue *value = NULL;
+ const char *pref, *name;
+ gint rate, subbands, blocks;
+ GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+ name = gst_structure_get_name(structure);
+ /* FIXME only sbc supported here, should suport mp3 */
+ if (!(IS_SBC(name))) {
+ GST_ERROR_OBJECT(sink, "Unsupported format %s", name);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "rate");
+ rate = g_value_get_int(value);
+ if (rate == 44100)
+ cfg->frequency = BT_A2DP_SAMPLING_FREQ_44100;
+ else if (rate == 48000)
+ cfg->frequency = BT_A2DP_SAMPLING_FREQ_48000;
+ else if (rate == 32000)
+ cfg->frequency = BT_A2DP_SAMPLING_FREQ_32000;
+ else if (rate == 16000)
+ cfg->frequency = BT_A2DP_SAMPLING_FREQ_16000;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid rate while setting caps");
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "mode");
+ pref = g_value_get_string(value);
+ if (strcmp(pref, "auto") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_AUTO;
+ else if (strcmp(pref, "mono") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+ else if (strcmp(pref, "dual") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (strcmp(pref, "stereo") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+ else if (strcmp(pref, "joint") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid mode %s", pref);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "allocation");
+ pref = g_value_get_string(value);
+ if (strcmp(pref, "auto") == 0)
+ cfg->allocation_method = BT_A2DP_ALLOCATION_AUTO;
+ else if (strcmp(pref, "loudness") == 0)
+ cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+ else if (strcmp(pref, "snr") == 0)
+ cfg->allocation_method = BT_A2DP_ALLOCATION_SNR;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "subbands");
+ subbands = g_value_get_int(value);
+ if (subbands == 8)
+ cfg->subbands = BT_A2DP_SUBBANDS_8;
+ else if (subbands == 4)
+ cfg->subbands = BT_A2DP_SUBBANDS_4;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "blocks");
+ blocks = g_value_get_int(value);
+ if (blocks == 16)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_16;
+ else if (blocks == 12)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_12;
+ else if (blocks == 8)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_8;
+ else if (blocks == 4)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_4;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "bitpool");
+ cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value);
+
+ memcpy(pkt, cfg, sizeof(*pkt));
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sender_sink_conf_recv_stream_fd(
+ GstA2dpSenderSink *self)
+{
+ struct bluetooth_data *data = self->data;
+ gint ret;
+ GIOError err;
+ GError *gerr = NULL;
+ GIOStatus status;
+ GIOFlags flags;
+ gsize read;
+
+ ret = gst_a2dp_sender_sink_bluetooth_recvmsg_fd(self);
+ if (ret < 0)
+ return FALSE;
+
+ if (!self->stream) {
+ GST_ERROR_OBJECT(self, "Error while configuring device: "
+ "could not acquire audio socket");
+ return FALSE;
+ }
+
+ /* set stream socket to nonblock */
+ GST_LOG_OBJECT(self, "setting stream socket to nonblock");
+ flags = g_io_channel_get_flags(self->stream);
+ flags |= G_IO_FLAG_NONBLOCK;
+ status = g_io_channel_set_flags(self->stream, flags, &gerr);
+ if (status != G_IO_STATUS_NORMAL) {
+ if (gerr)
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server socket to nonblock: "
+ "%s", gerr->message);
+ else
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server "
+ "socket to nonblock");
+ }
+
+ /* It is possible there is some outstanding
+ data in the pipe - we have to empty it */
+ GST_LOG_OBJECT(self, "emptying stream pipe");
+ while (1) {
+ err = g_io_channel_read(self->stream, data->buffer,
+ (gsize) data->link_mtu,
+ &read);
+ if (err != G_IO_ERROR_NONE || read <= 0)
+ break;
+ }
+
+ /* set stream socket to block */
+ GST_LOG_OBJECT(self, "setting stream socket to block");
+ flags = g_io_channel_get_flags(self->stream);
+ flags &= ~G_IO_FLAG_NONBLOCK;
+ status = g_io_channel_set_flags(self->stream, flags, &gerr);
+ if (status != G_IO_STATUS_NORMAL) {
+ if (gerr)
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server socket to block:"
+ "%s", gerr->message);
+ else
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server "
+ "socket to block");
+ }
+
+ memset(data->buffer, 0, sizeof(data->buffer));
+
+ return TRUE;
+}
+
+static gboolean server_callback(GIOChannel *chan,
+ GIOCondition cond, gpointer data)
+{
+ GstA2dpSenderSink *sink;
+
+ if (cond & G_IO_HUP || cond & G_IO_NVAL)
+ return FALSE;
+ else if (cond & G_IO_ERR) {
+ sink = GST_A2DP_SENDER_SINK(data);
+ GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR");
+ }
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sender_sink_update_caps(GstA2dpSenderSink *self)
+{
+ sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities;
+ GstStructure *structure;
+ GValue *value;
+ GValue *list;
+ gchar *tmp;
+ gboolean mono, stereo;
+
+ GST_LOG_OBJECT(self, "updating device caps");
+
+ structure = gst_structure_empty_new("audio/x-sbc");
+ value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
+
+ /* mode */
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) {
+ g_value_set_static_string(value, "joint");
+ gst_value_list_prepend_value(list, value);
+ g_value_set_static_string(value, "stereo");
+ gst_value_list_prepend_value(list, value);
+ g_value_set_static_string(value, "mono");
+ gst_value_list_prepend_value(list, value);
+ g_value_set_static_string(value, "dual");
+ gst_value_list_prepend_value(list, value);
+ } else {
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+ g_value_set_static_string(value, "mono");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
+ g_value_set_static_string(value, "stereo");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
+ g_value_set_static_string(value, "dual");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
+ g_value_set_static_string(value, "joint");
+ gst_value_list_prepend_value(list, value);
+ }
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "mode", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* subbands */
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ value = g_value_init(value, G_TYPE_INT);
+ if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
+ g_value_set_int(value, 4);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
+ g_value_set_int(value, 8);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "subbands", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* blocks */
+ value = g_value_init(value, G_TYPE_INT);
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
+ g_value_set_int(value, 16);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
+ g_value_set_int(value, 12);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
+ g_value_set_int(value, 8);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
+ g_value_set_int(value, 4);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "blocks", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* allocation */
+ g_value_init(value, G_TYPE_STRING);
+ list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
+ if (sbc->allocation_method == BT_A2DP_ALLOCATION_AUTO) {
+ g_value_set_static_string(value, "loudness");
+ gst_value_list_prepend_value(list, value);
+ g_value_set_static_string(value, "snr");
+ gst_value_list_prepend_value(list, value);
+ } else {
+ if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
+ g_value_set_static_string(value, "loudness");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
+ g_value_set_static_string(value, "snr");
+ gst_value_list_prepend_value(list, value);
+ }
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "allocation", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* rate */
+ g_value_init(value, G_TYPE_INT);
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_48000) {
+ g_value_set_int(value, 48000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_44100) {
+ g_value_set_int(value, 44100);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_32000) {
+ g_value_set_int(value, 32000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_16000) {
+ g_value_set_int(value, 16000);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "rate", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* bitpool */
+ value = g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value,
+ MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL),
+ MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL));
+ gst_structure_set_value(structure, "bitpool", value);
+ g_value_unset(value);
+
+ /* channels */
+ if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) {
+ g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value, 1, 2);
+ } else {
+ mono = FALSE;
+ stereo = FALSE;
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ mono = TRUE;
+ if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+ (sbc->channel_mode &
+ BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+ (sbc->channel_mode &
+ BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+ stereo = TRUE;
+
+ if (mono && stereo) {
+ g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value, 1, 2);
+ } else {
+ g_value_init(value, G_TYPE_INT);
+ if (mono)
+ g_value_set_int(value, 1);
+ else if (stereo)
+ g_value_set_int(value, 2);
+ else {
+ GST_ERROR_OBJECT(self,
+ "Unexpected number of channels");
+ g_value_set_int(value, 0);
+ }
+ }
+ }
+ gst_structure_set_value(structure, "channels", value);
+ g_free(value);
+
+ if (self->dev_caps != NULL)
+ gst_caps_unref(self->dev_caps);
+ self->dev_caps = gst_caps_new_full(structure, NULL);
+
+ tmp = gst_caps_to_string(self->dev_caps);
+ GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp);
+ g_free(tmp);
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sender_sink_get_capabilities(GstA2dpSenderSink *self)
+{
+ gchar *buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_getcapabilities_req *req = (void *) buf;
+ struct bt_getcapabilities_rsp *rsp = (void *) buf;
+ GIOError io_error;
+
+ memset(req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+
+ req->h.msg_type = BT_GETCAPABILITIES_REQ;
+ if (self->device == NULL)
+ return FALSE;
+ strncpy(req->device, self->device, 18);
+
+ io_error = gst_a2dp_sender_sink_audioservice_send(self, &req->h);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while asking device caps");
+ }
+
+ io_error = gst_a2dp_sender_sink_audioservice_expect(self,
+ &rsp->rsp_h.msg_h, BT_GETCAPABILITIES_RSP);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while getting device caps");
+ return FALSE;
+ }
+
+ if (rsp->rsp_h.posix_errno != 0) {
+ GST_ERROR_OBJECT(self, "BT_GETCAPABILITIES failed : %s(%d)",
+ strerror(rsp->rsp_h.posix_errno),
+ rsp->rsp_h.posix_errno);
+ return FALSE;
+ }
+
+ memcpy(&self->data->caps, rsp, sizeof(*rsp));
+ if (!gst_a2dp_sender_sink_update_caps(self)) {
+ GST_WARNING_OBJECT(self, "failed to update capabilities");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sender_sink_start(GstBaseSink *basesink)
+{
+ GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink);
+ gint sk;
+ gint err;
+
+ GST_INFO_OBJECT(self, "start");
+
+ self->watch_id = 0;
+
+ sk = bt_audio_service_open();
+ if (sk <= 0) {
+ err = errno;
+ GST_ERROR_OBJECT(self, "Cannot open connection to bt "
+ "audio service: %s %d", strerror(err), err);
+ goto failed;
+ }
+
+ self->server = g_io_channel_unix_new(sk);
+ self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
+ G_IO_NVAL, server_callback, self);
+
+ self->data = g_new0(struct bluetooth_data, 1);
+ memset(self->data, 0, sizeof(struct bluetooth_data));
+
+ self->stream = NULL;
+ self->stream_caps = NULL;
+
+ if (!gst_a2dp_sender_sink_get_capabilities(self))
+ goto failed;
+
+ return TRUE;
+
+failed:
+ bt_audio_service_close(sk);
+ return FALSE;
+}
+
+static gboolean gst_a2dp_sender_sink_stream_start(GstA2dpSenderSink *self)
+{
+ gchar buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstart_req *req = (void *) buf;
+ struct bt_streamstart_rsp *rsp = (void *) buf;
+ struct bt_streamfd_ind *ind = (void*) buf;
+ GIOError io_error;
+
+ GST_DEBUG_OBJECT(self, "stream start");
+
+ memset (req, 0, sizeof(buf));
+ req->h.msg_type = BT_STREAMSTART_REQ;
+
+ io_error = gst_a2dp_sender_sink_audioservice_send(self, &req->h);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "start packet");
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT(self, "stream start packet sent");
+
+ io_error = gst_a2dp_sender_sink_audioservice_expect(self,
+ &rsp->rsp_h.msg_h, BT_STREAMSTART_RSP);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while stream "
+ "start confirmation");
+ return FALSE;
+ }
+
+ if (rsp->rsp_h.posix_errno != 0) {
+ GST_ERROR_OBJECT(self, "BT_STREAMSTART_RSP failed : %s(%d)",
+ strerror(rsp->rsp_h.posix_errno),
+ rsp->rsp_h.posix_errno);
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT(self, "stream started");
+
+ io_error = gst_a2dp_sender_sink_audioservice_expect(self, &ind->h,
+ BT_STREAMFD_IND);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while receiving "
+ "stream filedescriptor");
+ return FALSE;
+ }
+
+ if (!gst_a2dp_sender_sink_conf_recv_stream_fd(self))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sender_sink_configure(GstA2dpSenderSink *self,
+ GstCaps *caps)
+{
+ gchar buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_setconfiguration_req *req = (void *) buf;
+ struct bt_setconfiguration_rsp *rsp = (void *) buf;
+ gboolean ret;
+ GIOError io_error;
+ gchar *temp;
+
+ temp = gst_caps_to_string(caps);
+ GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
+ g_free(temp);
+
+ memset (req, 0, sizeof(buf));
+ req->h.msg_type = BT_SETCONFIGURATION_REQ;
+ req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE;
+ strncpy(req->device, self->device, 18);
+ ret = gst_a2dp_sender_sink_init_pkt_conf(self, caps,
+ &req->sbc_capabilities);
+ if (!ret) {
+ GST_ERROR_OBJECT(self, "Couldn't parse caps "
+ "to packet configuration");
+ return FALSE;
+ }
+
+ io_error = gst_a2dp_sender_sink_audioservice_send(self, &req->h);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "configurarion packet");
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT(self, "configuration packet sent");
+
+ io_error = gst_a2dp_sender_sink_audioservice_expect(self,
+ &rsp->rsp_h.msg_h, BT_SETCONFIGURATION_RSP);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while receiving device "
+ "confirmation");
+ return FALSE;
+ }
+
+ if (rsp->rsp_h.posix_errno != 0) {
+ GST_ERROR_OBJECT(self, "BT_SETCONFIGURATION_RSP failed : "
+ "%s(%d)",
+ strerror(rsp->rsp_h.posix_errno),
+ rsp->rsp_h.posix_errno);
+ return FALSE;
+ }
+
+ self->data->link_mtu = rsp->link_mtu;
+ GST_DEBUG_OBJECT(self, "configuration set");
+
+ return TRUE;
+}
+
+static GstFlowReturn gst_a2dp_sender_sink_preroll(GstBaseSink *basesink,
+ GstBuffer *buffer)
+{
+ GstA2dpSenderSink *sink = GST_A2DP_SENDER_SINK(basesink);
+ gboolean ret;
+
+ GST_A2DP_SENDER_SINK_MUTEX_LOCK(sink);
+
+ ret = gst_a2dp_sender_sink_stream_start(sink);
+
+ GST_A2DP_SENDER_SINK_MUTEX_UNLOCK(sink);
+
+ if (!ret)
+ return GST_FLOW_ERROR;
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn gst_a2dp_sender_sink_render(GstBaseSink *basesink,
+ GstBuffer *buffer)
+{
+ GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink);
+ gsize ret;
+ GIOError err;
+
+ err = g_io_channel_write(self->stream, (gchar*)GST_BUFFER_DATA(buffer),
+ (gsize)(GST_BUFFER_SIZE(buffer)), &ret);
+
+ if (err != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s",
+ errno, strerror(errno));
+ return GST_FLOW_ERROR;
+ }
+
+ return GST_FLOW_OK;
+}
+
+static gboolean gst_a2dp_sender_sink_unlock(GstBaseSink *basesink)
+{
+ GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink);
+
+ if (self->stream != NULL)
+ g_io_channel_flush (self->stream, NULL);
+
+ return TRUE;
+}
+
+static GstFlowReturn gst_a2dp_sender_sink_buffer_alloc(GstBaseSink *basesink,
+ guint64 offset, guint size, GstCaps* caps,
+ GstBuffer **buf)
+{
+ GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink);
+
+ *buf = gst_buffer_new_and_alloc(size);
+ if (!(*buf)) {
+ GST_ERROR_OBJECT(self, "buffer allocation failed");
+ return GST_FLOW_ERROR;
+ }
+
+ gst_buffer_set_caps(*buf, caps);
+
+ GST_BUFFER_OFFSET(*buf) = offset;
+
+ return GST_FLOW_OK;
+}
+
+static void gst_a2dp_sender_sink_class_init(GstA2dpSenderSinkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ object_class->finalize = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sender_sink_finalize);
+ object_class->set_property = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sender_sink_set_property);
+ object_class->get_property = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sender_sink_get_property);
+
+ basesink_class->start = GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_start);
+ basesink_class->stop = GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_stop);
+ basesink_class->render = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sender_sink_render);
+ basesink_class->preroll = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sender_sink_preroll);
+ basesink_class->unlock = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sender_sink_unlock);
+
+ basesink_class->buffer_alloc =
+ GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_buffer_alloc);
+
+ g_object_class_install_property(object_class, PROP_DEVICE,
+ g_param_spec_string("device", "Device",
+ "Bluetooth remote device address",
+ NULL, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(a2dp_sender_sink_debug, "a2dpsendersink", 0,
+ "A2DP sink element");
+}
+
+static void gst_a2dp_sender_sink_init(GstA2dpSenderSink *self,
+ GstA2dpSenderSinkClass *klass)
+{
+ self->device = NULL;
+ self->data = NULL;
+
+ self->stream = NULL;
+
+ self->dev_caps = NULL;
+
+ self->sink_lock = g_mutex_new();
+}
+
+static GIOError gst_a2dp_sender_sink_audioservice_send(
+ GstA2dpSenderSink *self,
+ const bt_audio_msg_header_t *msg)
+{
+ GIOError error;
+ gsize written;
+
+ error = g_io_channel_write(self->server, (const gchar*) msg,
+ BT_AUDIO_IPC_PACKET_SIZE, &written);
+ if (error != G_IO_ERROR_NONE)
+ GST_ERROR_OBJECT(self, "Error sending data to audio service:"
+ " %s(%d)", strerror(errno), errno);
+
+ return error;
+}
+
+static GIOError gst_a2dp_sender_sink_audioservice_recv(
+ GstA2dpSenderSink *self,
+ bt_audio_msg_header_t *inmsg)
+{
+ GIOError status;
+ gsize bytes_read;
+ const char *type;
+
+ status = g_io_channel_read(self->server, (gchar*) inmsg,
+ BT_AUDIO_IPC_PACKET_SIZE, &bytes_read);
+ if (status != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error receiving data from "
+ "audio service");
+ return status;
+ }
+
+ type = bt_audio_strmsg(inmsg->msg_type);
+ if (!type) {
+ status = G_IO_ERROR_INVAL;
+ GST_ERROR_OBJECT(self, "Bogus message type %d "
+ "received from audio service",
+ inmsg->msg_type);
+ }
+
+ return status;
+}
+
+static GIOError gst_a2dp_sender_sink_audioservice_expect(
+ GstA2dpSenderSink *self, bt_audio_msg_header_t *outmsg,
+ int expected_type)
+{
+ GIOError status;
+
+ status = gst_a2dp_sender_sink_audioservice_recv(self, outmsg);
+ if (status != G_IO_ERROR_NONE)
+ return status;
+
+ if (outmsg->msg_type != expected_type)
+ status = G_IO_ERROR_INVAL;
+
+ return status;
+}
+
+gboolean gst_a2dp_sender_sink_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "a2dpsendersink",
+ GST_RANK_NONE, GST_TYPE_A2DP_SENDER_SINK);
+}
+
+
+/* public functions */
+GstCaps *gst_a2dp_sender_sink_get_device_caps(GstA2dpSenderSink *sink)
+{
+ if (sink->dev_caps == NULL)
+ return NULL;
+
+ return gst_caps_copy(sink->dev_caps);
+}
+
+gboolean gst_a2dp_sender_sink_set_device_caps(GstA2dpSenderSink *self,
+ GstCaps *caps)
+{
+ gboolean ret;
+
+ GST_DEBUG_OBJECT(self, "setting device caps");
+ GST_A2DP_SENDER_SINK_MUTEX_LOCK(self);
+ ret = gst_a2dp_sender_sink_configure(self, caps);
+
+ if (self->stream_caps)
+ gst_caps_unref(self->stream_caps);
+ self->stream_caps = gst_caps_ref(caps);
+
+ GST_A2DP_SENDER_SINK_MUTEX_UNLOCK(self);
+
+ return ret;
+}
+
+guint gst_a2dp_sender_sink_get_link_mtu(GstA2dpSenderSink *sink)
+{
+ return sink->data->link_mtu;
+}
+
+void gst_a2dp_sender_sink_set_device(GstA2dpSenderSink *self, const gchar* dev)
+{
+ if (self->device != NULL)
+ g_free(self->device);
+
+ GST_LOG_OBJECT(self, "Setting device: %s", dev);
+ self->device = g_strdup(dev);
+}
+
+gchar *gst_a2dp_sender_sink_get_device(GstA2dpSenderSink *self)
+{
+ return g_strdup(self->device);
+}
+
diff --git a/audio/gsta2dpsendersink.h b/audio/gsta2dpsendersink.h
new file mode 100644
index 00000000..863ef6cb
--- /dev/null
+++ b/audio/gsta2dpsendersink.h
@@ -0,0 +1,90 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __GST_A2DP_SENDER_SINK_H
+#define __GST_A2DP_SENDER_SINK_H
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_A2DP_SENDER_SINK \
+ (gst_a2dp_sender_sink_get_type())
+#define GST_A2DP_SENDER_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_A2DP_SENDER_SINK,\
+ GstA2dpSenderSink))
+#define GST_A2DP_SENDER_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_A2DP_SENDER_SINK,\
+ GstA2dpSenderSinkClass))
+#define GST_IS_A2DP_SENDER_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_A2DP_SENDER_SINK))
+#define GST_IS_A2DP_SENDER_SINK_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SENDER_SINK))
+
+typedef struct _GstA2dpSenderSink GstA2dpSenderSink;
+typedef struct _GstA2dpSenderSinkClass GstA2dpSenderSinkClass;
+
+struct bluetooth_data;
+
+struct _GstA2dpSenderSink {
+ GstBaseSink sink;
+
+ gchar *device;
+ GIOChannel *stream;
+
+ struct bluetooth_data *data;
+ GIOChannel *server;
+
+ /* stream connection data */
+ GstCaps *stream_caps;
+
+ GstCaps *dev_caps;
+
+ GMutex *sink_lock;
+
+ guint watch_id;
+};
+
+struct _GstA2dpSenderSinkClass {
+ GstBaseSinkClass parent_class;
+};
+
+GType gst_a2dp_sender_sink_get_type(void);
+
+GstCaps *gst_a2dp_sender_sink_get_device_caps(GstA2dpSenderSink *sink);
+gboolean gst_a2dp_sender_sink_set_device_caps(GstA2dpSenderSink *sink,
+ GstCaps *caps);
+
+guint gst_a2dp_sender_sink_get_link_mtu(GstA2dpSenderSink *sink);
+
+void gst_a2dp_sender_sink_set_device(GstA2dpSenderSink *sink,
+ const gchar* device);
+
+gchar *gst_a2dp_sender_sink_get_device(GstA2dpSenderSink *sink);
+
+gboolean gst_a2dp_sender_sink_plugin_init(GstPlugin *plugin);
+
+G_END_DECLS
+
+#endif /* __GST_A2DP_SENDER_SINK_H */
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
index 5798bc24..d90909c7 100644
--- a/audio/gsta2dpsink.c
+++ b/audio/gsta2dpsink.c
@@ -26,166 +26,67 @@
#endif
#include <unistd.h>
-#include <sys/un.h>
-#include <sys/socket.h>
-#include <fcntl.h>
#include <pthread.h>
-#include <netinet/in.h>
-
-#include <bluetooth/bluetooth.h>
-
-#include "ipc.h"
-#include "rtp.h"
#include "gstsbcutil.h"
-
#include "gsta2dpsink.h"
-GST_DEBUG_CATEGORY_STATIC(a2dp_sink_debug);
-#define GST_CAT_DEFAULT a2dp_sink_debug
-
-#define BUFFER_SIZE 2048
-#define TEMPLATE_MAX_BITPOOL_VALUE 64
+GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
+#define GST_CAT_DEFAULT gst_a2dp_sink_debug
-#define GST_A2DP_SINK_MUTEX_LOCK(s) G_STMT_START { \
- g_mutex_lock (s->sink_lock); \
- } G_STMT_END
-
-#define GST_A2DP_SINK_MUTEX_UNLOCK(s) G_STMT_START { \
- g_mutex_unlock (s->sink_lock); \
- } G_STMT_END
-
-
-struct bluetooth_data {
- struct bt_getcapabilities_rsp caps; /* Bluetooth device capabilities */
- struct bt_setconfiguration_rsp cfg; /* Bluetooth device configuration */
- int samples; /* Number of encoded samples */
- gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */
- gsize count; /* Codec transfer buffer counter */
-
- int nsamples; /* Cumulative number of codec samples */
- uint16_t seq_num; /* Cumulative packet sequence */
- int frame_count; /* Current frames in buffer*/
-};
-
-
-#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0)
-#define IS_MPEG(n) (strcmp((n), "audio/mpeg") == 0)
+#define A2DP_SBC_RTP_PAYLOAD_TYPE 1
+#define TEMPLATE_MAX_BITPOOL_STR "64"
enum {
PROP_0,
- PROP_DEVICE,
+ PROP_DEVICE
};
-GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBaseSink, GST_TYPE_BASE_SINK);
+GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
-static const GstElementDetails a2dp_sink_details =
+static const GstElementDetails gst_a2dp_sink_details =
GST_ELEMENT_DETAILS("Bluetooth A2DP sink",
"Sink/Audio",
"Plays audio to an A2DP device",
"Marcel Holtmann <marcel@holtmann.org>");
-static GstStaticPadTemplate a2dp_sink_factory =
+static GstStaticPadTemplate gst_a2dp_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, "
"rate = (int) { 16000, 32000, 44100, 48000 }, "
"channels = (int) [ 1, 2 ], "
"mode = (string) { mono, dual, stereo, joint }, "
"blocks = (int) { 4, 8, 12, 16 }, "
"subbands = (int) { 4, 8 }, "
- "allocation = (string) { snr, loudness },"
- /* FIXME use constant here */
- "bitpool = (int) [ 2, 64 ]; "
- "audio/mpeg, "
- "mpegversion = (int) 1, "
- "layer = (int) [ 1, 3 ], "
- "rate = (int) { 16000, 22050, 24000, 32000, 44100, 48000 }, "
- "channels = (int) [ 1, 2 ]"));
-
-static GIOError gst_a2dp_sink_audioservice_send(GstA2dpSink *self,
- const bt_audio_msg_header_t *msg);
-static GIOError gst_a2dp_sink_audioservice_expect(GstA2dpSink *self,
- bt_audio_msg_header_t *outmsg,
- int expected_type);
-
+ "allocation = (string) { snr, loudness }, "
+ "bitpool = (int) [ 2, "
+ TEMPLATE_MAX_BITPOOL_STR " ]; "
+ ));
static void gst_a2dp_sink_base_init(gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+ gst_element_class_set_details(element_class,
+ &gst_a2dp_sink_details);
gst_element_class_add_pad_template(element_class,
- gst_static_pad_template_get(&a2dp_sink_factory));
-
- gst_element_class_set_details(element_class, &a2dp_sink_details);
-}
-
-static gboolean gst_a2dp_sink_stop(GstBaseSink *basesink)
-{
- GstA2dpSink *self = GST_A2DP_SINK(basesink);
-
- GST_INFO_OBJECT(self, "stop");
-
- if (self->watch_id != 0) {
- g_source_remove(self->watch_id);
- self->watch_id = 0;
- }
-
- if (self->stream) {
- g_io_channel_flush(self->stream, NULL);
- g_io_channel_close(self->stream);
- g_io_channel_unref(self->stream);
- self->stream = NULL;
- }
-
- if (self->server) {
- bt_audio_service_close(g_io_channel_unix_get_fd(self->server));
- g_io_channel_unref(self->server);
- self->server = NULL;
- }
-
- if (self->data) {
- g_free(self->data);
- self->data = NULL;
- }
-
- if (self->stream_caps) {
- gst_caps_unref(self->stream_caps);
- self->stream_caps = NULL;
- }
-
- if (self->dev_caps) {
- gst_caps_unref(self->dev_caps);
- self->dev_caps = NULL;
- }
-
- return TRUE;
-}
-
-static void gst_a2dp_sink_finalize(GObject *object)
-{
- GstA2dpSink *self = GST_A2DP_SINK(object);
-
- if (self->data)
- gst_a2dp_sink_stop(GST_BASE_SINK(self));
-
- if (self->device)
- g_free(self->device);
-
- g_mutex_free(self->sink_lock);
-
- G_OBJECT_CLASS(parent_class)->finalize(object);
+ gst_static_pad_template_get(&gst_a2dp_sink_factory));
}
static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
- GstA2dpSink *sink = GST_A2DP_SINK(object);
+ GstA2dpSink *self = GST_A2DP_SINK(object);
switch (prop_id) {
case PROP_DEVICE:
- if (sink->device)
- g_free(sink->device);
- sink->device = g_value_dup_string(value);
+ if (self->sink != NULL)
+ gst_a2dp_sender_sink_set_device(self->sink,
+ g_value_get_string(value));
+
+ if (self->device != NULL)
+ g_free(self->device);
+ self->device = g_value_dup_string(value);
break;
default:
@@ -197,11 +98,16 @@ static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
- GstA2dpSink *sink = GST_A2DP_SINK(object);
+ GstA2dpSink *self = GST_A2DP_SINK(object);
+ gchar *device;
switch (prop_id) {
case PROP_DEVICE:
- g_value_set_string(value, sink->device);
+ if (self->sink != NULL) {
+ device = gst_a2dp_sender_sink_get_device(self->sink);
+ if (device != NULL)
+ g_value_take_string(value, device);
+ }
break;
default:
@@ -210,775 +116,305 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
}
}
-static gint gst_a2dp_sink_bluetooth_recvmsg_fd(GstA2dpSink *sink)
-{
- int err, ret;
-
- ret = bt_audio_service_get_data_fd(g_io_channel_unix_get_fd(sink->server));
-
- if (ret < 0) {
- err = errno;
- GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)",
- strerror(err), err);
- return -err;
- }
-
- sink->stream = g_io_channel_unix_new(ret);
- GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
-
- return 0;
-}
-
-static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink,
- GstCaps *caps,
- sbc_capabilities_t *pkt)
+static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,
+ GstStateChange transition)
{
- sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities;
- const GValue *value = NULL;
- const char *pref, *name;
- gint rate, blocks, subbands;
- GstStructure *structure = gst_caps_get_structure(caps, 0);
-
- name = gst_structure_get_name(structure);
- /* FIXME only sbc supported here, should suport mp3 */
- if (!(IS_SBC(name))) {
- GST_ERROR_OBJECT(sink, "Unsupported format %s", name);
- return FALSE;
- }
-
- value = gst_structure_get_value(structure, "rate");
- rate = g_value_get_int(value);
- if (rate == 44100)
- cfg->frequency = BT_A2DP_SAMPLING_FREQ_44100;
- else if (rate == 48000)
- cfg->frequency = BT_A2DP_SAMPLING_FREQ_48000;
- else if (rate == 32000)
- cfg->frequency = BT_A2DP_SAMPLING_FREQ_32000;
- else if (rate == 16000)
- cfg->frequency = BT_A2DP_SAMPLING_FREQ_16000;
- else {
- GST_ERROR_OBJECT(sink, "Invalid rate while setting caps");
- return FALSE;
- }
-
- value = gst_structure_get_value(structure, "mode");
- pref = g_value_get_string(value);
- if (strcmp(pref, "auto") == 0)
- cfg->channel_mode = BT_A2DP_CHANNEL_MODE_AUTO;
- else if (strcmp(pref, "mono") == 0)
- cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
- else if (strcmp(pref, "dual") == 0)
- cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
- else if (strcmp(pref, "stereo") == 0)
- cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
- else if (strcmp(pref, "joint") == 0)
- cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
- else {
- GST_ERROR_OBJECT(sink, "Invalid mode %s", pref);
- return FALSE;
- }
+ GstA2dpSink *self = GST_A2DP_SINK(element);
- value = gst_structure_get_value(structure, "allocation");
- pref = g_value_get_string(value);
- if (strcmp(pref, "auto") == 0)
- cfg->allocation_method = BT_A2DP_ALLOCATION_AUTO;
- else if (strcmp(pref, "loudness") == 0)
- cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
- else if (strcmp(pref, "snr") == 0)
- cfg->allocation_method = BT_A2DP_ALLOCATION_SNR;
- else {
- GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref);
- return FALSE;
- }
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ gst_element_set_state(GST_ELEMENT(self->sink),
+ GST_STATE_READY);
+ break;
- value = gst_structure_get_value(structure, "subbands");
- subbands = g_value_get_int(value);
- if (subbands == 8)
- cfg->subbands = BT_A2DP_SUBBANDS_8;
- else if (subbands == 4)
- cfg->subbands = BT_A2DP_SUBBANDS_4;
- else {
- GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands);
- return FALSE;
- }
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (self->newseg_event != NULL) {
+ gst_event_unref(self->newseg_event);
+ self->newseg_event = NULL;
+ }
+ break;
- value = gst_structure_get_value(structure, "blocks");
- blocks = g_value_get_int(value);
- if (blocks == 16)
- cfg->block_length = BT_A2DP_BLOCK_LENGTH_16;
- else if (blocks == 12)
- cfg->block_length = BT_A2DP_BLOCK_LENGTH_12;
- else if (blocks == 8)
- cfg->block_length = BT_A2DP_BLOCK_LENGTH_8;
- else if (blocks == 4)
- cfg->block_length = BT_A2DP_BLOCK_LENGTH_4;
- else {
- GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks);
- return FALSE;
+ default:
+ break;
}
- /* FIXME min and max ??? */
- value = gst_structure_get_value(structure, "bitpool");
-
- cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value);
-
- memcpy(pkt, cfg, sizeof(*pkt));
-
- return TRUE;
+ return GST_ELEMENT_CLASS(parent_class)->change_state(element,
+ transition);
}
-static gboolean gst_a2dp_sink_conf_recv_stream_fd(GstA2dpSink *self)
+static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
{
- struct bluetooth_data *data = self->data;
- gint ret;
- GIOError err;
- GError *gerr = NULL;
- GIOStatus status;
- GIOFlags flags;
- gsize read;
-
- ret = gst_a2dp_sink_bluetooth_recvmsg_fd(self);
- if (ret < 0)
- return FALSE;
-
- if (!self->stream) {
- GST_ERROR_OBJECT(self, "Error while configuring device: "
- "could not acquire audio socket");
- return FALSE;
- }
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
- /* set stream socket to nonblock */
- GST_LOG_OBJECT(self, "setting stream socket to nonblock");
- flags = g_io_channel_get_flags(self->stream);
- flags |= G_IO_FLAG_NONBLOCK;
- status = g_io_channel_set_flags(self->stream, flags, &gerr);
- if (status != G_IO_STATUS_NORMAL) {
- if (gerr)
- GST_WARNING_OBJECT(self, "Error while "
- "setting server socket to nonblock: "
- "%s", gerr->message);
- else
- GST_WARNING_OBJECT(self, "Error while "
- "setting server "
- "socket to nonblock");
- }
+ parent_class = g_type_class_peek_parent(klass);
- /* It is possible there is some outstanding
- data in the pipe - we have to empty it */
- GST_LOG_OBJECT(self, "emptying stream pipe");
- while (1) {
- err = g_io_channel_read(self->stream, data->buffer,
- (gsize) data->cfg.link_mtu,
- &read);
- if (err != G_IO_ERROR_NONE || read <= 0)
- break;
- }
+ object_class->set_property = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_set_property);
+ object_class->get_property = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_get_property);
- /* set stream socket to block */
- GST_LOG_OBJECT(self, "setting stream socket to block");
- flags = g_io_channel_get_flags(self->stream);
- flags &= ~G_IO_FLAG_NONBLOCK;
- status = g_io_channel_set_flags(self->stream, flags, &gerr);
- if (status != G_IO_STATUS_NORMAL) {
- if (gerr)
- GST_WARNING_OBJECT(self, "Error while "
- "setting server socket to block:"
- "%s", gerr->message);
- else
- GST_WARNING_OBJECT(self, "Error while "
- "setting server "
- "socket to block");
- }
+ element_class->change_state = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_change_state);
- memset(data->buffer, 0, sizeof(data->buffer));
+ g_object_class_install_property(object_class, PROP_DEVICE,
+ g_param_spec_string("device", "Device",
+ "Bluetooth remote device address",
+ NULL, G_PARAM_READWRITE));
- return TRUE;
+ GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
+ "A2DP sink element");
}
-static gboolean server_callback(GIOChannel *chan,
- GIOCondition cond, gpointer data)
+static GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self)
{
- GstA2dpSink *sink;
-
- if (cond & G_IO_HUP || cond & G_IO_NVAL)
- return FALSE;
- else if (cond & G_IO_ERR) {
- sink = GST_A2DP_SINK(data);
- GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR");
- }
-
- return TRUE;
+ return gst_a2dp_sender_sink_get_device_caps(self->sink);
}
-static gboolean gst_a2dp_sink_update_caps(GstA2dpSink *self)
+static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad)
{
- sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities;
- GstStructure *structure;
- GValue *value;
- GValue *list;
- gchar *tmp;
-
- GST_LOG_OBJECT(self, "updating device caps");
-
- structure = gst_structure_empty_new("audio/x-sbc");
- value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
-
- /* mode */
- list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
- if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) {
- g_value_set_static_string(value, "joint");
- gst_value_list_prepend_value(list, value);
- g_value_set_static_string(value, "stereo");
- gst_value_list_prepend_value(list, value);
- g_value_set_static_string(value, "mono");
- gst_value_list_prepend_value(list, value);
- g_value_set_static_string(value, "dual");
- gst_value_list_prepend_value(list, value);
- } else {
- if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
- g_value_set_static_string(value, "mono");
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
- g_value_set_static_string(value, "stereo");
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
- g_value_set_static_string(value, "dual");
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
- g_value_set_static_string(value, "joint");
- gst_value_list_prepend_value(list, value);
- }
- }
- g_value_unset(value);
- if (list) {
- gst_structure_set_value(structure, "mode", list);
- g_free(list);
- list = NULL;
- }
-
- /* subbands */
- list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
- value = g_value_init(value, G_TYPE_INT);
- if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
- g_value_set_int(value, 4);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
- g_value_set_int(value, 8);
- gst_value_list_prepend_value(list, value);
- }
- g_value_unset(value);
- if (list) {
- gst_structure_set_value(structure, "subbands", list);
- g_free(list);
- list = NULL;
- }
-
- /* blocks */
- value = g_value_init(value, G_TYPE_INT);
- list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
- if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
- g_value_set_int(value, 16);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
- g_value_set_int(value, 12);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
- g_value_set_int(value, 8);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
- g_value_set_int(value, 4);
- gst_value_list_prepend_value(list, value);
- }
- g_value_unset(value);
- if (list) {
- gst_structure_set_value(structure, "blocks", list);
- g_free(list);
- list = NULL;
- }
-
- /* allocation */
- g_value_init(value, G_TYPE_STRING);
- list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
- if (sbc->allocation_method == BT_A2DP_ALLOCATION_AUTO) {
- g_value_set_static_string(value, "loudness");
- gst_value_list_prepend_value(list, value);
- g_value_set_static_string(value, "snr");
- gst_value_list_prepend_value(list, value);
+ GstCaps *caps;
+ GstCaps *caps_aux;
+ GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+
+ if (self->sink == NULL) {
+ GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized "
+ "returning template caps");
+ caps = gst_static_pad_template_get_caps(
+ &gst_a2dp_sink_factory);
} else {
- if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
- g_value_set_static_string(value, "loudness");
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
- g_value_set_static_string(value, "snr");
- gst_value_list_prepend_value(list, value);
- }
- }
- g_value_unset(value);
- if (list) {
- gst_structure_set_value(structure, "allocation", list);
- g_free(list);
- list = NULL;
- }
-
- /* rate */
- g_value_init(value, G_TYPE_INT);
- list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
- if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_48000) {
- g_value_set_int(value, 48000);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_44100) {
- g_value_set_int(value, 44100);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_32000) {
- g_value_set_int(value, 32000);
- gst_value_list_prepend_value(list, value);
- }
- if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_16000) {
- g_value_set_int(value, 16000);
- gst_value_list_prepend_value(list, value);
- }
- g_value_unset(value);
- if (list) {
- gst_structure_set_value(structure, "rate", list);
- g_free(list);
- list = NULL;
- }
-
- /* bitpool */
- value = g_value_init(value, GST_TYPE_INT_RANGE);
- gst_value_set_int_range(value,
- MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL_VALUE),
- MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL_VALUE));
- gst_structure_set_value(structure, "bitpool", value);
-
- /* channels */
- gst_value_set_int_range(value, 1, 2);
- gst_structure_set_value(structure, "channels", value);
-
- g_free(value);
-
- if (self->dev_caps != NULL)
- gst_caps_unref(self->dev_caps);
- self->dev_caps = gst_caps_new_full(structure, NULL);
-
- tmp = gst_caps_to_string(self->dev_caps);
- GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp);
- g_free(tmp);
-
- return TRUE;
+ GST_LOG_OBJECT(self, "Getting device caps");
+ caps = gst_a2dp_sink_get_device_caps(self);
+ if (caps == NULL)
+ caps = gst_static_pad_template_get_caps(
+ &gst_a2dp_sink_factory);
+ }
+ caps_aux = gst_caps_copy(caps);
+ g_object_set(self->capsfilter, "caps", caps_aux, NULL);
+ gst_caps_unref(caps_aux);
+ return caps;
}
-static gboolean gst_a2dp_sink_get_capabilities(GstA2dpSink *self)
+static gboolean gst_a2dp_sink_init_sender_sink(GstA2dpSink *self)
{
- gchar *buf[BT_AUDIO_IPC_PACKET_SIZE];
- struct bt_getcapabilities_req *req = (void *) buf;
- bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf;
- struct bt_getcapabilities_rsp *rsp = (void *) buf;
- GIOError io_error;
-
- memset(req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ GstElement *sink;
- req->h.msg_type = BT_GETCAPABILITIES_REQ;
- strncpy(req->device, self->device, 18);
-
- io_error = gst_a2dp_sink_audioservice_send(self, &req->h);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error while asking device caps");
- }
+ if (self->sink == NULL)
+ sink = gst_element_factory_make("a2dpsendersink", "sendersink");
+ else
+ sink = GST_ELEMENT(self->sink);
- io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h,
- BT_GETCAPABILITIES_RSP);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error while getting device caps");
+ if (sink == NULL) {
+ GST_ERROR_OBJECT(self, "Couldn't create a2dpsendersink");
return FALSE;
}
- if (rsp_hdr->posix_errno != 0) {
- GST_ERROR_OBJECT(self, "BT_GETCAPABILITIES failed : %s(%d)",
- strerror(rsp_hdr->posix_errno),
- rsp_hdr->posix_errno);
- return FALSE;
+ if (!gst_bin_add(GST_BIN(self), sink)) {
+ GST_ERROR_OBJECT(self, "failed to add a2dpsendersink "
+ "to the bin");
+ goto cleanup_and_fail;
}
- memcpy(&self->data->caps, rsp, sizeof(*rsp));
- if (!gst_a2dp_sink_update_caps(self)) {
- GST_WARNING_OBJECT(self, "failed to update capabilities");
- return FALSE;
+ if (gst_element_set_state(sink, GST_STATE_READY) ==
+ GST_STATE_CHANGE_FAILURE) {
+ GST_ERROR_OBJECT(self, "a2dpsendersink failed to go to ready");
+ goto remove_element_and_fail;
}
- return TRUE;
-}
-
-static gboolean gst_a2dp_sink_start(GstBaseSink *basesink)
-{
- GstA2dpSink *self = GST_A2DP_SINK(basesink);
- gint sk;
- gint err;
-
- GST_INFO_OBJECT(self, "start");
-
- self->watch_id = 0;
-
- sk = bt_audio_service_open();
- if (sk <= 0) {
- err = errno;
- GST_ERROR_OBJECT(self, "Cannot open connection to bt "
- "audio service: %s %d", strerror(err), err);
- goto failed;
+ if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) {
+ GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay "
+ "to a2dpsendersink");
+ goto remove_element_and_fail;
}
- self->server = g_io_channel_unix_new(sk);
- self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
- G_IO_NVAL, server_callback, self);
+ self->sink = GST_A2DP_SENDER_SINK(sink);
+ g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
- self->data = g_new0(struct bluetooth_data, 1);
- memset(self->data, 0, sizeof(struct bluetooth_data));
+ gst_element_set_state(sink, GST_STATE_PAUSED);
- self->stream = NULL;
- self->stream_caps = NULL;
+ return TRUE;
- if (!gst_a2dp_sink_get_capabilities(self))
- goto failed;
+remove_element_and_fail:
+ gst_element_set_state (sink, GST_STATE_NULL);
+ gst_bin_remove(GST_BIN(self), sink);
+ return FALSE;
- return TRUE;
+cleanup_and_fail:
+ if (sink != NULL)
+ g_object_unref(G_OBJECT(sink));
-failed:
- bt_audio_service_close(sk);
return FALSE;
}
-static gboolean gst_a2dp_sink_stream_start(GstA2dpSink *self)
+static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)
{
- gchar buf[BT_AUDIO_IPC_PACKET_SIZE];
- struct bt_streamstart_req *req = (void *) buf;
- bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf;
- struct bt_streamfd_ind *ind = (void*) buf;
- GIOError io_error;
-
- GST_DEBUG_OBJECT(self, "stream start");
-
- memset (req, 0, sizeof(buf));
- req->h.msg_type = BT_STREAMSTART_REQ;
-
- io_error = gst_a2dp_sink_audioservice_send(self, &req->h);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error ocurred while sending "
- "start packet");
- return FALSE;
- }
-
- GST_DEBUG_OBJECT(self, "stream start packet sent");
-
- io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h,
- BT_STREAMSTART_RSP);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error while stream start confirmation");
- return FALSE;
- }
-
- if (rsp_hdr->posix_errno != 0) {
- GST_ERROR_OBJECT(self, "BT_STREAMSTART_RSP failed : %s(%d)",
- strerror(rsp_hdr->posix_errno),
- rsp_hdr->posix_errno);
- return FALSE;
- }
-
- GST_DEBUG_OBJECT(self, "stream started");
+ GstElement *rtppay;
- io_error = gst_a2dp_sink_audioservice_expect(self, &ind->h,
- BT_STREAMFD_IND);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error while receiving stream fd");
+ rtppay = gst_element_factory_make("rtpsbcpay", "rtp");
+ if (rtppay == NULL) {
+ GST_ERROR_OBJECT(self, "Couldn't create rtpsbcpay");
return FALSE;
}
- if (!gst_a2dp_sink_conf_recv_stream_fd(self))
- return FALSE;
-
- return TRUE;
-}
-
-static gboolean gst_a2dp_sink_configure(GstA2dpSink *self, GstCaps *caps)
-{
- gchar buf[BT_AUDIO_IPC_PACKET_SIZE];
- struct bt_setconfiguration_req *req = (void *) buf;
- bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf;
- struct bt_setconfiguration_rsp *rsp = (void *) buf;
- gboolean ret;
- GIOError io_error;
-
- GST_DEBUG_OBJECT(self, "configuring device");
-
- memset (req, 0, sizeof(buf));
- req->h.msg_type = BT_SETCONFIGURATION_REQ;
- req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE;
- strncpy(req->device, self->device, 18);
- ret = gst_a2dp_sink_init_pkt_conf(self, caps, &req->sbc_capabilities);
- if (!ret) {
- GST_ERROR_OBJECT(self, "Couldn't parse caps "
- "to packet configuration");
- return FALSE;
+ if (!gst_bin_add(GST_BIN(self), rtppay)) {
+ GST_ERROR_OBJECT(self, "failed to add rtp sbc pay to the bin");
+ goto cleanup_and_fail;
}
- io_error = gst_a2dp_sink_audioservice_send(self, &req->h);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error ocurred while sending "
- "configurarion packet");
- return FALSE;
+ if (gst_element_set_state(rtppay, GST_STATE_READY) ==
+ GST_STATE_CHANGE_FAILURE) {
+ GST_ERROR_OBJECT(self, "rtpsbcpay failed to go to ready");
+ goto remove_element_and_fail;
}
- GST_DEBUG_OBJECT(self, "configuration packet sent");
-
- io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h,
- BT_SETCONFIGURATION_RSP);
- if (io_error != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error while receiving device confirmation");
- return FALSE;
+ if (!gst_element_link(self->capsfilter, rtppay)) {
+ GST_ERROR_OBJECT(self, "couldn't link capsfilter "
+ "to rtpsbcpay");
+ goto remove_element_and_fail;
}
- if (rsp_hdr->posix_errno != 0) {
- GST_ERROR_OBJECT(self, "BT_SETCONFIGURATION_RSP failed : %s(%d)",
- strerror(rsp_hdr->posix_errno),
- rsp_hdr->posix_errno);
- return FALSE;
- }
+ self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+ g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL);
- memcpy(&self->data->cfg, rsp, sizeof(*rsp));
- GST_DEBUG_OBJECT(self, "configuration set");
+ gst_element_set_state(rtppay, GST_STATE_PAUSED);
return TRUE;
-}
-static GstFlowReturn gst_a2dp_sink_preroll(GstBaseSink *basesink,
- GstBuffer *buffer)
-{
- GstA2dpSink *sink = GST_A2DP_SINK(basesink);
- gboolean ret;
-
- GST_A2DP_SINK_MUTEX_LOCK(sink);
-
- ret = gst_a2dp_sink_stream_start(sink);
-
- GST_A2DP_SINK_MUTEX_UNLOCK(sink);
+remove_element_and_fail:
+ gst_element_set_state (rtppay, GST_STATE_NULL);
+ gst_bin_remove(GST_BIN(self), rtppay);
+ return FALSE;
- if (!ret)
- return GST_FLOW_ERROR;
+cleanup_and_fail:
+ if (rtppay != NULL)
+ g_object_unref(G_OBJECT(rtppay));
- return GST_FLOW_OK;
+ return FALSE;
}
-static int gst_a2dp_sink_avdtp_write(GstA2dpSink *self)
+static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)
{
- gsize ret;
- struct bluetooth_data *data = self->data;
- struct rtp_header *header;
- struct rtp_payload *payload;
- GIOError err;
-
- header = (void *) data->buffer;
- payload = (void *) (data->buffer + sizeof(*header));
-
- memset(data->buffer, 0, sizeof(*header) + sizeof(*payload));
-
- payload->frame_count = data->frame_count;
- header->v = 2;
- header->pt = 1;
- header->sequence_number = htons(data->seq_num);
- header->timestamp = htonl(data->nsamples);
- header->ssrc = htonl(1);
-
- err = g_io_channel_write(self->stream, data->buffer, data->count, &ret);
- if (err != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error while sending data");
- ret = -1;
- }
-
- /* Reset buffer of data to send */
- data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
- data->frame_count = 0;
- data->samples = 0;
- data->seq_num++;
-
- return ret;
-}
+ GstA2dpSink *self;
+ GstStructure *structure;
-static GstFlowReturn gst_a2dp_sink_render(GstBaseSink *basesink,
- GstBuffer *buffer)
-{
- GstA2dpSink *self = GST_A2DP_SINK(basesink);
- struct bluetooth_data *data = self->data;
- gint encoded;
- gint ret;
+ self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+ GST_INFO_OBJECT(self, "setting caps");
- encoded = GST_BUFFER_SIZE(buffer);
+ structure = gst_caps_get_structure(caps, 0);
- if (data->count + encoded >= data->cfg.link_mtu) {
- ret = gst_a2dp_sink_avdtp_write(self);
- if (ret < 0)
- return GST_FLOW_ERROR;
+ /* first, we need to create our rtp payloader */
+ if (gst_structure_has_name(structure, "audio/x-sbc"))
+ gst_a2dp_sink_init_rtp_sbc_element(self);
+ else {
+ GST_ERROR_OBJECT(self, "Unexpected media type");
+ return FALSE;
}
- memcpy(data->buffer + data->count, GST_BUFFER_DATA(buffer), encoded);
- data->count += encoded;
- data->frame_count++;
-
- return GST_FLOW_OK;
-}
-
-static GstCaps* gst_a2dp_sink_get_caps(GstBaseSink *basesink)
-{
- GstA2dpSink *self = GST_A2DP_SINK(basesink);
-
- if (self->dev_caps)
- return gst_caps_ref(self->dev_caps);
-
- return gst_caps_copy(gst_pad_get_pad_template_caps(GST_BASE_SINK_PAD(self)));
-}
-
-static gboolean gst_a2dp_sink_set_caps(GstBaseSink *basesink, GstCaps *caps)
-{
- GstA2dpSink *self = GST_A2DP_SINK(basesink);
- gboolean ret;
-
- GST_A2DP_SINK_MUTEX_LOCK(self);
- ret = gst_a2dp_sink_configure(self, caps);
-
- if (self->stream_caps)
- gst_caps_unref(self->stream_caps);
- self->stream_caps = gst_caps_ref(caps);
-
- GST_A2DP_SINK_MUTEX_UNLOCK(self);
+ if (!gst_a2dp_sink_init_sender_sink(self))
+ return FALSE;
- return ret;
-}
+ if (!gst_a2dp_sender_sink_set_device_caps(self->sink, caps))
+ return FALSE;
-static gboolean gst_a2dp_sink_unlock(GstBaseSink *basesink)
-{
- GstA2dpSink *self = GST_A2DP_SINK(basesink);
+ g_object_set(G_OBJECT(self->rtp), "mtu",
+ gst_a2dp_sender_sink_get_link_mtu(self->sink), NULL);
- if (self->stream != NULL)
- g_io_channel_flush (self->stream, NULL);
+ /* we forward our new segment here if we have one */
+ gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp),
+ self->newseg_event);
+ self->newseg_event = NULL;
- return TRUE;
+ return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps);
}
-static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
+/* used for catching newsegment events while we don't have a sink, for
+ * later forwarding it to the sink */
+static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)
{
- GObjectClass *object_class = G_OBJECT_CLASS(klass);
- GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass);
+ GstA2dpSink *self;
- parent_class = g_type_class_peek_parent(klass);
+ self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
- object_class->finalize = GST_DEBUG_FUNCPTR(gst_a2dp_sink_finalize);
- object_class->set_property = GST_DEBUG_FUNCPTR(
- gst_a2dp_sink_set_property);
- object_class->get_property = GST_DEBUG_FUNCPTR(
- gst_a2dp_sink_get_property);
-
- basesink_class->start = GST_DEBUG_FUNCPTR(gst_a2dp_sink_start);
- basesink_class->stop = GST_DEBUG_FUNCPTR(gst_a2dp_sink_stop);
- basesink_class->render = GST_DEBUG_FUNCPTR(gst_a2dp_sink_render);
- basesink_class->preroll = GST_DEBUG_FUNCPTR(gst_a2dp_sink_preroll);
- basesink_class->set_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps);
- basesink_class->get_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_get_caps);
- basesink_class->unlock = GST_DEBUG_FUNCPTR(gst_a2dp_sink_unlock);
-
- g_object_class_install_property(object_class, PROP_DEVICE,
- g_param_spec_string("device", "Device",
- "Bluetooth remote device address",
- NULL, G_PARAM_READWRITE));
+ if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT &&
+ gst_element_get_parent(GST_ELEMENT(self->sink)) !=
+ GST_OBJECT_CAST(self)) {
+ if (self->newseg_event != NULL)
+ gst_event_unref(self->newseg_event);
+ self->newseg_event = gst_event_ref(event);
+ }
- GST_DEBUG_CATEGORY_INIT(a2dp_sink_debug, "a2dpsink", 0,
- "A2DP sink element");
+ return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event);
}
-static void gst_a2dp_sink_init(GstA2dpSink *self, GstA2dpSinkClass *klass)
+static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self)
{
- self->device = NULL;
- self->data = NULL;
-
- self->stream = NULL;
+ GstElement *element;
- self->dev_caps = NULL;
+ element = gst_element_factory_make("capsfilter", "filter");
+ if (element == NULL)
+ goto failed;
- self->sink_lock = g_mutex_new();
-}
+ if (!gst_bin_add(GST_BIN(self), element))
+ goto failed;
-static GIOError gst_a2dp_sink_audioservice_send(GstA2dpSink *self,
- const bt_audio_msg_header_t *msg)
-{
- gint err;
- GIOError error;
- gsize written;
-
- GST_DEBUG_OBJECT(self, "sending %s", bt_audio_strmsg(msg->msg_type));
-
- error = g_io_channel_write(self->server, (const gchar*) msg,
- BT_AUDIO_IPC_PACKET_SIZE, &written);
- if (error != G_IO_ERROR_NONE) {
- err = errno;
- GST_ERROR_OBJECT(self, "Error sending data to audio service:"
- " %s(%d)", strerror(err), err);
- }
+ self->capsfilter = element;
+ return TRUE;
- return error;
+failed:
+ GST_ERROR_OBJECT(self, "Failed to initialize caps filter");
+ return FALSE;
}
-static GIOError gst_a2dp_sink_audioservice_recv(GstA2dpSink *self,
- bt_audio_msg_header_t *inmsg)
+static void gst_a2dp_sink_init(GstA2dpSink *self,
+ GstA2dpSinkClass *klass)
{
- GIOError status;
- gsize bytes_read;
- const char *type;
-
- status = g_io_channel_read(self->server, (gchar*) inmsg,
- BT_AUDIO_IPC_PACKET_SIZE, &bytes_read);
- if (status != G_IO_ERROR_NONE) {
- GST_ERROR_OBJECT(self, "Error receiving data from service");
- return status;
- }
-
- type = bt_audio_strmsg(inmsg->msg_type);
- if (!type) {
- GST_ERROR_OBJECT(self, "Bogus message type %d "
- "received from audio service",
- inmsg->msg_type);
- return G_IO_ERROR_INVAL;
- }
-
- GST_DEBUG_OBJECT(self, "Received %s", type);
+ GstPad *capsfilter_pad;
- return status;
+ self->sink = NULL;
+ self->rtp = NULL;
+ self->device = NULL;
+ self->capsfilter = NULL;
+ self->newseg_event = NULL;
+
+ /* we initialize our capsfilter */
+ gst_a2dp_sink_init_caps_filter(self);
+ g_object_set(self->capsfilter, "caps",
+ gst_static_pad_template_get_caps(&gst_a2dp_sink_factory),
+ NULL);
+
+ /* we search for the capsfilter sinkpad */
+ capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink");
+
+ /* now we add a ghostpad */
+ self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink",
+ capsfilter_pad));
+ g_object_unref(capsfilter_pad);
+
+ /* the getcaps of our ghostpad must reflect the device caps */
+ gst_pad_set_getcaps_function(GST_PAD(self->ghostpad),
+ gst_a2dp_sink_get_caps);
+ self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad);
+ gst_pad_set_setcaps_function(GST_PAD(self->ghostpad),
+ GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps));
+
+ /* we need to handle events on our own and we also need the eventfunc
+ * of the ghostpad for forwarding calls */
+ self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad));
+ gst_pad_set_event_function(GST_PAD(self->ghostpad),
+ gst_a2dp_sink_handle_event);
+
+ if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad)))
+ GST_ERROR_OBJECT(self, "failed to add ghostpad");
+
+ self->sink = GST_A2DP_SENDER_SINK(gst_element_factory_make(
+ "a2dpsendersink", "sendersink"));
+ if (self->sink == NULL)
+ GST_WARNING_OBJECT(self, "failed to create a2dpsendersink");
}
-static GIOError gst_a2dp_sink_audioservice_expect(GstA2dpSink *self,
- bt_audio_msg_header_t *outmsg,
- int expected_type)
+gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin)
{
- GIOError status;
-
- status = gst_a2dp_sink_audioservice_recv(self, outmsg);
- if (status != G_IO_ERROR_NONE)
- return status;
-
- if (outmsg->msg_type != expected_type) {
- GST_ERROR_OBJECT(self, "Bogus message %s "
- "received while %s was expected",
- bt_audio_strmsg(outmsg->msg_type),
- bt_audio_strmsg(expected_type));
- return G_IO_ERROR_INVAL;
- }
-
- return status;
+ return gst_element_register (plugin, "a2dpsink",
+ GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK);
}
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
index f5b9b69b..4bf9d603 100644
--- a/audio/gsta2dpsink.h
+++ b/audio/gsta2dpsink.h
@@ -22,7 +22,8 @@
*/
#include <gst/gst.h>
-#include <gst/base/gstbasesink.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include "gsta2dpsendersink.h"
G_BEGIN_DECLS
@@ -40,31 +41,27 @@ G_BEGIN_DECLS
typedef struct _GstA2dpSink GstA2dpSink;
typedef struct _GstA2dpSinkClass GstA2dpSinkClass;
-struct bluetooth_data;
-
struct _GstA2dpSink {
- GstBaseSink sink;
-
- gchar *device;
- GIOChannel *stream;
+ GstBin bin;
- struct bluetooth_data *data;
- GIOChannel *server;
+ GstBaseRTPPayload *rtp;
+ GstA2dpSenderSink *sink;
+ GstElement *capsfilter;
- /* stream connection data */
- GstCaps *stream_caps;
-
- GstCaps *dev_caps;
+ gchar *device;
- GMutex *sink_lock;
+ GstGhostPad *ghostpad;
+ GstPadSetCapsFunction ghostpad_setcapsfunc;
+ GstPadEventFunction ghostpad_eventfunc;
- guint watch_id;
+ GstEvent *newseg_event;
};
struct _GstA2dpSinkClass {
- GstBaseSinkClass parent_class;
+ GstBinClass parent_class;
};
GType gst_a2dp_sink_get_type(void);
+gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin);
G_END_DECLS
diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c
index 593a311e..764bc899 100644
--- a/audio/gstbluetooth.c
+++ b/audio/gstbluetooth.c
@@ -28,7 +28,9 @@
#include "gstsbcenc.h"
#include "gstsbcdec.h"
#include "gstsbcparse.h"
+#include "gsta2dpsendersink.h"
#include "gsta2dpsink.h"
+#include "gstrtpsbcpay.h"
static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc");
@@ -55,20 +57,22 @@ static gboolean plugin_init(GstPlugin *plugin)
SBC_CAPS, NULL, NULL) == FALSE)
return FALSE;
- if (gst_element_register(plugin, "sbcenc",
- GST_RANK_NONE, GST_TYPE_SBC_ENC) == FALSE)
+ if (!gst_sbc_enc_plugin_init(plugin))
return FALSE;
- if (gst_element_register(plugin, "sbcdec",
- GST_RANK_PRIMARY, GST_TYPE_SBC_DEC) == FALSE)
+ if (!gst_sbc_dec_plugin_init(plugin))
return FALSE;
- if (gst_element_register(plugin, "sbcparse",
- GST_RANK_PRIMARY, GST_TYPE_SBC_PARSE) == FALSE)
+ if (!gst_sbc_parse_plugin_init(plugin))
return FALSE;
- if (gst_element_register(plugin, "a2dpsink",
- GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK) == FALSE)
+ if (!gst_a2dp_sender_sink_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_a2dp_sink_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_rtp_sbc_pay_plugin_init(plugin))
return FALSE;
return TRUE;
diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c
new file mode 100644
index 00000000..68aa28a9
--- /dev/null
+++ b/audio/gstrtpsbcpay.c
@@ -0,0 +1,337 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstrtpsbcpay.h"
+#include <math.h>
+#include <string.h>
+
+#define RTP_SBC_PAYLOAD_HEADER_SIZE 1
+#define DEFAULT_MIN_FRAMES 0
+#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE)
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct rtp_payload {
+ guint8 frame_count:4;
+ guint8 rfa0:1;
+ guint8 is_last_fragment:1;
+ guint8 is_first_fragment:1;
+ guint8 is_fragmented:1;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct rtp_payload {
+ guint8 is_fragmented:1;
+ guint8 is_first_fragment:1;
+ guint8 is_last_fragment:1;
+ guint8 rfa0:1;
+ guint8 frame_count:4;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+enum {
+ PROP_0,
+ PROP_MIN_FRAMES
+};
+
+GST_DEBUG_CATEGORY_STATIC(gst_rtp_sbc_pay_debug);
+#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug
+
+GST_BOILERPLATE(GstRtpSBCPay, gst_rtp_sbc_pay, GstBaseRTPPayload,
+ GST_TYPE_BASE_RTP_PAYLOAD);
+
+static const GstElementDetails gst_rtp_sbc_pay_details =
+ GST_ELEMENT_DETAILS("RTP packet payloader",
+ "Codec/Payloader/Network",
+ "Payload SBC audio as RTP packets",
+ "Thiago Sousa Santos "
+ "<thiagoss@lcc.ufcg.edu.br>");
+
+static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc, " /* FIXME remove those caps? */
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "mode = (string) { mono, dual, stereo, joint }, "
+ "blocks = (int) { 4, 8, 12, 16 }, "
+ "subbands = (int) { 4, 8 }, "
+ "allocation = (string) { snr, loudness },"
+ "bitpool = (int) [ 2, 64 ]; ")
+ );
+
+static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory =
+ GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("application/x-rtp") /* FIXME put things here */
+ );
+
+static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gint gst_rtp_sbc_pay_get_frame_len(gint subbands, gint channels,
+ gint blocks, gint bitpool, const gchar* channel_mode)
+{
+ gint len;
+ gint join;
+
+ len = 4 + (4 * subbands * channels)/8;
+
+ if (strcmp(channel_mode, "mono") == 0 ||
+ strcmp(channel_mode, "dual") == 0)
+ len += ((blocks * channels * bitpool)+7) / 8;
+ else {
+ join = strcmp(channel_mode, "joint") == 0 ? 1 : 0;
+ len += ((join * subbands + blocks * bitpool)+7)/8;
+ }
+
+ return len;
+}
+
+static gboolean gst_rtp_sbc_pay_set_caps(GstBaseRTPPayload *payload,
+ GstCaps *caps)
+{
+ GstRtpSBCPay *sbcpay;
+ gint rate, subbands, channels, blocks, bitpool;
+ gint frame_len;
+ const gchar* channel_mode;
+ GstStructure *structure;
+
+ sbcpay = GST_RTP_SBC_PAY(payload);
+
+ structure = gst_caps_get_structure(caps, 0);
+ if (!gst_structure_get_int(structure, "rate", &rate))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "channels", &channels))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "blocks", &blocks))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "subbands", &subbands))
+ return FALSE;
+
+ channel_mode = gst_structure_get_string(structure, "mode");
+ if (!channel_mode)
+ return FALSE;
+
+ frame_len = gst_rtp_sbc_pay_get_frame_len(subbands, channels, blocks,
+ bitpool, channel_mode);
+
+ sbcpay->frame_length = frame_len;
+
+ gst_basertppayload_set_options (payload, "audio", FALSE, "SBC", rate);
+
+ GST_DEBUG_OBJECT(payload, "calculated frame length: %d ", frame_len);
+
+ return gst_basertppayload_set_outcaps (payload, NULL);
+}
+
+static GstFlowReturn gst_rtp_sbc_pay_flush_buffers(GstRtpSBCPay *sbcpay)
+{
+ guint available;
+ guint max_payload;
+ GstBuffer* outbuf;
+ guint8 *payload_data;
+ guint8 *data;
+ struct rtp_payload *payload;
+
+ if (sbcpay->frame_length == 0) {
+ GST_ERROR_OBJECT(sbcpay, "Frame length is 0");
+ return GST_FLOW_ERROR;
+ }
+
+ available = gst_adapter_available(sbcpay->adapter);
+
+ max_payload = gst_rtp_buffer_calc_payload_len(
+ GST_BASE_RTP_PAYLOAD_MTU(sbcpay) - RTP_SBC_PAYLOAD_HEADER_SIZE,
+ 0, 0);
+
+ max_payload = MIN(max_payload, available);
+
+ outbuf = gst_rtp_buffer_new_allocate(max_payload +
+ RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0);
+
+ gst_rtp_buffer_set_payload_type(outbuf,
+ GST_BASE_RTP_PAYLOAD_PT(sbcpay));
+
+ data = gst_adapter_take(sbcpay->adapter, max_payload);
+ payload_data = gst_rtp_buffer_get_payload(outbuf);
+
+ payload = (struct rtp_payload*) payload_data;
+ memset(payload, 0, sizeof(struct rtp_payload));
+ payload->frame_count = max_payload / sbcpay->frame_length;
+
+ memcpy(payload_data + RTP_SBC_PAYLOAD_HEADER_SIZE, data, max_payload);
+ g_free(data);
+
+ /* FIXME - timestamp it! */
+ GST_DEBUG_OBJECT (sbcpay, "Pushing %d bytes", max_payload);
+
+ return gst_basertppayload_push (GST_BASE_RTP_PAYLOAD(sbcpay), outbuf);
+}
+
+static GstFlowReturn gst_rtp_sbc_pay_handle_buffer(GstBaseRTPPayload *payload,
+ GstBuffer *buffer)
+{
+ GstRtpSBCPay *sbcpay;
+ guint available;
+
+ sbcpay = GST_RTP_SBC_PAY(payload);
+ gst_adapter_push(sbcpay->adapter, gst_buffer_copy(buffer));
+
+ available = gst_adapter_available(sbcpay->adapter);
+ if (available + RTP_SBC_HEADER_TOTAL >=
+ GST_BASE_RTP_PAYLOAD_MTU(sbcpay) ||
+ (sbcpay->min_frames != -1 && available >
+ (sbcpay->min_frames * sbcpay->frame_length)))
+ return gst_rtp_sbc_pay_flush_buffers(sbcpay);
+
+ return GST_FLOW_OK;
+}
+
+static gboolean gst_rtp_sbc_pay_handle_event(GstPad *pad,
+ GstEvent *event)
+{
+ GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(GST_PAD_PARENT(pad));
+
+ switch (GST_EVENT_TYPE(event)) {
+ case GST_EVENT_EOS:
+ gst_rtp_sbc_pay_flush_buffers(sbcpay);
+ break;
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void gst_rtp_sbc_pay_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(&gst_rtp_sbc_pay_sink_factory));
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&gst_rtp_sbc_pay_src_factory));
+
+ gst_element_class_set_details(element_class, &gst_rtp_sbc_pay_details);
+}
+
+static void gst_rtp_sbc_pay_finalize(GObject *object)
+{
+ GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(object);
+ g_object_unref (sbcpay->adapter);
+
+ GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void gst_rtp_sbc_pay_class_init(GstRtpSBCPayClass *klass)
+{
+ GObjectClass *gobject_class;
+ GstBaseRTPPayloadClass *payload_class =
+ GST_BASE_RTP_PAYLOAD_CLASS(klass);
+
+ gobject_class = G_OBJECT_CLASS(klass);
+ parent_class = g_type_class_peek_parent(klass);
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_finalize);
+ gobject_class->set_property = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_get_property);
+
+ payload_class->set_caps = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_set_caps);
+ payload_class->handle_buffer = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_handle_buffer);
+ payload_class->handle_event = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_handle_event);
+
+ /* properties */
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_MIN_FRAMES,
+ g_param_spec_int ("min-frames", "minimum frame number",
+ "Minimum quantity of frames to send in one packet "
+ "(-1 for maximum allowed by the mtu)",
+ -1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(gst_rtp_sbc_pay_debug, "rtpsbcpay", 0,
+ "RTP SBC payloader");
+}
+
+static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstRtpSBCPay *sbcpay;
+
+ sbcpay = GST_RTP_SBC_PAY (object);
+
+ switch (prop_id) {
+ case PROP_MIN_FRAMES:
+ sbcpay->min_frames = g_value_get_int(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstRtpSBCPay *sbcpay;
+
+ sbcpay = GST_RTP_SBC_PAY (object);
+
+ switch (prop_id) {
+ case PROP_MIN_FRAMES:
+ g_value_set_int(value, sbcpay->min_frames);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_rtp_sbc_pay_init(GstRtpSBCPay *self, GstRtpSBCPayClass *klass)
+{
+ self->adapter = gst_adapter_new();
+ self->frame_length = 0;
+
+ self->min_frames = DEFAULT_MIN_FRAMES;
+}
+
+gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "rtpsbcpay",
+ GST_RANK_NONE, GST_TYPE_RTP_SBC_PAY);
+}
+
diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h
new file mode 100644
index 00000000..f086a1c7
--- /dev/null
+++ b/audio/gstrtpsbcpay.h
@@ -0,0 +1,65 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include <gst/base/gstadapter.h>
+#include <gst/rtp/gstrtpbuffer.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_RTP_SBC_PAY \
+ (gst_rtp_sbc_pay_get_type())
+#define GST_RTP_SBC_PAY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_SBC_PAY,\
+ GstRtpSBCPay))
+#define GST_RTP_SBC_PAY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_SBC_PAY,\
+ GstRtpSBCPayClass))
+#define GST_IS_RTP_SBC_PAY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_SBC_PAY))
+#define GST_IS_RTP_SBC_PAY_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_SBC_PAY))
+
+typedef struct _GstRtpSBCPay GstRtpSBCPay;
+typedef struct _GstRtpSBCPayClass GstRtpSBCPayClass;
+
+struct _GstRtpSBCPay {
+ GstBaseRTPPayload base;
+
+ GstAdapter *adapter;
+
+ guint frame_length;
+
+ guint min_frames;
+};
+
+struct _GstRtpSBCPayClass {
+ GstBaseRTPPayloadClass parent_class;
+};
+
+GType gst_rtp_sbc_pay_get_type(void);
+
+gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcdec.c b/audio/gstsbcdec.c
index a60c3e69..8c27daba 100644
--- a/audio/gstsbcdec.c
+++ b/audio/gstsbcdec.c
@@ -188,10 +188,21 @@ static void gst_sbc_dec_class_init(GstSbcDecClass *klass)
static void gst_sbc_dec_init(GstSbcDec *self, GstSbcDecClass *klass)
{
- 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));
+ 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");
+ self->srcpad = gst_pad_new_from_static_template(
+ &sbc_dec_src_factory, "src");
gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
}
+
+gboolean gst_sbc_dec_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "sbcdec",
+ GST_RANK_PRIMARY, GST_TYPE_SBC_DEC);
+}
+
+
diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h
index 4a6922a0..0bb0b57e 100644
--- a/audio/gstsbcdec.h
+++ b/audio/gstsbcdec.h
@@ -58,4 +58,6 @@ struct _GstSbcDecClass {
GType gst_sbc_dec_get_type(void);
+gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin);
+
G_END_DECLS
diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c
index 185151f5..08ddc14f 100644
--- a/audio/gstsbcenc.c
+++ b/audio/gstsbcenc.c
@@ -120,32 +120,6 @@ static GstStaticPadTemplate sbc_enc_src_factory =
gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps);
-static void sbc_enc_set_structure_int_param(GstSbcEnc *enc,
- GstStructure *structure, const gchar* field,
- gint field_value)
-{
- GValue *value;
-
- value = g_new0(GValue,1);
- value = g_value_init(value, G_TYPE_INT);
- g_value_set_int(value, field_value);
- gst_structure_set_value(structure, field, value);
- g_free(value);
-}
-
-static void sbc_enc_set_structure_string_param(GstSbcEnc *enc,
- GstStructure *structure, const gchar* field,
- const gchar* field_value)
-{
- GValue *value;
-
- value = g_new0(GValue,1);
- value = g_value_init(value, G_TYPE_STRING);
- g_value_set_string(value, field_value);
- gst_structure_set_value(structure, field, value);
- g_free(value);
-}
-
static GstCaps* sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)
{
GstCaps* src_caps;
@@ -153,45 +127,49 @@ static GstCaps* sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)
GEnumValue *enum_value;
GEnumClass *enum_class;
gchar* temp;
+ GValue *value;
src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad));
structure = gst_caps_get_structure(src_caps, 0);
+ value = g_new0(GValue, 1);
+
if (enc->rate != 0)
- sbc_enc_set_structure_int_param(enc, structure, "rate",
- enc->rate);
+ gst_sbc_util_set_structure_int_param(structure, "rate",
+ enc->rate, value);
if (enc->channels != 0)
- sbc_enc_set_structure_int_param(enc, structure, "channels",
- enc->channels);
+ gst_sbc_util_set_structure_int_param(structure, "channels",
+ enc->channels, value);
if (enc->subbands != 0)
- sbc_enc_set_structure_int_param(enc, structure, "subbands",
- enc->subbands);
+ gst_sbc_util_set_structure_int_param(structure, "subbands",
+ enc->subbands, value);
if (enc->blocks != 0)
- sbc_enc_set_structure_int_param(enc, structure, "blocks",
- enc->blocks);
+ gst_sbc_util_set_structure_int_param(structure, "blocks",
+ enc->blocks, value);
if (enc->mode != BT_A2DP_CHANNEL_MODE_AUTO) {
enum_class = g_type_class_ref(GST_TYPE_SBC_MODE);
enum_value = g_enum_get_value(enum_class, enc->mode);
- sbc_enc_set_structure_string_param(enc, structure, "mode",
- enum_value->value_nick);
+ gst_sbc_util_set_structure_string_param(structure, "mode",
+ enum_value->value_nick, value);
g_type_class_unref(enum_class);
}
if (enc->allocation != BT_A2DP_ALLOCATION_AUTO) {
enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION);
enum_value = g_enum_get_value(enum_class, enc->allocation);
- sbc_enc_set_structure_string_param(enc, structure, "allocation",
- enum_value->value_nick);
+ gst_sbc_util_set_structure_string_param(structure, "allocation",
+ enum_value->value_nick, value);
g_type_class_unref(enum_class);
}
temp = gst_caps_to_string(src_caps);
GST_DEBUG_OBJECT(enc, "Srcpad caps: %s", temp);
g_free(temp);
+ g_free(value);
return src_caps;
}
@@ -207,23 +185,10 @@ static GstCaps* sbc_enc_src_getcaps (GstPad * pad)
static gboolean sbc_enc_src_setcaps (GstPad *pad, GstCaps *caps)
{
- GstCaps* srcpad_caps;
- GstCaps* temp_caps;
- gboolean res = TRUE;
GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
GST_LOG_OBJECT(enc, "setting srcpad caps");
- srcpad_caps = sbc_enc_generate_srcpad_caps(enc);
- temp_caps = gst_caps_intersect(srcpad_caps, caps);
- if (temp_caps == GST_CAPS_NONE)
- res = FALSE;
-
- gst_caps_unref(temp_caps);
- gst_caps_unref(srcpad_caps);
-
- g_return_val_if_fail(res, FALSE);
-
return gst_sbc_enc_fill_sbc_params(enc, caps);
}
@@ -313,38 +278,16 @@ error:
gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps)
{
- GstStructure *structure;
- gint rate, channels, subbands, blocks, bitpool;
- const gchar* mode;
- const gchar* allocation;
-
- g_assert(gst_caps_is_fixed(caps));
- structure = gst_caps_get_structure(caps, 0);
-
- if (!gst_structure_get_int(structure, "rate", &rate))
- return FALSE;
- if (!gst_structure_get_int(structure, "channels", &channels))
- return FALSE;
- if (!gst_structure_get_int(structure, "subbands", &subbands))
- return FALSE;
- if (!gst_structure_get_int(structure, "blocks", &blocks))
- return FALSE;
- if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+ if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps))
return FALSE;
- if (!(mode = gst_structure_get_string(structure, "mode")))
- return FALSE;
- if (!(allocation = gst_structure_get_string(structure, "allocation")))
- return FALSE;
-
- enc->rate = enc->sbc.rate = rate;
- enc->channels = enc->sbc.channels = channels;
- enc->blocks = enc->sbc.blocks = blocks;
- enc->subbands = enc->sbc.subbands = subbands;
- enc->sbc.bitpool = bitpool;
- enc->mode = enc->sbc.joint = gst_sbc_get_mode_int(mode);
- enc->allocation = enc->sbc.allocation = gst_sbc_get_allocation_mode_int(allocation);
+ enc->rate = enc->sbc.rate;
+ enc->channels = enc->sbc.channels;
+ enc->blocks = enc->sbc.blocks;
+ enc->subbands = enc->sbc.subbands;
+ enc->mode = enc->sbc.joint;
+ enc->allocation = enc->sbc.allocation;
enc->codesize = sbc_get_codesize(&enc->sbc);
enc->frame_length = sbc_get_frame_length(&enc->sbc);
enc->frame_duration = sbc_get_frame_duration(&enc->sbc);
@@ -390,6 +333,8 @@ static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer)
gst_adapter_flush(adapter, consumed);
GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer);
+ /* we have only 1 frame */
+ GST_BUFFER_DURATION(output) = enc->frame_duration;
res = gst_pad_push(enc->srcpad, output);
if (res != GST_FLOW_OK)
@@ -559,17 +504,22 @@ static void gst_sbc_enc_class_init(GstSbcEncClass *klass)
static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)
{
- self->sinkpad = gst_pad_new_from_static_template(&sbc_enc_sink_factory, "sink");
+ self->sinkpad = gst_pad_new_from_static_template(
+ &sbc_enc_sink_factory, "sink");
gst_pad_set_setcaps_function (self->sinkpad,
GST_DEBUG_FUNCPTR (sbc_enc_sink_setcaps));
gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
- self->srcpad = gst_pad_new_from_static_template(&sbc_enc_src_factory, "src");
- gst_pad_set_getcaps_function(self->srcpad, GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps));
- gst_pad_set_setcaps_function(self->srcpad, GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps));
+ self->srcpad = gst_pad_new_from_static_template(
+ &sbc_enc_src_factory, "src");
+ gst_pad_set_getcaps_function(self->srcpad,
+ GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps));
+ gst_pad_set_setcaps_function(self->srcpad,
+ GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps));
gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
- gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(sbc_enc_chain));
+ gst_pad_set_chain_function(self->sinkpad,
+ GST_DEBUG_FUNCPTR(sbc_enc_chain));
self->subbands = SBC_ENC_DEFAULT_SUB_BANDS;
self->blocks = SBC_ENC_DEFAULT_BLOCKS;
@@ -578,5 +528,16 @@ static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)
self->rate = SBC_ENC_DEFAULT_RATE;
self->channels = SBC_ENC_DEFAULT_CHANNELS;
+ self->frame_length = 0;
+ self->frame_duration = 0;
+
self->adapter = gst_adapter_new();
}
+
+gboolean gst_sbc_enc_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "sbcenc",
+ GST_RANK_NONE, GST_TYPE_SBC_ENC);
+}
+
+
diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h
index d81428c9..c7b21638 100644
--- a/audio/gstsbcenc.h
+++ b/audio/gstsbcenc.h
@@ -68,4 +68,6 @@ struct _GstSbcEncClass {
GType gst_sbc_enc_get_type(void);
+gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin);
+
G_END_DECLS
diff --git a/audio/gstsbcparse.c b/audio/gstsbcparse.c
index bae7d623..49d0bb6e 100644
--- a/audio/gstsbcparse.c
+++ b/audio/gstsbcparse.c
@@ -56,171 +56,83 @@ static GstStaticPadTemplate sbc_parse_src_factory =
"allocation = (string) { snr, loudness },"
"bitpool = (int) [ 2, 64 ]"));
-/* Creates a fixed caps from the caps given. */
-/* FIXME use gstsbcutil caps fixating function */
-static GstCaps* sbc_parse_select_caps(GstSbcParse *parse, GstCaps *caps)
+static gboolean sbc_parse_sink_setcaps(GstPad * pad, GstCaps * caps)
{
- GstCaps *result;
+ GstSbcParse *parse;
GstStructure *structure;
- const GValue *value;
- gboolean error = FALSE;
- gint temp, rate, channels, blocks, subbands, bitpool;
- const gchar* allocation = NULL;
- const gchar* mode = NULL;
- const gchar* error_message = NULL;
- gchar* str;
-
- str = gst_caps_to_string(caps);
- GST_DEBUG_OBJECT(parse, "Parsing caps: %s", str);
- g_free(str);
+ gint rate, channels;
+
+ parse = GST_SBC_PARSE(GST_PAD_PARENT(pad));
structure = gst_caps_get_structure(caps, 0);
- if (!gst_structure_has_field(structure, "rate")) {
- error = TRUE;
- error_message = "no rate.";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "rate");
- if (GST_VALUE_HOLDS_LIST(value))
- temp = gst_sbc_select_rate_from_list(value);
- else
- temp = g_value_get_int(value);
- rate = temp;
- }
+ if (!gst_structure_get_int(structure, "rate", &rate))
+ return FALSE;
- if (!gst_structure_has_field(structure, "channels")) {
- error = TRUE;
- error_message = "no channels.";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "channels");
- if (GST_VALUE_HOLDS_INT_RANGE(value))
- temp = gst_sbc_select_channels_from_range(value);
- else
- temp = g_value_get_int(value);
- channels = temp;
- }
+ if (!gst_structure_get_int(structure, "channels", &channels))
+ return FALSE;
- if (!gst_structure_has_field(structure, "blocks")) {
- error = TRUE;
- error_message = "no blocks.";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "blocks");
- if (GST_VALUE_HOLDS_LIST(value))
- temp = gst_sbc_select_blocks_from_list(value);
- else
- temp = g_value_get_int(value);
- blocks = temp;
- }
+ if (!(parse->rate == 0 || rate == parse->rate))
+ return FALSE;
- if (!gst_structure_has_field(structure, "subbands")) {
- error = TRUE;
- error_message = "no subbands.";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "subbands");
- if (GST_VALUE_HOLDS_LIST(value))
- temp = gst_sbc_select_subbands_from_list(value);
- else
- temp = g_value_get_int(value);
- subbands = temp;
- }
+ if (!(parse->channels == 0 || channels == parse->channels))
+ return FALSE;
- if (!gst_structure_has_field(structure, "bitpool")) {
- error = TRUE;
- error_message = "no bitpool";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "bitpool");
- if (GST_VALUE_HOLDS_INT_RANGE(value))
- temp = gst_sbc_select_bitpool_from_range(value);
- else
- temp = g_value_get_int(value);
- bitpool = temp;
- }
+ parse->rate = rate;
+ parse->channels = channels;
- if (!gst_structure_has_field(structure, "allocation")) {
- error = TRUE;
- error_message = "no allocation.";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "allocation");
- if (GST_VALUE_HOLDS_LIST(value))
- allocation = gst_sbc_get_allocation_from_list(value);
- else
- allocation = g_value_get_string(value);
- }
+ return gst_sbc_util_fill_sbc_params(&parse->sbc, caps);
+}
- if (!gst_structure_has_field(structure, "mode")) {
- error = TRUE;
- error_message = "no mode.";
- goto error;
- } else {
- value = gst_structure_get_value(structure, "mode");
- if (GST_VALUE_HOLDS_LIST(value))
- mode = gst_sbc_get_mode_from_list(value);
- else
- mode = g_value_get_string(value);
- }
+static GstCaps* sbc_parse_src_getcaps(GstPad *pad)
+{
+ GstCaps *caps;
+ const GstCaps *allowed_caps;
+ GstStructure *structure;
+ GValue *value;
+ GstSbcParse *parse = GST_SBC_PARSE(GST_PAD_PARENT(pad));
-error:
- if (error) {
- GST_ERROR_OBJECT (parse, "Invalid input caps: %s",
- error_message);
- return NULL;
- }
+ allowed_caps = gst_pad_get_allowed_caps(pad);
+ if (allowed_caps == NULL)
+ allowed_caps = gst_pad_get_pad_template_caps(pad);
+ caps = gst_caps_copy(allowed_caps);
- result = gst_caps_new_simple("audio/x-sbc",
- "rate", G_TYPE_INT, rate,
- "channels", G_TYPE_INT, channels,
- "mode", G_TYPE_STRING, mode,
- "blocks", G_TYPE_INT, blocks,
- "subbands", G_TYPE_INT, subbands,
- "allocation", G_TYPE_STRING, allocation,
- "bitpool", G_TYPE_INT, bitpool,
- NULL);
- parse->sbc.rate = rate;
- parse->sbc.channels = channels;
- parse->sbc.blocks = blocks;
- parse->sbc.subbands = subbands;
- parse->sbc.bitpool = bitpool;
- parse->sbc.joint = gst_sbc_get_mode_int(mode);
- parse->sbc.allocation = gst_sbc_get_allocation_mode_int(allocation);
-
- return result;
+ value = g_new0(GValue, 1);
+
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (parse->rate != 0)
+ gst_sbc_util_set_structure_int_param(structure, "rate",
+ parse->rate, value);
+ if (parse->channels != 0)
+ gst_sbc_util_set_structure_int_param(structure, "channels",
+ parse->channels, value);
+
+ g_free(value);
+
+ return caps;
}
-static gboolean sbc_parse_sink_setcaps(GstPad * pad, GstCaps * caps)
+static gboolean sbc_parse_src_acceptcaps(GstPad *pad, GstCaps *caps)
{
+ GstStructure *structure;
GstSbcParse *parse;
- GstCaps *inter, *other, *srccaps;
+ gint rate, channels;
parse = GST_SBC_PARSE(GST_PAD_PARENT(pad));
- other = gst_pad_peer_get_caps(parse->srcpad);
- if (other == NULL)
- other = gst_caps_new_any();
+ structure = gst_caps_get_structure(caps, 0);
- inter = gst_caps_intersect(caps, other);
- if (gst_caps_is_empty(inter)) {
- gst_caps_unref(inter);
+ if (!gst_structure_get_int(structure, "rate", &rate))
return FALSE;
- }
- srccaps = sbc_parse_select_caps(parse, inter);
- if (srccaps == NULL) {
- gst_caps_unref(inter);
+ if (!gst_structure_get_int(structure, "channels", &channels))
return FALSE;
- }
- gst_pad_set_caps(parse->srcpad, srccaps);
+ if ((parse->rate == 0 || parse->rate == rate)
+ && (parse->channels == 0 || parse->channels == channels))
+ return TRUE;
- gst_caps_unref(inter);
- gst_caps_unref(other);
- gst_caps_unref(srccaps);
-
- return TRUE;
+ return FALSE;
}
static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer)
@@ -234,11 +146,13 @@ static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer)
timestamp = GST_BUFFER_TIMESTAMP(buffer);
if (parse->buffer) {
- GstBuffer *temp = buffer;
+ GstBuffer *temp;
+ temp = buffer;
buffer = gst_buffer_span(parse->buffer, 0, buffer,
- GST_BUFFER_SIZE(parse->buffer) + GST_BUFFER_SIZE(buffer));
- gst_buffer_unref(temp);
+ GST_BUFFER_SIZE(parse->buffer)
+ + GST_BUFFER_SIZE(buffer));
gst_buffer_unref(parse->buffer);
+ gst_buffer_unref(temp);
parse->buffer = NULL;
}
@@ -300,11 +214,13 @@ static GstStateChangeReturn sbc_parse_change_state(GstElement *element,
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_DEBUG("Finish subband codec");
+
if (parse->buffer) {
gst_buffer_unref(parse->buffer);
parse->buffer = NULL;
}
sbc_finish(&parse->sbc);
+
break;
default:
@@ -341,12 +257,27 @@ static void gst_sbc_parse_class_init(GstSbcParseClass *klass)
static void gst_sbc_parse_init(GstSbcParse *self, GstSbcParseClass *klass)
{
- self->sinkpad = gst_pad_new_from_static_template(&sbc_parse_sink_factory, "sink");
- gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(sbc_parse_chain));
+ self->sinkpad = gst_pad_new_from_static_template(
+ &sbc_parse_sink_factory, "sink");
+ gst_pad_set_chain_function(self->sinkpad,
+ GST_DEBUG_FUNCPTR(sbc_parse_chain));
gst_pad_set_setcaps_function (self->sinkpad,
GST_DEBUG_FUNCPTR (sbc_parse_sink_setcaps));
gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
- self->srcpad = gst_pad_new_from_static_template(&sbc_parse_src_factory, "src");
+ self->srcpad = gst_pad_new_from_static_template(
+ &sbc_parse_src_factory, "src");
+ gst_pad_set_getcaps_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (sbc_parse_src_getcaps));
+ gst_pad_set_acceptcaps_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (sbc_parse_src_acceptcaps));
+ /* FIXME get encoding parameters on set caps */
gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
}
+
+gboolean gst_sbc_parse_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "sbcparse",
+ GST_RANK_NONE, GST_TYPE_SBC_PARSE);
+}
+
diff --git a/audio/gstsbcparse.h b/audio/gstsbcparse.h
index ceaf2197..eb9ca441 100644
--- a/audio/gstsbcparse.h
+++ b/audio/gstsbcparse.h
@@ -50,6 +50,9 @@ struct _GstSbcParse {
GstBuffer *buffer;
sbc_t sbc;
+
+ gint channels;
+ gint rate;
};
struct _GstSbcParseClass {
@@ -58,4 +61,6 @@ struct _GstSbcParseClass {
GType gst_sbc_parse_get_type(void);
+gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin);
+
G_END_DECLS
diff --git a/audio/gstsbcutil.c b/audio/gstsbcutil.c
index f2351e6b..d791a8d6 100644
--- a/audio/gstsbcutil.c
+++ b/audio/gstsbcutil.c
@@ -26,6 +26,7 @@
#endif
#include "ipc.h"
+#include <math.h>
#include "gstsbcutil.h"
/*
@@ -306,4 +307,103 @@ error:
return result;
}
+/**
+ * Sets the int field_value to the param "field" on the structure.
+ * value is used to do the operation, it must be a uninitialized (zero-filled)
+ * GValue, it will be left unitialized at the end of the function.
+ */
+void gst_sbc_util_set_structure_int_param(GstStructure *structure,
+ const gchar* field, gint field_value,
+ GValue *value)
+{
+ value = g_value_init(value, G_TYPE_INT);
+ g_value_set_int(value, field_value);
+ gst_structure_set_value(structure, field, value);
+ g_value_unset(value);
+}
+
+/**
+ * Sets the string field_value to the param "field" on the structure.
+ * value is used to do the operation, it must be a uninitialized (zero-filled)
+ * GValue, it will be left unitialized at the end of the function.
+ */
+void gst_sbc_util_set_structure_string_param(GstStructure *structure,
+ const gchar* field, const gchar* field_value,
+ GValue *value)
+{
+ value = g_value_init(value, G_TYPE_STRING);
+ g_value_set_string(value, field_value);
+ gst_structure_set_value(structure, field, value);
+ g_value_unset(value);
+}
+
+gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps)
+{
+ GstStructure *structure;
+ gint rate, channels, subbands, blocks, bitpool;
+ const gchar* mode;
+ const gchar* allocation;
+
+ g_assert(gst_caps_is_fixed(caps));
+
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (!gst_structure_get_int(structure, "rate", &rate))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "channels", &channels))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "subbands", &subbands))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "blocks", &blocks))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+ return FALSE;
+
+ if (!(mode = gst_structure_get_string(structure, "mode")))
+ return FALSE;
+ if (!(allocation = gst_structure_get_string(structure, "allocation")))
+ return FALSE;
+
+ sbc->rate = rate;
+ sbc->channels = channels;
+ sbc->blocks = blocks;
+ sbc->subbands = subbands;
+ sbc->bitpool = bitpool;
+ sbc->joint = gst_sbc_get_mode_int(mode);
+ sbc->allocation = gst_sbc_get_allocation_mode_int(allocation);
+
+ return TRUE;
+}
+
+gint gst_sbc_util_calc_frame_len(gint subbands, gint channels,
+ gint blocks, gint bitpool, gint channel_mode)
+{
+ gint len;
+ gint join;
+ len = 4 + (4 * subbands * channels)/8;
+
+ if (channel_mode == BT_A2DP_CHANNEL_MODE_MONO ||
+ channel_mode == BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+ len += ((blocks * channels * bitpool)+7) / 8;
+ else {
+ join = channel_mode == BT_A2DP_CHANNEL_MODE_JOINT_STEREO
+ ? 1 : 0;
+ len += ((join * subbands + blocks * bitpool)+7)/8;
+ }
+
+ return len;
+}
+
+gint gst_sbc_util_calc_bitrate(gint frame_len, gint rate, gint subbands,
+ gint blocks)
+{
+ return (((frame_len * 8 * rate / subbands) / blocks) / 1000);
+}
+
+gint64 gst_sbc_util_calc_frame_duration(gint rate, gint blocks, gint subbands)
+{
+ gint64 res = 1000000;
+ return res * blocks * subbands / rate;
+}
+
diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h
index 4581abf7..a67bf1be 100644
--- a/audio/gstsbcutil.h
+++ b/audio/gstsbcutil.h
@@ -49,3 +49,22 @@ const gchar *gst_sbc_get_mode_string(int joint);
GstCaps* gst_sbc_caps_from_sbc(sbc_capabilities_t *sbc, gint channels);
GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message);
+
+void gst_sbc_util_set_structure_int_param(GstStructure *structure,
+ const gchar* field, gint field_value,
+ GValue *value);
+
+void gst_sbc_util_set_structure_string_param(GstStructure *structure,
+ const gchar* field, const gchar* field_value,
+ GValue *value);
+
+gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps);
+
+gint gst_sbc_util_calc_frame_len(gint subbands, gint channels,
+ gint blocks, gint bitpool, gint channel_mode);
+
+gint gst_sbc_util_calc_bitrate(gint frame_len, gint rate, gint subbands,
+ gint blocks);
+
+gint64 gst_sbc_util_calc_frame_duration(gint rate, gint blocks, gint subbands);
+