/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2009 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 <gst/rtp/gstrtpbuffer.h> #include "ipc.h" #include "rtp.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 DEFAULT_AUTOCONNECT TRUE #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_get_capabilities_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_AUDIO(n) (strcmp((n), "audio/mpeg") == 0) enum { PROP_0, PROP_DEVICE, PROP_AUTOCONNECT }; 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 <marcel@holtmann.org>"); 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\"," "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " "clock-rate = (int) { 16000, 32000, " "44100, 48000 }, " "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, guint8 expected_name); 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_shutdown(self->stream, TRUE, NULL); 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; case PROP_AUTOCONNECT: sink->autoconnect = g_value_get_boolean(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; case PROP_AUTOCONNECT: g_value_set_boolean(value, sink->autoconnect); 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 codec_capabilities_t *gst_avdtp_find_caps(GstAvdtpSink *sink, uint8_t codec_type) { struct bt_get_capabilities_rsp *rsp = sink->data->caps; codec_capabilities_t *codec = (void *) rsp->data; int bytes_left = rsp->h.length - sizeof(*rsp); while (bytes_left > 0) { if ((codec->type == codec_type) && !(codec->lock & BT_WRITE_LOCK)) break; bytes_left -= codec->length; codec = (void *) codec + codec->length; } if (bytes_left <= 0) return NULL; return codec; } static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink, GstCaps *caps, sbc_capabilities_t *pkt) { sbc_capabilities_t *cfg; const GValue *value = NULL; const char *pref, *name; gint rate, subbands, blocks; GstStructure *structure = gst_caps_get_structure(caps, 0); cfg = (void *) gst_avdtp_find_caps(sink, BT_A2DP_SBC_SINK); name = gst_structure_get_name(structure); if (!(IS_SBC(name))) { GST_ERROR_OBJECT(sink, "Unexpected format %s, " "was expecting sbc", 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, "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, "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) { if (cond & G_IO_HUP || cond & G_IO_NVAL) return FALSE; else if (cond & G_IO_ERR) GST_WARNING_OBJECT(GST_AVDTP_SINK(data), "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_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_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 */ 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; if (!mpeg) return NULL; 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; mpeg_capabilities_t *mpeg; GstStructure *sbc_structure; GstStructure *mpeg_structure; gchar *tmp; GST_LOG_OBJECT(self, "updating device caps"); sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK); mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK); 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_SUGGESTED_BUFFER_SIZE]; struct bt_get_capabilities_req *req = (void *) buf; struct bt_get_capabilities_rsp *rsp = (void *) buf; GIOError io_error; memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); req->h.type = BT_REQUEST; req->h.name = BT_GET_CAPABILITIES; req->h.length = sizeof(*req); if (self->device == NULL) return FALSE; strncpy(req->destination, self->device, 18); if (self->autoconnect) req->flags |= BT_FLAG_AUTOCONNECT; 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; } rsp->h.length = 0; io_error = gst_avdtp_sink_audioservice_expect(self, &rsp->h, BT_GET_CAPABILITIES); if (io_error != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error while getting device caps"); return FALSE; } self->data->caps = g_malloc0(rsp->h.length); memcpy(self->data->caps, rsp, rsp->h.length); 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-stereo") == 0) return BT_A2DP_CHANNEL_MODE_JOINT_STEREO; else if (strcmp(mode, "dual-channel") == 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"); return; } 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"); return; } 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); self->stream = NULL; self->stream_caps = NULL; self->mp3_using_crc = -1; self->channel_mode = -1; 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_SUGGESTED_BUFFER_SIZE]; struct bt_start_stream_req *req = (void *) buf; struct bt_start_stream_rsp *rsp = (void *) buf; struct bt_new_stream_ind *ind = (void *) buf; GIOError io_error; memset(req, 0, sizeof(buf)); req->h.type = BT_REQUEST; req->h.name = BT_START_STREAM; req->h.length = sizeof(*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; } rsp->h.length = sizeof(*rsp); io_error = gst_avdtp_sink_audioservice_expect(self, &rsp->h, BT_START_STREAM); if (io_error != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error while stream " "start confirmation"); return FALSE; } ind->h.length = sizeof(*ind); io_error = gst_avdtp_sink_audioservice_expect(self, &ind->h, BT_NEW_STREAM); 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; const gchar *name; GstStructure *structure = gst_caps_get_structure(caps, 0); name = gst_structure_get_name(structure); if (!(IS_MPEG_AUDIO(name))) { GST_ERROR_OBJECT(self, "Unexpected format %s, " "was expecting mp3", name); return FALSE; } /* 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; return TRUE; } static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self, GstCaps *caps) { gchar buf[BT_SUGGESTED_BUFFER_SIZE]; struct bt_open_req *open_req = (void *) buf; struct bt_open_rsp *open_rsp = (void *) buf; struct bt_set_configuration_req *req = (void *) buf; struct bt_set_configuration_rsp *rsp = (void *) buf; gboolean ret; GIOError io_error; gchar *temp; GstStructure *structure; codec_capabilities_t *codec = NULL; temp = gst_caps_to_string(caps); GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp); g_free(temp); structure = gst_caps_get_structure(caps, 0); if (gst_structure_has_name(structure, "audio/x-sbc")) codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK); else if (gst_structure_has_name(structure, "audio/mpeg")) codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK); if (codec == NULL) { GST_ERROR_OBJECT(self, "Couldn't parse caps " "to packet configuration"); return FALSE; } memset(req, 0, BT_SUGGESTED_BUFFER_SIZE); open_req->h.type = BT_REQUEST; open_req->h.name = BT_OPEN; open_req->h.length = sizeof(*open_req); strncpy(open_req->destination, self->device, 18); open_req->seid = codec->seid; open_req->lock = BT_WRITE_LOCK; io_error = gst_avdtp_sink_audioservice_send(self, &open_req->h); if (io_error != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error ocurred while sending " "open packet"); return FALSE; } open_rsp->h.length = sizeof(*open_rsp); io_error = gst_avdtp_sink_audioservice_expect(self, &open_rsp->h, BT_OPEN); if (io_error != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error while receiving device " "confirmation"); return FALSE; } memset(req, 0, sizeof(buf)); req->h.type = BT_REQUEST; req->h.name = BT_SET_CONFIGURATION; req->h.length = sizeof(*req); if (codec->type == BT_A2DP_SBC_SINK) ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps, (void *) &req->codec); else ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps, (void *) &req->codec); if (!ret) { GST_ERROR_OBJECT(self, "Couldn't parse caps " "to packet configuration"); return FALSE; } req->h.length += req->codec.length - sizeof(req->codec); 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; } rsp->h.length = sizeof(*rsp); io_error = gst_avdtp_sink_audioservice_expect(self, &rsp->h, BT_SET_CONFIGURATION); if (io_error != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error while receiving device " "confirmation"); return FALSE; } self->data->link_mtu = rsp->link_mtu; 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)); g_object_class_install_property(object_class, PROP_AUTOCONNECT, g_param_spec_boolean("auto-connect", "Auto-connect", "Automatically attempt to connect " "to device", DEFAULT_AUTOCONNECT, G_PARAM_READWRITE)); GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0, "A2DP headset 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->autoconnect = DEFAULT_AUTOCONNECT; self->sink_lock = g_mutex_new(); /* FIXME this is for not synchronizing with clock, should be tested * with devices to see the behaviour gst_base_sink_set_sync(GST_BASE_SINK(self), FALSE); */ } static GIOError gst_avdtp_sink_audioservice_send( GstAvdtpSink *self, const bt_audio_msg_header_t *msg) { GIOError error; gsize written; const char *type, *name; uint16_t length; length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE; error = g_io_channel_write(self->server, (const gchar *) msg, length, &written); if (error != G_IO_ERROR_NONE) GST_ERROR_OBJECT(self, "Error sending data to audio service:" " %s(%d)", strerror(errno), errno); type = bt_audio_strtype(msg->type); name = bt_audio_strname(msg->name); GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name); 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, *name; uint16_t length; length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE; status = g_io_channel_read(self->server, (gchar *) inmsg, length, &bytes_read); if (status != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error receiving data from " "audio service"); return status; } type = bt_audio_strtype(inmsg->type); if (!type) { status = G_IO_ERROR_INVAL; GST_ERROR_OBJECT(self, "Bogus message type %d " "received from audio service", inmsg->type); } name = bt_audio_strname(inmsg->name); if (!name) { status = G_IO_ERROR_INVAL; GST_ERROR_OBJECT(self, "Bogus message name %d " "received from audio service", inmsg->name); } if (inmsg->type == BT_ERROR) { bt_audio_error_t *err = (void *) inmsg; status = G_IO_ERROR_INVAL; GST_ERROR_OBJECT(self, "%s failed : " "%s(%d)", name, strerror(err->posix_errno), err->posix_errno); } GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name); return status; } static GIOError gst_avdtp_sink_audioservice_expect( GstAvdtpSink *self, bt_audio_msg_header_t *outmsg, guint8 expected_name) { GIOError status; status = gst_avdtp_sink_audioservice_recv(self, outmsg); if (status != G_IO_ERROR_NONE) return status; if (outmsg->name != expected_name) 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_WARNING_OBJECT(self, "crc changed during stream"); 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_WARNING_OBJECT(self, "channel mode changed during stream"); return; } self->channel_mode = new_mode; if (self->channel_mode == -1) GST_WARNING_OBJECT(self, "Received invalid channel " "mode: %s", mode); }