/* Copyright (c) 2013 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include "a2dp-codecs.h" #include "cras_a2dp_endpoint.h" #include "cras_a2dp_iodev.h" #include "cras_iodev.h" #include "cras_bt_constants.h" #include "cras_bt_endpoint.h" #include "cras_bt_log.h" #include "cras_system_state.h" #include "cras_util.h" #define A2DP_SOURCE_ENDPOINT_PATH "/org/chromium/Cras/Bluetooth/A2DPSource" #define A2DP_SINK_ENDPOINT_PATH "/org/chromium/Cras/Bluetooth/A2DPSink" /* Pointers for the only connected a2dp device. */ static struct a2dp { struct cras_iodev *iodev; struct cras_bt_device *device; } connected_a2dp; static int cras_a2dp_get_capabilities(struct cras_bt_endpoint *endpoint, void *capabilities, int *len) { a2dp_sbc_t *sbc_caps = capabilities; if (*len < sizeof(*sbc_caps)) return -ENOSPC; *len = sizeof(*sbc_caps); /* Return all capabilities. */ sbc_caps->channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO; sbc_caps->frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000; sbc_caps->allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS; sbc_caps->subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; sbc_caps->block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; sbc_caps->min_bitpool = MIN_BITPOOL; sbc_caps->max_bitpool = MAX_BITPOOL; return 0; } static int cras_a2dp_select_configuration(struct cras_bt_endpoint *endpoint, void *capabilities, int len, void *configuration) { a2dp_sbc_t *sbc_caps = capabilities; a2dp_sbc_t *sbc_config = configuration; /* Pick the highest configuration. */ if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) { sbc_config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; } else if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_STEREO) { sbc_config->channel_mode = SBC_CHANNEL_MODE_STEREO; } else if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) { sbc_config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; } else if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_MONO) { sbc_config->channel_mode = SBC_CHANNEL_MODE_MONO; } else { syslog(LOG_WARNING, "No supported channel modes."); return -ENOSYS; } if (sbc_caps->frequency & SBC_SAMPLING_FREQ_48000) { sbc_config->frequency = SBC_SAMPLING_FREQ_48000; } else if (sbc_caps->frequency & SBC_SAMPLING_FREQ_44100) { sbc_config->frequency = SBC_SAMPLING_FREQ_44100; } else if (sbc_caps->frequency & SBC_SAMPLING_FREQ_32000) { sbc_config->frequency = SBC_SAMPLING_FREQ_32000; } else if (sbc_caps->frequency & SBC_SAMPLING_FREQ_16000) { sbc_config->frequency = SBC_SAMPLING_FREQ_16000; } else { syslog(LOG_WARNING, "No supported sampling frequencies."); return -ENOSYS; } if (sbc_caps->allocation_method & SBC_ALLOCATION_LOUDNESS) { sbc_config->allocation_method = SBC_ALLOCATION_LOUDNESS; } else if (sbc_caps->allocation_method & SBC_ALLOCATION_SNR) { sbc_config->allocation_method = SBC_ALLOCATION_SNR; } else { syslog(LOG_WARNING, "No supported allocation method."); return -ENOSYS; } if (sbc_caps->subbands & SBC_SUBBANDS_8) { sbc_config->subbands = SBC_SUBBANDS_8; } else if (sbc_caps->subbands & SBC_SUBBANDS_4) { sbc_config->subbands = SBC_SUBBANDS_4; } else { syslog(LOG_WARNING, "No supported subbands."); return -ENOSYS; } if (sbc_caps->block_length & SBC_BLOCK_LENGTH_16) { sbc_config->block_length = SBC_BLOCK_LENGTH_16; } else if (sbc_caps->block_length & SBC_BLOCK_LENGTH_12) { sbc_config->block_length = SBC_BLOCK_LENGTH_12; } else if (sbc_caps->block_length & SBC_BLOCK_LENGTH_8) { sbc_config->block_length = SBC_BLOCK_LENGTH_8; } else if (sbc_caps->block_length & SBC_BLOCK_LENGTH_4) { sbc_config->block_length = SBC_BLOCK_LENGTH_4; } else { syslog(LOG_WARNING, "No supported block length."); return -ENOSYS; } sbc_config->min_bitpool = (sbc_caps->min_bitpool > MIN_BITPOOL ? sbc_caps->min_bitpool : MIN_BITPOOL); sbc_config->max_bitpool = (sbc_caps->max_bitpool < MAX_BITPOOL ? sbc_caps->max_bitpool : MAX_BITPOOL); return 0; } static void cras_a2dp_set_configuration(struct cras_bt_endpoint *endpoint, struct cras_bt_transport *transport) { struct cras_bt_device *device; device = cras_bt_transport_device(transport); cras_bt_device_a2dp_configured(device); } static void cras_a2dp_suspend(struct cras_bt_endpoint *endpoint, struct cras_bt_transport *transport) { struct cras_bt_device *device = cras_bt_transport_device(transport); BTLOG(btlog, BT_A2DP_SUSPENDED, 0, 0); cras_a2dp_suspend_connected_device(device); cras_bt_device_notify_profile_dropped(device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK); } static void a2dp_transport_state_changed(struct cras_bt_endpoint *endpoint, struct cras_bt_transport *transport) { if (connected_a2dp.iodev && transport) { /* When pending message is received in bluez, try to aquire * the transport. */ if (cras_bt_transport_fd(transport) != -1 && cras_bt_transport_state(transport) == CRAS_BT_TRANSPORT_STATE_PENDING) cras_bt_transport_try_acquire(transport); } } static struct cras_bt_endpoint cras_a2dp_endpoint = { /* BlueZ connects the device A2DP Sink to our A2DP Source endpoint, * and the device A2DP Source to our A2DP Sink. It's best if you don't * think about it too hard. */ .object_path = A2DP_SOURCE_ENDPOINT_PATH, .uuid = A2DP_SOURCE_UUID, .codec = A2DP_CODEC_SBC, .get_capabilities = cras_a2dp_get_capabilities, .select_configuration = cras_a2dp_select_configuration, .set_configuration = cras_a2dp_set_configuration, .suspend = cras_a2dp_suspend, .transport_state_changed = a2dp_transport_state_changed }; int cras_a2dp_endpoint_create(DBusConnection *conn) { return cras_bt_endpoint_add(conn, &cras_a2dp_endpoint); } void cras_a2dp_start(struct cras_bt_device *device) { struct cras_bt_transport *transport = cras_a2dp_endpoint.transport; BTLOG(btlog, BT_A2DP_START, 0, 0); if (!transport || device != cras_bt_transport_device(transport)) { syslog(LOG_ERR, "Device and active transport not match."); return; } if (connected_a2dp.iodev) { syslog(LOG_WARNING, "Replacing existing endpoint configuration"); a2dp_iodev_destroy(connected_a2dp.iodev); } connected_a2dp.iodev = a2dp_iodev_create(transport); connected_a2dp.device = cras_bt_transport_device(transport); if (!connected_a2dp.iodev) syslog(LOG_WARNING, "Failed to create a2dp iodev"); } struct cras_bt_device *cras_a2dp_connected_device() { return connected_a2dp.device; } void cras_a2dp_suspend_connected_device(struct cras_bt_device *device) { if (connected_a2dp.device != device) return; if (connected_a2dp.iodev) { syslog(LOG_INFO, "Destroying iodev for A2DP device"); a2dp_iodev_destroy(connected_a2dp.iodev); connected_a2dp.iodev = NULL; connected_a2dp.device = NULL; } }