diff --git a/sys/bluez/gsta2dpsink.c b/sys/bluez/gsta2dpsink.c index 7528a4263d..dc0eb474a7 100644 --- a/sys/bluez/gsta2dpsink.c +++ b/sys/bluez/gsta2dpsink.c @@ -134,8 +134,7 @@ gst_a2dp_sink_set_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_DEVICE: if (self->sink != NULL) - gst_a2dp_sender_sink_set_device (self->sink, - g_value_get_string (value)); + gst_avdtp_sink_set_device (self->sink, g_value_get_string (value)); if (self->device != NULL) g_free (self->device); @@ -158,7 +157,7 @@ gst_a2dp_sink_get_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_DEVICE: if (self->sink != NULL) { - device = gst_a2dp_sender_sink_get_device (self->sink); + device = gst_avdtp_sink_get_device (self->sink); if (device != NULL) g_value_take_string (value, device); } @@ -228,15 +227,14 @@ gst_a2dp_sink_change_state (GstElement * element, GstStateChange transition) self->sink_is_in_bin = FALSE; self->sink = - GST_A2DP_SENDER_SINK (gst_element_factory_make ("a2dpsendersink", - "sendersink")); + GST_AVDTP_SINK (gst_element_factory_make ("avdtpsink", "avdtpsink")); if (self->sink == NULL) { - GST_WARNING_OBJECT (self, "failed to create a2dpsendersink"); + GST_WARNING_OBJECT (self, "failed to create avdtpsink"); return GST_STATE_CHANGE_FAILURE; } if (self->device != NULL) - gst_a2dp_sender_sink_set_device (self->sink, self->device); + gst_avdtp_sink_set_device (self->sink, self->device); ret = gst_element_set_state (GST_ELEMENT (self->sink), GST_STATE_READY); break; @@ -264,8 +262,7 @@ gst_a2dp_sink_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_READY_TO_NULL: if (self->sink_is_in_bin) { if (!gst_bin_remove (GST_BIN (self), GST_ELEMENT (self->sink))) - GST_WARNING_OBJECT (self, "Failed to remove " - "a2dpsendersink from bin"); + GST_WARNING_OBJECT (self, "Failed to remove " "avdtpsink from bin"); } else if (self->sink != NULL) { gst_element_set_state (GST_ELEMENT (self->sink), GST_STATE_NULL); g_object_unref (G_OBJECT (self->sink)); @@ -308,7 +305,7 @@ gst_a2dp_sink_class_init (GstA2dpSinkClass * klass) static GstCaps * gst_a2dp_sink_get_device_caps (GstA2dpSink * self) { - return gst_a2dp_sender_sink_get_device_caps (self->sink); + return gst_avdtp_sink_get_device_caps (self->sink); } static GstCaps * @@ -340,31 +337,31 @@ gst_a2dp_sink_init_sender_sink (GstA2dpSink * self) GstElement *sink; if (self->sink == NULL) - sink = gst_element_factory_make ("a2dpsendersink", "sendersink"); + sink = gst_element_factory_make ("avdtpsink", "avdtosink"); else sink = GST_ELEMENT (self->sink); if (sink == NULL) { - GST_ERROR_OBJECT (self, "Couldn't create a2dpsendersink"); + GST_ERROR_OBJECT (self, "Couldn't create avdtpsink"); return FALSE; } if (!gst_bin_add (GST_BIN (self), sink)) { - GST_ERROR_OBJECT (self, "failed to add a2dpsendersink " "to the bin"); + GST_ERROR_OBJECT (self, "failed to add avdtpsink " "to the bin"); goto cleanup_and_fail; } if (gst_element_set_state (sink, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) { - GST_ERROR_OBJECT (self, "a2dpsendersink failed to go to ready"); + GST_ERROR_OBJECT (self, "avdtpsink failed to go to ready"); goto remove_element_and_fail; } if (!gst_element_link (GST_ELEMENT (self->rtp), sink)) { - GST_ERROR_OBJECT (self, "couldn't link rtpsbcpay " "to a2dpsendersink"); + GST_ERROR_OBJECT (self, "couldn't link rtpsbcpay " "to avdtpsink"); goto remove_element_and_fail; } - self->sink = GST_A2DP_SENDER_SINK (sink); + self->sink = GST_AVDTP_SINK (sink); self->sink_is_in_bin = TRUE; g_object_set (G_OBJECT (self->sink), "device", self->device, NULL); @@ -461,10 +458,10 @@ gst_a2dp_sink_init_dynamic_elements (GstA2dpSink * self, GstCaps * caps) /* send directly the crc */ if (gst_tag_list_get_boolean (self->taglist, "has-crc", &crc)) - gst_a2dp_sender_sink_set_crc (self->sink, crc); + gst_avdtp_sink_set_crc (self->sink, crc); if (gst_tag_list_get_string (self->taglist, "channel-mode", &mode)) - gst_a2dp_sender_sink_set_channel_mode (self->sink, mode); + gst_avdtp_sink_set_channel_mode (self->sink, mode); capsfilterpad = gst_ghost_pad_get_target (self->ghostpad); gst_pad_send_event (capsfilterpad, event); @@ -472,11 +469,11 @@ gst_a2dp_sink_init_dynamic_elements (GstA2dpSink * self, GstCaps * caps) g_free (mode); } - if (!gst_a2dp_sender_sink_set_device_caps (self->sink, caps)) + if (!gst_avdtp_sink_set_device_caps (self->sink, caps)) return FALSE; g_object_set (G_OBJECT (self->rtp), "mtu", - gst_a2dp_sender_sink_get_link_mtu (self->sink), NULL); + gst_avdtp_sink_get_link_mtu (self->sink), NULL); /* we forward our new segment here if we have one */ if (self->newseg_event) { diff --git a/sys/bluez/gsta2dpsink.h b/sys/bluez/gsta2dpsink.h index 368f837a65..26da4c473f 100644 --- a/sys/bluez/gsta2dpsink.h +++ b/sys/bluez/gsta2dpsink.h @@ -23,7 +23,7 @@ #include #include -#include "gsta2dpsendersink.h" +#include "gstavdtpsink.h" G_BEGIN_DECLS @@ -45,7 +45,7 @@ struct _GstA2dpSink { GstBin bin; GstBaseRTPPayload *rtp; - GstA2dpSenderSink *sink; + GstAvdtpSink *sink; GstElement *capsfilter; gchar *device; diff --git a/sys/bluez/gstavdtpsink.c b/sys/bluez/gstavdtpsink.c new file mode 100644 index 0000000000..b41467b564 --- /dev/null +++ b/sys/bluez/gstavdtpsink.c @@ -0,0 +1,1336 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2007 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "ipc.h" +#include "rtp.h" +#include "gstsbcutil.h" + +#include "gstavdtpsink.h" + +GST_DEBUG_CATEGORY_STATIC (avdtp_sink_debug); +#define GST_CAT_DEFAULT avdtp_sink_debug + +#define BUFFER_SIZE 2048 +#define TEMPLATE_MAX_BITPOOL 64 +#define CRC_PROTECTED 1 +#define CRC_UNPROTECTED 0 + +#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START { \ + g_mutex_lock (s->sink_lock); \ + } G_STMT_END + +#define GST_AVDTP_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 (GstAvdtpSink, gst_avdtp_sink, GstBaseSink, GST_TYPE_BASE_SINK); + +static const GstElementDetails avdtp_sink_details = +GST_ELEMENT_DETAILS ("Bluetooth AVDTP sink", + "Sink/Audio", + "Plays audio to an A2DP device", + "Marcel Holtmann "); + +static GstStaticPadTemplate avdtp_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\";" + "application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " + GST_RTP_PAYLOAD_MPA_STRING ", " + "clock-rate = (int) 90000; " + "application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " + GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) 90000, " "encoding-name = (string) \"MPA\"")); + +static GIOError gst_avdtp_sink_audioservice_send (GstAvdtpSink * self, + const bt_audio_msg_header_t * msg); +static GIOError gst_avdtp_sink_audioservice_expect (GstAvdtpSink * self, + bt_audio_msg_header_t * outmsg, int expected_type); + + +static void +gst_avdtp_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 (&avdtp_sink_factory)); + + gst_element_class_set_details (element_class, &avdtp_sink_details); +} + +static gboolean +gst_avdtp_sink_stop (GstBaseSink * basesink) +{ + GstAvdtpSink *self = GST_AVDTP_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_avdtp_sink_finalize (GObject * object) +{ + GstAvdtpSink *self = GST_AVDTP_SINK (object); + + if (self->data) + gst_avdtp_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_avdtp_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstAvdtpSink *sink = GST_AVDTP_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_avdtp_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstAvdtpSink *sink = GST_AVDTP_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_avdtp_sink_bluetooth_recvmsg_fd (GstAvdtpSink * 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_avdtp_sink_init_pkt_conf (GstAvdtpSink * 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_SBC_SAMPLING_FREQ_44100; + else if (rate == 48000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_48000; + else if (rate == 32000) + cfg->frequency = BT_SBC_SAMPLING_FREQ_32000; + else if (rate == 16000) + cfg->frequency = BT_SBC_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_avdtp_sink_conf_recv_stream_fd (GstAvdtpSink * self) +{ + struct bluetooth_data *data = self->data; + gint ret; + GIOError err; + GError *gerr = NULL; + GIOStatus status; + GIOFlags flags; + gsize read; + + ret = gst_avdtp_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) +{ + GstAvdtpSink *sink; + + if (cond & G_IO_HUP || cond & G_IO_NVAL) + return FALSE; + else if (cond & G_IO_ERR) { + sink = GST_AVDTP_SINK (data); + GST_WARNING_OBJECT (sink, "Untreated callback G_IO_ERR"); + } + + return TRUE; +} + +static GstStructure * +gst_avdtp_sink_parse_sbc_caps (GstAvdtpSink * self, sbc_capabilities_t * sbc) +{ + GstStructure *structure; + GValue *value; + GValue *list; + gboolean mono, stereo; + + 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_SBC_SAMPLING_FREQ_48000) { + g_value_set_int (value, 48000); + gst_value_list_prepend_value (list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) { + g_value_set_int (value, 44100); + gst_value_list_prepend_value (list, value); + } + if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) { + g_value_set_int (value, 32000); + gst_value_list_prepend_value (list, value); + } + if (sbc->frequency & BT_SBC_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); + + return structure; +} + +static GstStructure * +gst_avdtp_sink_parse_mpeg_caps (GstAvdtpSink * self, mpeg_capabilities_t * mpeg) +{ + GstStructure *structure; + GValue *value; + GValue *list; + gboolean valid_layer = FALSE; + gboolean mono, stereo; + + GST_LOG_OBJECT (self, "parsing mpeg caps"); + + structure = gst_structure_empty_new ("audio/mpeg"); + value = g_new0 (GValue, 1); + g_value_init (value, G_TYPE_INT); + + list = g_value_init (g_new0 (GValue, 1), GST_TYPE_LIST); + g_value_set_int (value, 1); + gst_value_list_prepend_value (list, value); + g_value_set_int (value, 2); + gst_value_list_prepend_value (list, value); + gst_structure_set_value (structure, "mpegversion", list); + g_free (list); + + /* layer */ + GST_LOG_OBJECT (self, "setting mpeg layer"); + list = g_value_init (g_new0 (GValue, 1), GST_TYPE_LIST); + if (mpeg->layer & BT_MPEG_LAYER_1) { + g_value_set_int (value, 1); + gst_value_list_prepend_value (list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_2) { + g_value_set_int (value, 2); + gst_value_list_prepend_value (list, value); + valid_layer = TRUE; + } + if (mpeg->layer & BT_MPEG_LAYER_3) { + g_value_set_int (value, 3); + gst_value_list_prepend_value (list, value); + valid_layer = TRUE; + } + if (list) { + gst_structure_set_value (structure, "layer", list); + g_free (list); + list = NULL; + } + + if (!valid_layer) { + gst_structure_free (structure); + g_free (value); + return NULL; + } + + /* rate */ + GST_LOG_OBJECT (self, "setting mpeg rate"); + list = g_value_init (g_new0 (GValue, 1), GST_TYPE_LIST); + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) { + g_value_set_int (value, 48000); + gst_value_list_prepend_value (list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) { + g_value_set_int (value, 44100); + gst_value_list_prepend_value (list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) { + g_value_set_int (value, 32000); + gst_value_list_prepend_value (list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) { + g_value_set_int (value, 24000); + gst_value_list_prepend_value (list, value); + } + if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) { + g_value_set_int (value, 22050); + gst_value_list_prepend_value (list, value); + } + if (mpeg->frequency & BT_MPEG_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; + } + + /* channels */ + GST_LOG_OBJECT (self, "setting mpeg channels"); + mono = FALSE; + stereo = FALSE; + if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + mono = TRUE; + if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || + (mpeg->channel_mode & + BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || + (mpeg->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); + + return structure; +} + +static gboolean +gst_avdtp_sink_update_caps (GstAvdtpSink * self) +{ + sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities; + mpeg_capabilities_t *mpeg = &self->data->caps.mpeg_capabilities; + GstStructure *sbc_structure; + GstStructure *mpeg_structure; + gchar *tmp; + + GST_LOG_OBJECT (self, "updating device caps"); + + sbc_structure = gst_avdtp_sink_parse_sbc_caps (self, sbc); + mpeg_structure = gst_avdtp_sink_parse_mpeg_caps (self, mpeg); + + if (self->dev_caps != NULL) + gst_caps_unref (self->dev_caps); + self->dev_caps = gst_caps_new_full (sbc_structure, NULL); + if (mpeg_structure != NULL) + gst_caps_append_structure (self->dev_caps, mpeg_structure); + + tmp = gst_caps_to_string (self->dev_caps); + GST_DEBUG_OBJECT (self, "Device capabilities: %s", tmp); + g_free (tmp); + + return TRUE; +} + +static gboolean +gst_avdtp_sink_get_capabilities (GstAvdtpSink * 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_avdtp_sink_audioservice_send (self, &req->h); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT (self, "Error while asking device caps"); + return FALSE; + } + + io_error = gst_avdtp_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_avdtp_sink_update_caps (self)) { + GST_WARNING_OBJECT (self, "failed to update capabilities"); + return FALSE; + } + + return TRUE; +} + +static gint +gst_avdtp_sink_get_channel_mode (const gchar * mode) +{ + if (strcmp (mode, "stereo") == 0) + return BT_A2DP_CHANNEL_MODE_STEREO; + else if (strcmp (mode, "joint") == 0) + return BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (strcmp (mode, "dual") == 0) + return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (strcmp (mode, "mono") == 0) + return BT_A2DP_CHANNEL_MODE_MONO; + else + return -1; +} + +static void +gst_avdtp_sink_tag (const GstTagList * taglist, + const gchar * tag, gpointer user_data) +{ + gboolean crc; + gchar *channel_mode = NULL; + GstAvdtpSink *self = GST_AVDTP_SINK (user_data); + + if (strcmp (tag, "has-crc") == 0) { + + if (!gst_tag_list_get_boolean (taglist, tag, &crc)) { + GST_WARNING_OBJECT (self, "failed to get crc tag"); + self->mpeg_stream_changed = TRUE; + } + + gst_avdtp_sink_set_crc (self, crc); + + } else if (strcmp (tag, "channel-mode") == 0) { + + if (!gst_tag_list_get_string (taglist, tag, &channel_mode)) { + GST_WARNING_OBJECT (self, "failed to get channel-mode tag"); + self->mpeg_stream_changed = TRUE; + } + + self->channel_mode = gst_avdtp_sink_get_channel_mode (channel_mode); + if (self->channel_mode == -1) + GST_WARNING_OBJECT (self, "Received invalid channel " + "mode: %s", channel_mode); + g_free (channel_mode); + + } else + GST_DEBUG_OBJECT (self, "received unused tag: %s", tag); +} + +static gboolean +gst_avdtp_sink_event (GstBaseSink * basesink, GstEvent * event) +{ + GstAvdtpSink *self = GST_AVDTP_SINK (basesink); + GstTagList *taglist = NULL; + + if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) { + /* we check the tags, mp3 has tags that are importants and + * are outside caps */ + gst_event_parse_tag (event, &taglist); + gst_tag_list_foreach (taglist, gst_avdtp_sink_tag, self); + } + + return TRUE; +} + +static gboolean +gst_avdtp_sink_start (GstBaseSink * basesink) +{ + GstAvdtpSink *self = GST_AVDTP_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; + self->mp3_using_crc = -1; + self->channel_mode = -1; + self->mpeg_stream_changed = FALSE; + + if (!gst_avdtp_sink_get_capabilities (self)) { + GST_ERROR_OBJECT (self, "failed to get capabilities " "from device"); + goto failed; + } + + return TRUE; + +failed: + bt_audio_service_close (sk); + return FALSE; +} + +static gboolean +gst_avdtp_sink_stream_start (GstAvdtpSink * 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_avdtp_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_avdtp_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_avdtp_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_avdtp_sink_conf_recv_stream_fd (self)) + return FALSE; + + return TRUE; +} + +static gboolean +gst_avdtp_sink_init_mp3_pkt_conf (GstAvdtpSink * self, GstCaps * caps, + mpeg_capabilities_t * pkt) +{ + const GValue *value = NULL; + gint rate, layer; + GstStructure *structure = gst_caps_get_structure (caps, 0); + + /* layer */ + value = gst_structure_get_value (structure, "layer"); + layer = g_value_get_int (value); + if (layer == 1) + pkt->layer = BT_MPEG_LAYER_1; + else if (layer == 2) + pkt->layer = BT_MPEG_LAYER_2; + else if (layer == 3) + pkt->layer = BT_MPEG_LAYER_3; + else { + GST_ERROR_OBJECT (self, "Unexpected layer: %d", layer); + return FALSE; + } + + /* crc */ + if (self->mp3_using_crc != -1) + pkt->crc = self->mp3_using_crc; + else { + GST_ERROR_OBJECT (self, "No info about crc was received, " + " can't proceed"); + return FALSE; + } + + /* channel mode */ + if (self->channel_mode != -1) + pkt->channel_mode = self->channel_mode; + else { + GST_ERROR_OBJECT (self, "No info about channel mode " + "received, can't proceed"); + return FALSE; + } + + /* mpf - we will only use the mandatory one */ + pkt->mpf = 0; + + value = gst_structure_get_value (structure, "rate"); + rate = g_value_get_int (value); + if (rate == 44100) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100; + else if (rate == 48000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000; + else if (rate == 32000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000; + else if (rate == 24000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000; + else if (rate == 22050) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050; + else if (rate == 16000) + pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000; + else { + GST_ERROR_OBJECT (self, "Invalid rate while setting caps"); + return FALSE; + } + + /* vbr - we always say its vbr, we don't have how to know it */ + pkt->bitrate = 0x8000; + + /* bitrate - we don't set anything, its vbr */ + /* FIXME - is this right? */ + + return TRUE; +} + +static gboolean +gst_avdtp_sink_configure (GstAvdtpSink * 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; + GstStructure *structure; + + 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); + structure = gst_caps_get_structure (caps, 0); + + if (gst_structure_has_name (structure, "audio/x-sbc")) + ret = gst_avdtp_sink_init_pkt_conf (self, caps, &req->sbc_capabilities); + else if (gst_structure_has_name (structure, "audio/mpeg")) + ret = gst_avdtp_sink_init_mp3_pkt_conf (self, caps, + &req->mpeg_capabilities); + else + ret = FALSE; + + if (!ret) { + GST_ERROR_OBJECT (self, "Couldn't parse caps " "to packet configuration"); + return FALSE; + } + + io_error = gst_avdtp_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_avdtp_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_avdtp_sink_preroll (GstBaseSink * basesink, GstBuffer * buffer) +{ + GstAvdtpSink *sink = GST_AVDTP_SINK (basesink); + gboolean ret; + + GST_AVDTP_SINK_MUTEX_LOCK (sink); + + ret = gst_avdtp_sink_stream_start (sink); + + GST_AVDTP_SINK_MUTEX_UNLOCK (sink); + + if (!ret) + return GST_FLOW_ERROR; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_avdtp_sink_render (GstBaseSink * basesink, GstBuffer * buffer) +{ + GstAvdtpSink *self = GST_AVDTP_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_avdtp_sink_unlock (GstBaseSink * basesink) +{ + GstAvdtpSink *self = GST_AVDTP_SINK (basesink); + + if (self->stream != NULL) + g_io_channel_flush (self->stream, NULL); + + return TRUE; +} + +static GstFlowReturn +gst_avdtp_sink_buffer_alloc (GstBaseSink * basesink, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf) +{ + GstAvdtpSink *self = GST_AVDTP_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_avdtp_sink_class_init (GstAvdtpSinkClass * 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_avdtp_sink_finalize); + object_class->set_property = GST_DEBUG_FUNCPTR (gst_avdtp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR (gst_avdtp_sink_get_property); + + basesink_class->start = GST_DEBUG_FUNCPTR (gst_avdtp_sink_start); + basesink_class->stop = GST_DEBUG_FUNCPTR (gst_avdtp_sink_stop); + basesink_class->render = GST_DEBUG_FUNCPTR (gst_avdtp_sink_render); + basesink_class->preroll = GST_DEBUG_FUNCPTR (gst_avdtp_sink_preroll); + basesink_class->unlock = GST_DEBUG_FUNCPTR (gst_avdtp_sink_unlock); + basesink_class->event = GST_DEBUG_FUNCPTR (gst_avdtp_sink_event); + + basesink_class->buffer_alloc = + GST_DEBUG_FUNCPTR (gst_avdtp_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 (avdtp_sink_debug, "a2dpsendersink", 0, + "A2DP sink element"); +} + +static void +gst_avdtp_sink_init (GstAvdtpSink * self, GstAvdtpSinkClass * klass) +{ + self->device = NULL; + self->data = NULL; + + self->stream = NULL; + + self->dev_caps = NULL; + + self->sink_lock = g_mutex_new (); +} + +static GIOError +gst_avdtp_sink_audioservice_send (GstAvdtpSink * 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_avdtp_sink_audioservice_recv (GstAvdtpSink * 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_avdtp_sink_audioservice_expect (GstAvdtpSink * self, + bt_audio_msg_header_t * outmsg, int expected_type) +{ + GIOError status; + + status = gst_avdtp_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_avdtp_sink_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "avdtpsink", + GST_RANK_NONE, GST_TYPE_AVDTP_SINK); +} + + +/* public functions */ +GstCaps * +gst_avdtp_sink_get_device_caps (GstAvdtpSink * sink) +{ + if (sink->dev_caps == NULL) + return NULL; + + return gst_caps_copy (sink->dev_caps); +} + +gboolean +gst_avdtp_sink_set_device_caps (GstAvdtpSink * self, GstCaps * caps) +{ + gboolean ret; + + GST_DEBUG_OBJECT (self, "setting device caps"); + GST_AVDTP_SINK_MUTEX_LOCK (self); + ret = gst_avdtp_sink_configure (self, caps); + + if (self->stream_caps) + gst_caps_unref (self->stream_caps); + self->stream_caps = gst_caps_ref (caps); + + GST_AVDTP_SINK_MUTEX_UNLOCK (self); + + return ret; +} + +guint +gst_avdtp_sink_get_link_mtu (GstAvdtpSink * sink) +{ + return sink->data->link_mtu; +} + +void +gst_avdtp_sink_set_device (GstAvdtpSink * 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_avdtp_sink_get_device (GstAvdtpSink * self) +{ + return g_strdup (self->device); +} + +void +gst_avdtp_sink_set_crc (GstAvdtpSink * self, gboolean crc) +{ + gint new_crc; + + new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED; + + /* test if we already received a different crc */ + if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) { + GST_ERROR_OBJECT (self, "crc changed during stream"); + /* FIXME test this, its not being used anywhere */ + self->mpeg_stream_changed = TRUE; + return; + } + self->mp3_using_crc = new_crc; + +} + +void +gst_avdtp_sink_set_channel_mode (GstAvdtpSink * self, const gchar * mode) +{ + gint new_mode; + + new_mode = gst_avdtp_sink_get_channel_mode (mode); + + if (self->channel_mode != -1 && new_mode != self->channel_mode) { + GST_ERROR_OBJECT (self, "channel mode changed during stream"); + self->mpeg_stream_changed = TRUE; + } + + self->channel_mode = new_mode; + if (self->channel_mode == -1) + GST_WARNING_OBJECT (self, "Received invalid channel " "mode: %s", mode); +} diff --git a/sys/bluez/gstavdtpsink.h b/sys/bluez/gstavdtpsink.h new file mode 100644 index 0000000000..d038082975 --- /dev/null +++ b/sys/bluez/gstavdtpsink.h @@ -0,0 +1,101 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2007 Marcel Holtmann + * + * + * 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_AVDTP_SINK_H +#define __GST_AVDTP_SINK_H + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_AVDTP_SINK \ + (gst_avdtp_sink_get_type()) +#define GST_AVDTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVDTP_SINK,\ + GstAvdtpSink)) +#define GST_AVDTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVDTP_SINK,\ + GstAvdtpSinkClass)) +#define GST_IS_AVDTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVDTP_SINK)) +#define GST_IS_AVDTP_SINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVDTP_SINK)) + +typedef struct _GstAvdtpSink GstAvdtpSink; +typedef struct _GstAvdtpSinkClass GstAvdtpSinkClass; + +struct bluetooth_data; + +struct _GstAvdtpSink { + GstBaseSink sink; + + gchar *device; + GIOChannel *stream; + + struct bluetooth_data *data; + GIOChannel *server; + + /* mp3 stream data (outside caps data)*/ + gboolean mpeg_stream_changed; + gint mp3_using_crc; + gint channel_mode; + + /* stream connection data */ + GstCaps *stream_caps; + + GstCaps *dev_caps; + + GMutex *sink_lock; + + guint watch_id; +}; + +struct _GstAvdtpSinkClass { + GstBaseSinkClass parent_class; +}; + +GType gst_avdtp_sink_get_type(void); + +GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink); +gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink, + GstCaps *caps); + +guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink); + +void gst_avdtp_sink_set_device(GstAvdtpSink *sink, + const gchar* device); + +gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink); + +gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin); + +void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc); + +void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self, + const gchar *mode); + + +G_END_DECLS + +#endif /* __GST_AVDTP_SINK_H */