diff options
author | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2008-01-23 13:14:02 +0000 |
---|---|---|
committer | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2008-01-23 13:14:02 +0000 |
commit | a0af7ee44534dad8f35a4142c6a22177e54ffc57 (patch) | |
tree | c6a475d035e40f617a213a9ad5327a7eb5d01388 | |
parent | 1cacae6dd9f44d0e403aa29e45eb3d20e7127f68 (diff) |
Make a2dpsink to act like a bin and split the payloader.
-rw-r--r-- | audio/Makefile.am | 4 | ||||
-rw-r--r-- | audio/gsta2dpsendersink.c | 1013 | ||||
-rw-r--r-- | audio/gsta2dpsendersink.h | 90 | ||||
-rw-r--r-- | audio/gsta2dpsink.c | 1042 | ||||
-rw-r--r-- | audio/gsta2dpsink.h | 29 | ||||
-rw-r--r-- | audio/gstbluetooth.c | 20 | ||||
-rw-r--r-- | audio/gstrtpsbcpay.c | 337 | ||||
-rw-r--r-- | audio/gstrtpsbcpay.h | 65 | ||||
-rw-r--r-- | audio/gstsbcdec.c | 17 | ||||
-rw-r--r-- | audio/gstsbcdec.h | 2 | ||||
-rw-r--r-- | audio/gstsbcenc.c | 131 | ||||
-rw-r--r-- | audio/gstsbcenc.h | 2 | ||||
-rw-r--r-- | audio/gstsbcparse.c | 223 | ||||
-rw-r--r-- | audio/gstsbcparse.h | 5 | ||||
-rw-r--r-- | audio/gstsbcutil.c | 100 | ||||
-rw-r--r-- | audio/gstsbcutil.h | 19 |
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); + |