blob: 4cda169d4b3b2c4e99e1fea5aedbeb1628ee2458 [file] [log] [blame]
/* 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 <errno.h>
#include <stdint.h>
#include <syslog.h>
#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;
}
}