227 lines
7.0 KiB
227 lines
7.0 KiB
/* 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;
|
|
}
|
|
}
|