// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "btio/btio.h" #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/sdp_lib.h" #include "profiles/audio/a2dp-codecs.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/log.h" #include "hal-msg.h" #include "ipc-common.h" #include "ipc.h" #include "a2dp.h" #include "utils.h" #include "bluetooth.h" #include "avdtp.h" #include "avrcp.h" #include "audio-msg.h" #define SVC_HINT_CAPTURING 0x08 #define IDLE_TIMEOUT 1 #define AUDIO_RETRY_TIMEOUT 2 static GIOChannel *server = NULL; static GSList *devices = NULL; static GSList *endpoints = NULL; static GSList *setups = NULL; static bdaddr_t adapter_addr; static uint32_t record_id = 0; static guint audio_retry_id = 0; static bool audio_retrying = false; static struct ipc *hal_ipc = NULL; static struct ipc *audio_ipc = NULL; static struct queue *lseps = NULL; struct a2dp_preset { void *data; int8_t len; }; struct a2dp_endpoint { uint8_t id; uint8_t codec; struct avdtp_local_sep *sep; struct a2dp_preset *caps; GSList *presets; }; struct a2dp_device { bdaddr_t dst; uint8_t state; GIOChannel *io; struct avdtp *session; guint idle_id; }; struct a2dp_setup { struct a2dp_device *dev; struct a2dp_endpoint *endpoint; struct a2dp_preset *preset; struct avdtp_stream *stream; uint8_t state; }; static int device_cmp(gconstpointer s, gconstpointer user_data) { const struct a2dp_device *dev = s; const bdaddr_t *dst = user_data; return bacmp(&dev->dst, dst); } static void preset_free(void *data) { struct a2dp_preset *preset = data; g_free(preset->data); g_free(preset); } static void unregister_endpoint(void *data) { struct a2dp_endpoint *endpoint = data; if (endpoint->sep) avdtp_unregister_sep(lseps, endpoint->sep); if (endpoint->caps) preset_free(endpoint->caps); g_slist_free_full(endpoint->presets, preset_free); g_free(endpoint); } static void setup_free(void *data) { struct a2dp_setup *setup = data; if (!g_slist_find(setup->endpoint->presets, setup->preset)) preset_free(setup->preset); g_free(setup); } static void setup_remove(struct a2dp_setup *setup) { setups = g_slist_remove(setups, setup); setup_free(setup); } static void setup_remove_all_by_dev(struct a2dp_device *dev) { GSList *l = setups; while (l) { struct a2dp_setup *setup = l->data; GSList *next = g_slist_next(l); if (setup->dev == dev) setup_remove(setup); l = next; } } static void a2dp_device_free(void *data) { struct a2dp_device *dev = data; if (dev->idle_id > 0) g_source_remove(dev->idle_id); if (dev->session) avdtp_unref(dev->session); if (dev->io) { g_io_channel_shutdown(dev->io, FALSE, NULL); g_io_channel_unref(dev->io); } setup_remove_all_by_dev(dev); g_free(dev); } static void a2dp_device_remove(struct a2dp_device *dev) { devices = g_slist_remove(devices, dev); a2dp_device_free(dev); } static struct a2dp_device *a2dp_device_new(const bdaddr_t *dst) { struct a2dp_device *dev; dev = g_new0(struct a2dp_device, 1); bacpy(&dev->dst, dst); devices = g_slist_prepend(devices, dev); return dev; } static bool a2dp_device_connect(struct a2dp_device *dev, BtIOConnect cb) { GError *err = NULL; dev->io = bt_io_connect(cb, dev, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_DEST_BDADDR, &dev->dst, BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); return false; } return true; } static void bt_a2dp_notify_state(struct a2dp_device *dev, uint8_t state) { struct hal_ev_a2dp_conn_state ev; char address[18]; if (dev->state == state) return; dev->state = state; ba2str(&dev->dst, address); DBG("device %s state %u", address, state); bdaddr2android(&dev->dst, ev.bdaddr); ev.state = state; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_EV_A2DP_CONN_STATE, sizeof(ev), &ev); if (state != HAL_A2DP_STATE_DISCONNECTED) return; bt_avrcp_disconnect(&dev->dst); a2dp_device_remove(dev); } static void bt_audio_notify_state(struct a2dp_setup *setup, uint8_t state) { struct hal_ev_a2dp_audio_state ev; char address[18]; if (setup->state == state) return; setup->state = state; ba2str(&setup->dev->dst, address); DBG("device %s state %u", address, state); bdaddr2android(&setup->dev->dst, ev.bdaddr); ev.state = state; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_EV_A2DP_AUDIO_STATE, sizeof(ev), &ev); } static void disconnect_cb(void *user_data) { struct a2dp_device *dev = user_data; bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); } static int sbc_check_config(void *caps, uint8_t caps_len, void *conf, uint8_t conf_len) { a2dp_sbc_t *cap, *config; if (conf_len != caps_len || conf_len != sizeof(a2dp_sbc_t)) { error("SBC: Invalid configuration size (%u)", conf_len); return -EINVAL; } cap = caps; config = conf; if (!(cap->frequency & config->frequency)) { error("SBC: Unsupported frequency (%u) by endpoint", config->frequency); return -EINVAL; } if (!(cap->channel_mode & config->channel_mode)) { error("SBC: Unsupported channel mode (%u) by endpoint", config->channel_mode); return -EINVAL; } if (!(cap->block_length & config->block_length)) { error("SBC: Unsupported block length (%u) by endpoint", config->block_length); return -EINVAL; } if (!(cap->allocation_method & config->allocation_method)) { error("SBC: Unsupported allocation method (%u) by endpoint", config->block_length); return -EINVAL; } if (config->max_bitpool < cap->min_bitpool) { error("SBC: Invalid maximun bitpool (%u < %u)", config->max_bitpool, cap->min_bitpool); return -EINVAL; } if (config->min_bitpool > cap->max_bitpool) { error("SBC: Invalid minimun bitpool (%u > %u)", config->min_bitpool, cap->min_bitpool); return -EINVAL; } if (config->max_bitpool > cap->max_bitpool) return -ERANGE; if (config->min_bitpool < cap->min_bitpool) return -ERANGE; return 0; } static int aac_check_config(void *caps, uint8_t caps_len, void *conf, uint8_t conf_len) { a2dp_aac_t *cap, *config; if (conf_len != caps_len || conf_len != sizeof(a2dp_aac_t)) { error("AAC: Invalid configuration size (%u)", conf_len); return -EINVAL; } cap = caps; config = conf; if (!(cap->object_type & config->object_type)) { error("AAC: Unsupported object type (%u) by endpoint", config->object_type); return -EINVAL; } if (!(AAC_GET_FREQUENCY(*cap) & AAC_GET_FREQUENCY(*config))) { error("AAC: Unsupported frequency (%u) by endpoint", AAC_GET_FREQUENCY(*config)); return -EINVAL; } if (!(cap->channels & config->channels)) { error("AAC: Unsupported channels (%u) by endpoint", config->channels); return -EINVAL; } /* VBR support in SNK is mandatory but let's make sure we don't try to * have VBR on remote which for some reason does not support it */ if (!cap->vbr && config->vbr) { error("AAC: Unsupported VBR (%u) by endpoint", config->vbr); return -EINVAL; } if (AAC_GET_BITRATE(*cap) < AAC_GET_BITRATE(*config)) return -ERANGE; return 0; } static int aptx_check_config(void *caps, uint8_t caps_len, void *conf, uint8_t conf_len) { a2dp_aptx_t *cap, *config; if (conf_len != caps_len || conf_len != sizeof(a2dp_aptx_t)) { error("APTX: Invalid configuration size (%u)", conf_len); return -EINVAL; } cap = caps; config = conf; if (!(cap->frequency & config->frequency)) { error("APTX: Unsupported frequenct (%u) by endpoint", config->frequency); return -EINVAL; } if (!(cap->channel_mode & config->channel_mode)) { error("APTX: Unsupported channel mode (%u) by endpoint", config->channel_mode); return -EINVAL; } return 0; } static int check_capabilities(struct a2dp_preset *preset, struct avdtp_media_codec_capability *codec, uint8_t codec_len) { a2dp_vendor_codec_t *vndcodec; /* Codec specific */ switch (codec->media_codec_type) { case A2DP_CODEC_SBC: return sbc_check_config(codec->data, codec_len, preset->data, preset->len); case A2DP_CODEC_MPEG24: return aac_check_config(codec->data, codec_len, preset->data, preset->len); case A2DP_CODEC_VENDOR: vndcodec = (void *) codec->data; if (A2DP_GET_VENDOR_ID(*vndcodec) == APTX_VENDOR_ID && A2DP_GET_CODEC_ID(*vndcodec) == APTX_CODEC_ID) return aptx_check_config(codec->data, codec_len, preset->data, preset->len); return -EINVAL; default: return -EINVAL; } } static struct a2dp_preset *sbc_select_range(void *caps, uint8_t caps_len, void *conf, uint8_t conf_len) { struct a2dp_preset *p; a2dp_sbc_t *cap, *config; cap = caps; config = conf; config->min_bitpool = MAX(config->min_bitpool, cap->min_bitpool); config->max_bitpool = MIN(config->max_bitpool, cap->max_bitpool); p = g_new0(struct a2dp_preset, 1); p->len = conf_len; p->data = util_memdup(conf, p->len); return p; } static struct a2dp_preset *aac_select_range(void *caps, uint8_t caps_len, void *conf, uint8_t conf_len) { struct a2dp_preset *p; a2dp_aac_t *cap, *config; uint32_t bitrate; cap = caps; config = conf; bitrate = MIN(AAC_GET_BITRATE(*cap), AAC_GET_BITRATE(*config)); AAC_SET_BITRATE(*config, bitrate); p = g_new0(struct a2dp_preset, 1); p->len = conf_len; p->data = util_memdup(conf, p->len); return p; } static struct a2dp_preset *select_preset_range(struct a2dp_preset *preset, struct avdtp_media_codec_capability *codec, uint8_t codec_len) { /* Codec specific */ switch (codec->media_codec_type) { case A2DP_CODEC_SBC: return sbc_select_range(codec->data, codec_len, preset->data, preset->len); case A2DP_CODEC_MPEG24: return aac_select_range(codec->data, codec_len, preset->data, preset->len); default: return NULL; } } static struct a2dp_preset *select_preset(struct a2dp_endpoint *endpoint, struct avdtp_remote_sep *rsep) { struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; GSList *l; uint8_t codec_len; service = avdtp_get_codec(rsep); codec = (struct avdtp_media_codec_capability *) service->data; codec_len = service->length - sizeof(*codec); for (l = endpoint->presets; l; l = g_slist_next(l)) { struct a2dp_preset *preset = l->data; int err; err = check_capabilities(preset, codec, codec_len); if (err == 0) return preset; if (err == -ERANGE) return select_preset_range(preset, codec, codec_len); } return NULL; } static void setup_add(struct a2dp_device *dev, struct a2dp_endpoint *endpoint, struct a2dp_preset *preset, struct avdtp_stream *stream) { struct a2dp_setup *setup; setup = g_new0(struct a2dp_setup, 1); setup->dev = dev; setup->endpoint = endpoint; setup->preset = preset; setup->stream = stream; setups = g_slist_append(setups, setup); if (dev->idle_id > 0) { g_source_remove(dev->idle_id); dev->idle_id = 0; } } static int select_configuration(struct a2dp_device *dev, struct a2dp_endpoint *endpoint, struct avdtp_remote_sep *rsep) { struct a2dp_preset *preset; struct avdtp_stream *stream; struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; GSList *caps; int err; preset = select_preset(endpoint, rsep); if (!preset) { error("Unable to select codec preset"); return -EINVAL; } service = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); caps = g_slist_append(NULL, service); codec = g_malloc0(sizeof(*codec) + preset->len); codec->media_type = AVDTP_MEDIA_TYPE_AUDIO; codec->media_codec_type = endpoint->codec; memcpy(codec->data, preset->data, preset->len); service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec, sizeof(*codec) + preset->len); caps = g_slist_append(caps, service); g_free(codec); err = avdtp_set_configuration(dev->session, rsep, endpoint->sep, caps, &stream); g_slist_free_full(caps, g_free); if (err < 0) { error("avdtp_set_configuration: %s", strerror(-err)); return err; } setup_add(dev, endpoint, preset, stream); return 0; } static void discover_cb(struct avdtp *session, GSList *seps, struct avdtp_error *err, void *user_data) { struct a2dp_device *dev = user_data; struct a2dp_endpoint *endpoint = NULL; struct avdtp_remote_sep *rsep = NULL; GSList *l; for (l = endpoints; l; l = g_slist_next(l)) { endpoint = l->data; rsep = avdtp_find_remote_sep(session, endpoint->sep); if (rsep) break; } if (!rsep) { error("Unable to find matching endpoint"); goto failed; } if (select_configuration(dev, endpoint, rsep) < 0) goto failed; return; failed: avdtp_shutdown(session); } static gboolean idle_timeout(gpointer user_data) { struct a2dp_device *dev = user_data; int err; dev->idle_id = 0; err = avdtp_discover(dev->session, discover_cb, dev); if (err == 0) return FALSE; error("avdtp_discover: %s", strerror(-err)); bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); return FALSE; } static void signaling_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct a2dp_device *dev = user_data; struct avdtp *session; uint16_t imtu, omtu; GError *gerr = NULL; int fd; if (err) { bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); error("%s", err->message); return; } bt_io_get(chan, &gerr, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); goto failed; } fd = g_io_channel_unix_get_fd(chan); /* FIXME: Add proper version */ session = avdtp_new(fd, imtu, omtu, 0x0100, lseps); if (!session) goto failed; dev->session = session; avdtp_add_disconnect_cb(dev->session, disconnect_cb, dev); /* Proceed to stream setup if initiator */ if (dev->io) { int perr; g_io_channel_unref(dev->io); dev->io = NULL; perr = avdtp_discover(dev->session, discover_cb, dev); if (perr < 0) { error("avdtp_discover: %s", strerror(-perr)); goto failed; } bt_avrcp_connect(&dev->dst); } else /* Init idle timeout to discover */ dev->idle_id = g_timeout_add_seconds(IDLE_TIMEOUT, idle_timeout, dev); return; failed: bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); } static void bt_a2dp_connect(const void *buf, uint16_t len) { const struct hal_cmd_a2dp_connect *cmd = buf; struct a2dp_device *dev; uint8_t status; char addr[18]; bdaddr_t dst; GSList *l; DBG(""); android2bdaddr(&cmd->bdaddr, &dst); l = g_slist_find_custom(devices, &dst, device_cmp); if (l) { status = HAL_STATUS_FAILED; goto failed; } dev = a2dp_device_new(&dst); if (!a2dp_device_connect(dev, signaling_connect_cb)) { a2dp_device_remove(dev); status = HAL_STATUS_FAILED; goto failed; } ba2str(&dev->dst, addr); DBG("connecting to %s", addr); bt_a2dp_notify_state(dev, HAL_A2DP_STATE_CONNECTING); status = HAL_STATUS_SUCCESS; failed: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_OP_A2DP_CONNECT, status); } static void bt_a2dp_disconnect(const void *buf, uint16_t len) { const struct hal_cmd_a2dp_connect *cmd = buf; uint8_t status; struct a2dp_device *dev; GSList *l; bdaddr_t dst; DBG(""); android2bdaddr(&cmd->bdaddr, &dst); l = g_slist_find_custom(devices, &dst, device_cmp); if (!l) { status = HAL_STATUS_FAILED; goto failed; } dev = l->data; status = HAL_STATUS_SUCCESS; if (dev->io) { bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); goto failed; } /* Wait AVDTP session to shutdown */ avdtp_shutdown(dev->session); bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTING); failed: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_OP_A2DP_DISCONNECT, status); } static const struct ipc_handler cmd_handlers[] = { /* HAL_OP_A2DP_CONNECT */ { bt_a2dp_connect, false, sizeof(struct hal_cmd_a2dp_connect) }, /* HAL_OP_A2DP_DISCONNECT */ { bt_a2dp_disconnect, false, sizeof(struct hal_cmd_a2dp_disconnect) }, }; static struct a2dp_setup *find_setup_by_device(struct a2dp_device *dev) { GSList *l; for (l = setups; l; l = g_slist_next(l)) { struct a2dp_setup *setup = l->data; if (setup->dev == dev) return setup; } return NULL; } static void transport_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct a2dp_device *dev = user_data; struct a2dp_setup *setup; uint16_t imtu, omtu; GError *gerr = NULL; int fd; if (err) { error("%s", err->message); return; } setup = find_setup_by_device(dev); if (!setup) { error("Unable to find stream setup"); return; } bt_io_get(chan, &gerr, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); return; } fd = g_io_channel_unix_get_fd(chan); if (!avdtp_stream_set_transport(setup->stream, fd, imtu, omtu)) { error("avdtp_stream_set_transport: failed"); return; } g_io_channel_set_close_on_unref(chan, FALSE); if (dev->io) { g_io_channel_unref(dev->io); dev->io = NULL; } bt_a2dp_notify_state(dev, HAL_A2DP_STATE_CONNECTED); } static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct a2dp_device *dev; bdaddr_t dst; char address[18]; GError *gerr = NULL; GSList *l; if (err) { error("%s", err->message); return; } bt_io_get(chan, &gerr, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); g_io_channel_shutdown(chan, TRUE, NULL); return; } ba2str(&dst, address); DBG("Incoming connection from %s", address); l = g_slist_find_custom(devices, &dst, device_cmp); if (l) { transport_connect_cb(chan, err, l->data); return; } dev = a2dp_device_new(&dst); bt_a2dp_notify_state(dev, HAL_A2DP_STATE_CONNECTING); signaling_connect_cb(chan, err, dev); } static sdp_record_t *a2dp_record(void) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *psm, *version, *features; uint16_t lp = AVDTP_UUID; uint16_t a2dp_ver = 0x0103, avdtp_ver = 0x0103, feat = 0x000f; record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &a2dp_uuid); sdp_set_service_classes(record, svclass_id); sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); profile[0].version = a2dp_ver; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID); proto[1] = sdp_list_append(NULL, &avdtp_uuid); version = sdp_data_alloc(SDP_UINT16, &avdtp_ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(record, aproto); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(record, "Audio Source", NULL, NULL); sdp_data_free(psm); sdp_data_free(version); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(pfseq, NULL); sdp_list_free(aproto, NULL); sdp_list_free(root, NULL); sdp_list_free(svclass_id, NULL); return record; } static gboolean sep_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep, GSList **caps, uint8_t *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_preset *cap = endpoint->caps; struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; *caps = NULL; service = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); *caps = g_slist_append(*caps, service); codec = g_malloc0(sizeof(*codec) + cap->len); codec->media_type = AVDTP_MEDIA_TYPE_AUDIO; codec->media_codec_type = endpoint->codec; memcpy(codec->data, cap->data, cap->len); service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec, sizeof(*codec) + cap->len); *caps = g_slist_append(*caps, service); g_free(codec); return TRUE; } static int check_config(struct a2dp_endpoint *endpoint, struct a2dp_preset *config) { GSList *l; struct a2dp_preset *caps; for (l = endpoint->presets; l; l = g_slist_next(l)) { struct a2dp_preset *preset = l->data; if (preset->len != config->len) continue; if (memcmp(preset->data, config->data, preset->len) == 0) return 0; } caps = endpoint->caps; /* Codec specific */ switch (endpoint->codec) { case A2DP_CODEC_SBC: return sbc_check_config(caps->data, caps->len, config->data, config->len); default: return -EINVAL; } } static struct a2dp_device *find_device_by_session(struct avdtp *session) { GSList *l; for (l = devices; l; l = g_slist_next(l)) { struct a2dp_device *dev = l->data; if (dev->session == session) return dev; } return NULL; } static struct a2dp_setup *find_setup(uint8_t id) { GSList *l; for (l = setups; l; l = g_slist_next(l)) { struct a2dp_setup *setup = l->data; if (setup->endpoint->id == id) return setup; } return NULL; } static void setup_remove_by_id(uint8_t id) { struct a2dp_setup *setup; setup = find_setup(id); if (!setup) { error("Unable to find stream setup for endpoint %u", id); return; } setup_remove(setup); } static gboolean sep_setconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, GSList *caps, avdtp_set_configuration_cb cb, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_device *dev; struct a2dp_preset *preset = NULL; DBG(""); dev = find_device_by_session(session); if (!dev) { error("Unable to find device for session %p", session); return FALSE; } for (; caps != NULL; caps = g_slist_next(caps)) { struct avdtp_service_capability *cap = caps->data; struct avdtp_media_codec_capability *codec; if (cap->category == AVDTP_DELAY_REPORTING) return FALSE; if (cap->category != AVDTP_MEDIA_CODEC) continue; codec = (struct avdtp_media_codec_capability *) cap->data; if (codec->media_codec_type != endpoint->codec) return FALSE; preset = g_new0(struct a2dp_preset, 1); preset->len = cap->length - sizeof(*codec); preset->data = util_memdup(codec->data, preset->len); if (check_config(endpoint, preset) < 0) { preset_free(preset); return FALSE; } } if (!preset) return FALSE; setup_add(dev, endpoint, preset, stream); cb(session, stream, NULL); return TRUE; } static gboolean sep_open_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for endpoint %u", endpoint->id); *err = AVDTP_SEP_NOT_IN_USE; return FALSE; } return TRUE; } static gboolean sep_close_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for endpoint %u", endpoint->id); *err = AVDTP_SEP_NOT_IN_USE; return FALSE; } bt_audio_notify_state(setup, HAL_AUDIO_STOPPED); setup_remove(setup); return TRUE; } static gboolean sep_start_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for endpoint %u", endpoint->id); *err = AVDTP_SEP_NOT_IN_USE; return FALSE; } bt_audio_notify_state(setup, HAL_AUDIO_STARTED); return TRUE; } static gboolean sep_suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for endpoint %u", endpoint->id); *err = AVDTP_SEP_NOT_IN_USE; return FALSE; } bt_audio_notify_state(setup, HAL_AUDIO_SUSPEND); return TRUE; } static struct avdtp_sep_ind sep_ind = { .get_capability = sep_getcap_ind, .set_configuration = sep_setconf_ind, .open = sep_open_ind, .close = sep_close_ind, .start = sep_start_ind, .suspend = sep_suspend_ind, }; static void sep_setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; int ret; DBG(""); setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for endpoint %u", endpoint->id); return; } if (err) goto failed; ret = avdtp_open(session, stream); if (ret < 0) { error("avdtp_open: %s", strerror(-ret)); goto failed; } return; failed: setup_remove(setup); } static void sep_open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_device *dev; DBG(""); if (err) goto failed; dev = find_device_by_session(session); if (!dev) { error("Unable to find device for session"); goto failed; } a2dp_device_connect(dev, transport_connect_cb); return; failed: setup_remove_by_id(endpoint->id); } static void sep_start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); if (err) { setup_remove_by_id(endpoint->id); return; } setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for %u endpoint", endpoint->id); return; } bt_audio_notify_state(setup, HAL_AUDIO_STARTED); } static void sep_suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); if (err) { setup_remove_by_id(endpoint->id); return; } setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for %u endpoint", endpoint->id); return; } bt_audio_notify_state(setup, HAL_AUDIO_STOPPED); } static void sep_close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; struct a2dp_setup *setup; DBG(""); if (err) return; setup = find_setup(endpoint->id); if (!setup) { error("Unable to find stream setup for %u endpoint", endpoint->id); return; } bt_audio_notify_state(setup, HAL_AUDIO_STOPPED); setup_remove(setup); } static void sep_abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_endpoint *endpoint = user_data; DBG(""); if (err) return; setup_remove_by_id(endpoint->id); } static struct avdtp_sep_cfm sep_cfm = { .set_configuration = sep_setconf_cfm, .open = sep_open_cfm, .start = sep_start_cfm, .suspend = sep_suspend_cfm, .close = sep_close_cfm, .abort = sep_abort_cfm, }; static uint8_t register_endpoint(const uint8_t *uuid, uint8_t codec, GSList *presets) { struct a2dp_endpoint *endpoint; /* FIXME: Add proper check for uuid */ endpoint = g_new0(struct a2dp_endpoint, 1); endpoint->id = g_slist_length(endpoints) + 1; endpoint->codec = codec; endpoint->sep = avdtp_register_sep(lseps, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO, codec, FALSE, &sep_ind, &sep_cfm, endpoint); endpoint->caps = presets->data; endpoint->presets = g_slist_copy(g_slist_nth(presets, 1)); if (endpoint->codec == A2DP_CODEC_VENDOR) { a2dp_vendor_codec_t *vndcodec = (void *) endpoint->caps->data; avdtp_sep_set_vendor_codec(endpoint->sep, A2DP_GET_VENDOR_ID(*vndcodec), A2DP_GET_CODEC_ID(*vndcodec)); } endpoints = g_slist_append(endpoints, endpoint); return endpoint->id; } static GSList *parse_presets(const struct audio_preset *p, uint8_t count, uint16_t len) { GSList *l = NULL; uint8_t i; for (i = 0; count > i; i++) { const uint8_t *ptr = (const uint8_t *) p; struct a2dp_preset *preset; if (len < sizeof(struct audio_preset)) { DBG("Invalid preset index %u", i); g_slist_free_full(l, preset_free); return NULL; } len -= sizeof(struct audio_preset); if (len == 0 || len < p->len) { DBG("Invalid preset size of %u for index %u", len, i); g_slist_free_full(l, preset_free); return NULL; } preset = g_new0(struct a2dp_preset, 1); preset->len = p->len; preset->data = util_memdup(p->data, preset->len); l = g_slist_append(l, preset); len -= preset->len; ptr += sizeof(*p) + preset->len; p = (const struct audio_preset *) ptr; } return l; } static void bt_audio_open(const void *buf, uint16_t len) { const struct audio_cmd_open *cmd = buf; struct audio_rsp_open rsp; GSList *presets; DBG(""); audio_retrying = false; if (cmd->presets == 0) { error("No audio presets found"); goto failed; } presets = parse_presets(cmd->preset, cmd->presets, len - sizeof(*cmd)); if (!presets) { error("No audio presets found"); goto failed; } rsp.id = register_endpoint(cmd->uuid, cmd->codec, presets); if (rsp.id == 0) { g_slist_free_full(presets, preset_free); error("Unable to register endpoint"); goto failed; } g_slist_free(presets); ipc_send_rsp_full(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN, sizeof(rsp), &rsp, -1); return; failed: ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN, AUDIO_STATUS_FAILED); } static struct a2dp_endpoint *find_endpoint(uint8_t id) { GSList *l; for (l = endpoints; l; l = g_slist_next(l)) { struct a2dp_endpoint *endpoint = l->data; if (endpoint->id == id) return endpoint; } return NULL; } static void bt_audio_close(const void *buf, uint16_t len) { const struct audio_cmd_close *cmd = buf; struct a2dp_endpoint *endpoint; DBG(""); endpoint = find_endpoint(cmd->id); if (!endpoint) { error("Unable to find endpoint %u", cmd->id); ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE, AUDIO_STATUS_FAILED); return; } endpoints = g_slist_remove(endpoints, endpoint); unregister_endpoint(endpoint); ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE, AUDIO_STATUS_SUCCESS); } static void bt_stream_open(const void *buf, uint16_t len) { const struct audio_cmd_open_stream *cmd = buf; struct audio_rsp_open_stream *rsp; struct a2dp_setup *setup; int fd; uint16_t omtu; DBG(""); if (cmd->id) setup = find_setup(cmd->id); else setup = setups ? setups->data : NULL; if (!setup) { error("Unable to find stream for endpoint %u", cmd->id); ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, AUDIO_STATUS_FAILED); return; } if (!avdtp_stream_get_transport(setup->stream, &fd, NULL, &omtu, NULL)) { error("avdtp_stream_get_transport: failed"); ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, AUDIO_STATUS_FAILED); return; } len = sizeof(struct audio_rsp_open_stream) + sizeof(struct audio_preset) + setup->preset->len; rsp = g_malloc0(len); rsp->id = setup->endpoint->id; rsp->mtu = omtu; rsp->preset->len = setup->preset->len; memcpy(rsp->preset->data, setup->preset->data, setup->preset->len); ipc_send_rsp_full(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, len, rsp, fd); g_free(rsp); } static void bt_stream_close(const void *buf, uint16_t len) { const struct audio_cmd_close_stream *cmd = buf; struct a2dp_setup *setup; int err; DBG(""); setup = find_setup(cmd->id); if (!setup) { error("Unable to find stream for endpoint %u", cmd->id); goto failed; } err = avdtp_close(setup->dev->session, setup->stream, FALSE); if (err < 0) { error("avdtp_close: %s", strerror(-err)); goto failed; } ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE_STREAM, AUDIO_STATUS_SUCCESS); return; failed: ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE_STREAM, AUDIO_STATUS_FAILED); } static void bt_stream_resume(const void *buf, uint16_t len) { const struct audio_cmd_resume_stream *cmd = buf; struct a2dp_setup *setup; int err; DBG(""); setup = find_setup(cmd->id); if (!setup) { error("Unable to find stream for endpoint %u", cmd->id); goto failed; } if (setup->state != HAL_AUDIO_STARTED) { err = avdtp_start(setup->dev->session, setup->stream); if (err < 0) { error("avdtp_start: %s", strerror(-err)); goto failed; } } ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_RESUME_STREAM, AUDIO_STATUS_SUCCESS); return; failed: ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_RESUME_STREAM, AUDIO_STATUS_FAILED); } static void bt_stream_suspend(const void *buf, uint16_t len) { const struct audio_cmd_suspend_stream *cmd = buf; struct a2dp_setup *setup; int err; DBG(""); setup = find_setup(cmd->id); if (!setup) { error("Unable to find stream for endpoint %u", cmd->id); goto failed; } err = avdtp_suspend(setup->dev->session, setup->stream); if (err < 0) { error("avdtp_suspend: %s", strerror(-err)); goto failed; } ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_SUSPEND_STREAM, AUDIO_STATUS_SUCCESS); return; failed: ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_SUSPEND_STREAM, AUDIO_STATUS_FAILED); } static const struct ipc_handler audio_handlers[] = { /* AUDIO_OP_OPEN */ { bt_audio_open, true, sizeof(struct audio_cmd_open) }, /* AUDIO_OP_CLOSE */ { bt_audio_close, false, sizeof(struct audio_cmd_close) }, /* AUDIO_OP_OPEN_STREAM */ { bt_stream_open, false, sizeof(struct audio_cmd_open_stream) }, /* AUDIO_OP_CLOSE_STREAM */ { bt_stream_close, false, sizeof(struct audio_cmd_close_stream) }, /* AUDIO_OP_RESUME_STREAM */ { bt_stream_resume, false, sizeof(struct audio_cmd_resume_stream) }, /* AUDIO_OP_SUSPEND_STREAM */ { bt_stream_suspend, false, sizeof(struct audio_cmd_suspend_stream) }, }; static void bt_audio_unregister(void) { DBG(""); if (audio_retry_id > 0) g_source_remove(audio_retry_id); g_slist_free_full(endpoints, unregister_endpoint); endpoints = NULL; g_slist_free_full(setups, setup_free); setups = NULL; ipc_cleanup(audio_ipc); audio_ipc = NULL; queue_destroy(lseps, NULL); } static bool bt_audio_register(ipc_disconnect_cb disconnect) { DBG(""); audio_ipc = ipc_init(BLUEZ_AUDIO_SK_PATH, sizeof(BLUEZ_AUDIO_SK_PATH), AUDIO_SERVICE_ID_MAX, false, disconnect, NULL); if (!audio_ipc) return false; ipc_register(audio_ipc, AUDIO_SERVICE_ID, audio_handlers, G_N_ELEMENTS(audio_handlers)); return true; } static gboolean audio_retry_register(void *data) { ipc_disconnect_cb cb = data; audio_retry_id = 0; audio_retrying = true; bt_audio_register(cb); return FALSE; } static void audio_disconnected(void *data) { GSList *l; bool restart; DBG(""); if (audio_retrying) goto retry; restart = endpoints != NULL ? true : false; bt_audio_unregister(); for (l = devices; l; l = g_slist_next(l)) { struct a2dp_device *dev = l->data; avdtp_shutdown(dev->session); } if (!restart) return; retry: audio_retry_id = g_timeout_add_seconds(AUDIO_RETRY_TIMEOUT, audio_retry_register, audio_disconnected); } bool bt_a2dp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) { GError *err = NULL; sdp_record_t *rec; DBG(""); bacpy(&adapter_addr, addr); lseps = queue_new(); server = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_CENTRAL, true, BT_IO_OPT_INVALID); if (!server) { error("Failed to listen on AVDTP channel: %s", err->message); g_error_free(err); return false; } rec = a2dp_record(); if (!rec) { error("Failed to allocate A2DP record"); goto fail; } if (bt_adapter_add_record(rec, SVC_HINT_CAPTURING) < 0) { error("Failed to register A2DP record"); sdp_record_free(rec); goto fail; } record_id = rec->handle; hal_ipc = ipc; ipc_register(hal_ipc, HAL_SERVICE_ID_A2DP, cmd_handlers, G_N_ELEMENTS(cmd_handlers)); if (bt_audio_register(audio_disconnected)) return true; fail: g_io_channel_shutdown(server, TRUE, NULL); g_io_channel_unref(server); server = NULL; return false; } void bt_a2dp_unregister(void) { DBG(""); g_slist_free_full(setups, setup_free); setups = NULL; g_slist_free_full(endpoints, unregister_endpoint); endpoints = NULL; g_slist_free_full(devices, a2dp_device_free); devices = NULL; ipc_unregister(hal_ipc, HAL_SERVICE_ID_A2DP); hal_ipc = NULL; bt_adapter_remove_record(record_id); record_id = 0; if (server) { g_io_channel_shutdown(server, TRUE, NULL); g_io_channel_unref(server); server = NULL; } if (audio_ipc) { ipc_unregister(audio_ipc, AUDIO_SERVICE_ID); ipc_cleanup(audio_ipc); audio_ipc = NULL; } }