blob: 88c046d5b78ecbbbea30aad7b5739ce1e2188388 [file] [log] [blame]
/* Copyright (c) 2014 The Chromium OS 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 <syslog.h>
#include "cras_bt_io.h"
#include "cras_bt_device.h"
#include "cras_dbus_util.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "utlist.h"
#define DEFAULT_BT_DEVICE_NAME "BLUETOOTH"
/* Extends cras_ionode to hold bluetooth profile information
* so that iodevs of different profile(A2DP or HFP/HSP) can be
* associated with the same bt_io.
* Members:
* base - The base class cras_ionode.
* profile_dev - Pointer to the profile specific iodev.
* profile - The bluetooth profile profile_dev runs on.
*/
struct bt_node {
struct cras_ionode base;
struct cras_iodev *profile_dev;
unsigned int profile;
};
/* The structure represents a virtual input or output device of a
* bluetooth audio device, speaker or headset for example. A node
* will be added to this virtual iodev for each profile supported
* by the bluetooth audio device.
* Member:
* base - The base class cras_iodev
* next_node_id - The index will give to the next node
*/
struct bt_io {
struct cras_iodev base;
unsigned int next_node_id;
struct cras_bt_device *device;
};
/* Returns the active profile specific iodev. */
static struct cras_iodev *active_profile_dev(const struct cras_iodev *iodev)
{
struct bt_node *active = (struct bt_node *)iodev->active_node;
return active->profile_dev;
}
/* Adds a profile specific iodev to btio. */
static struct cras_ionode *add_profile_dev(struct cras_iodev *bt_iodev,
struct cras_iodev *dev,
enum cras_bt_device_profile profile)
{
struct bt_node *n;
struct bt_io *btio = (struct bt_io *)bt_iodev;
n = (struct bt_node *)calloc(1, sizeof(*n));
if (!n)
return NULL;
n->base.dev = bt_iodev;
n->base.idx = btio->next_node_id++;
n->base.type = CRAS_NODE_TYPE_BLUETOOTH;
n->base.volume = 100;
gettimeofday(&n->base.plugged_time, NULL);
strcpy(n->base.name, dev->info.name);
n->profile_dev = dev;
n->profile = profile;
cras_iodev_add_node(bt_iodev, &n->base);
return &n->base;
}
/* Forces bt device to switch to use the given profile. Note that if
* it has already been open for streaming, the new active profile will
* take effect after the related btio(s) are reopened.
*/
static void bt_switch_to_profile(struct cras_bt_device *device,
enum cras_bt_device_profile profile)
{
switch (profile) {
case CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY:
case CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY:
cras_bt_device_set_active_profile(device,
CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY |
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
break;
case CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE:
cras_bt_device_set_active_profile(device,
CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
break;
default:
syslog(LOG_ERR, "Unexpect profile %u", profile);
break;
}
}
/* Checks if bt device is active for the given profile.
*/
static int device_using_profile(struct cras_bt_device *device,
unsigned int profile)
{
return cras_bt_device_get_active_profile(device) & profile;
}
/* Checks if the condition is met to switch to a different profile
* when trying to set the format to btio before open it. Base on two
* rules:
* (1) Prefer to use A2DP for output since the audio quality is better.
* (2) Must use HFP/HSP for input since A2DP doesn't support audio input.
*
* If the profile switch happens, return non-zero error code, otherwise
* return zero.
*/
static int update_supported_formats(struct cras_iodev *iodev)
{
struct bt_io *btio = (struct bt_io *)iodev;
struct cras_iodev *dev = active_profile_dev(iodev);
int rc, length, i;
/* Force to use HFP if opening input dev. */
if (device_using_profile(btio->device,
CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE) &&
iodev->direction == CRAS_STREAM_INPUT) {
bt_switch_to_profile(btio->device,
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
cras_bt_device_switch_profile_on_open(btio->device, iodev);
return -EAGAIN;
}
if (dev->format == NULL) {
dev->format = (struct cras_audio_format *)
malloc(sizeof(*dev->format));
*dev->format = *iodev->format;
}
if (dev->update_supported_formats) {
rc = dev->update_supported_formats(dev);
if (rc)
return rc;
}
/* Fill in the supported rates and channel counts. */
for (length = 0; dev->supported_rates[length]; length++);
free(iodev->supported_rates);
iodev->supported_rates = (size_t *)malloc(
(length + 1) * sizeof(*iodev->supported_rates));
for (i = 0; i < length + 1; i++)
iodev->supported_rates[i] = dev->supported_rates[i];
for (length = 0; dev->supported_channel_counts[length]; length++);
iodev->supported_channel_counts = (size_t *)malloc(
(length + 1) * sizeof(*iodev->supported_channel_counts));
for (i = 0; i < length + 1; i++)
iodev->supported_channel_counts[i] =
dev->supported_channel_counts[i];
for (length = 0; dev->supported_formats[length]; length++);
iodev->supported_formats = (snd_pcm_format_t *)malloc(
(length + 1) * sizeof(*iodev->supported_formats));
for (i = 0; i < length + 1; i++)
iodev->supported_formats[i] =
dev->supported_formats[i];
return 0;
}
static int open_dev(struct cras_iodev *iodev)
{
int rc;
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
/* Fill back the format iodev is using. */
*dev->format = *iodev->format;
rc = dev->open_dev(dev);
if (rc) {
/* Free format here to assure the update_supported_format
* callback will be called before any future open_dev call. */
cras_iodev_free_format(iodev);
return rc;
}
iodev->buffer_size = dev->buffer_size;
iodev->min_buffer_level = dev->min_buffer_level;
return 0;
}
static int close_dev(struct cras_iodev *iodev)
{
struct bt_io *btio = (struct bt_io *)iodev;
int rc;
struct cras_iodev *dev = active_profile_dev(iodev);
/* Force back to A2DP if closing HFP. */
if (device_using_profile(btio->device,
CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY |
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY) &&
iodev->direction == CRAS_STREAM_INPUT &&
cras_bt_device_has_a2dp(btio->device)) {
cras_bt_device_set_active_profile(btio->device,
CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
cras_bt_device_switch_profile_on_close(btio->device,
iodev);
}
rc = dev->close_dev(dev);
if (rc < 0)
return rc;
cras_iodev_free_format(iodev);
return 0;
}
static void set_bt_volume(struct cras_iodev *iodev)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (dev->active_node)
dev->active_node->volume = iodev->active_node->volume;
if (dev->set_volume)
dev->set_volume(dev);
}
static int is_open(const struct cras_iodev *iodev)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return 0;
return dev->is_open(dev);
}
static int frames_queued(const struct cras_iodev *iodev)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
return dev->frames_queued(dev);
}
static int dev_running(const struct cras_iodev *iodev)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
return dev->dev_running(dev);
}
static int delay_frames(const struct cras_iodev *iodev)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
return dev->delay_frames(dev);
}
static int get_buffer(struct cras_iodev *iodev,
struct cras_audio_area **area,
unsigned *frames)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
return dev->get_buffer(dev, area, frames);
}
static int put_buffer(struct cras_iodev *iodev, unsigned nwritten)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
return dev->put_buffer(dev, nwritten);
}
static int flush_buffer(struct cras_iodev *iodev)
{
struct cras_iodev *dev = active_profile_dev(iodev);
if (!dev)
return -EINVAL;
return dev->flush_buffer(dev);
}
/* If the first private iodev doesn't match the active profile stored on
* device, select to the correct private iodev.
*/
static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
{
struct bt_io *btio = (struct bt_io *)iodev;
struct cras_ionode *node;
struct bt_node *active = (struct bt_node *)iodev->active_node;
if (device_using_profile(btio->device, active->profile))
return;
/* Switch to the correct dev using active_profile. */
DL_FOREACH(iodev->nodes, node) {
struct bt_node *n = (struct bt_node *)node;
if (n == active)
continue;
if (device_using_profile(btio->device, n->profile)) {
active->profile = n->profile;
active->profile_dev = n->profile_dev;
/* Fill all volume related stuff to/from the
* profile dev. */
iodev->software_volume_needed =
active->profile_dev->software_volume_needed;
set_bt_volume(iodev);
}
}
}
struct cras_iodev *cras_bt_io_create(struct cras_bt_device *device,
struct cras_iodev *dev,
enum cras_bt_device_profile profile)
{
int err;
struct bt_io *btio;
struct cras_iodev *iodev;
struct cras_ionode *node;
struct bt_node *active;
if (!dev)
return NULL;
btio = (struct bt_io *)calloc(1, sizeof(*btio));
if (!btio)
goto error;
btio->device = device;
iodev = &btio->base;
iodev->direction = dev->direction;
strcpy(iodev->info.name, dev->info.name);
iodev->info.stable_id = dev->info.stable_id;
iodev->open_dev = open_dev;
iodev->is_open = is_open; /* Needed by thread_add_stream */
iodev->frames_queued = frames_queued;
iodev->dev_running = dev_running;
iodev->delay_frames = delay_frames;
iodev->get_buffer = get_buffer;
iodev->put_buffer = put_buffer;
iodev->flush_buffer = flush_buffer;
iodev->close_dev = close_dev;
iodev->update_supported_formats = update_supported_formats;
iodev->update_active_node = update_active_node;
iodev->software_volume_needed = dev->software_volume_needed;
iodev->set_volume = set_bt_volume;
/* Create the dummy node set to plugged so it's the only node exposed
* to UI, and point it to the first profile dev. */
active = (struct bt_node *)calloc(1, sizeof(*active));
if (!active)
return NULL;
active->base.dev = iodev;
active->base.idx = btio->next_node_id++;
active->base.type = CRAS_NODE_TYPE_BLUETOOTH;
active->base.volume = 100;
active->base.plugged = 1;
active->profile = profile;
active->profile_dev = dev;
gettimeofday(&active->base.plugged_time, NULL);
strcpy(active->base.name, dev->info.name);
/* The node name exposed to UI should be a valid UTF8 string. */
if (!is_utf8_string(active->base.name))
strcpy(active->base.name, DEFAULT_BT_DEVICE_NAME);
cras_iodev_add_node(iodev, &active->base);
node = add_profile_dev(&btio->base, dev, profile);
if (node == NULL)
goto error;
/* Default active profile to a2dp whenever it's allowed. */
if (!cras_bt_device_get_active_profile(device) ||
(profile == CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE &&
cras_bt_device_can_switch_to_a2dp(device)))
bt_switch_to_profile(device, profile);
if (iodev->direction == CRAS_STREAM_OUTPUT)
err = cras_iodev_list_add_output(iodev);
else
err = cras_iodev_list_add_input(iodev);
if (err)
goto error;
cras_iodev_set_active_node(iodev, &active->base);
return &btio->base;
error:
if (btio)
free(btio);
return NULL;
}
void cras_bt_io_destroy(struct cras_iodev *bt_iodev)
{
int rc;
struct bt_io *btio = (struct bt_io *)bt_iodev;
struct cras_ionode *node;
struct bt_node *n;
if (bt_iodev->direction == CRAS_STREAM_OUTPUT)
rc = cras_iodev_list_rm_output(bt_iodev);
else
rc = cras_iodev_list_rm_input(bt_iodev);
if (rc == -EBUSY)
return;
DL_FOREACH(bt_iodev->nodes, node) {
n = (struct bt_node *)node;
cras_iodev_rm_node(bt_iodev, node);
free(n);
}
free(btio);
}
struct cras_ionode *cras_bt_io_get_profile(struct cras_iodev *bt_iodev,
enum cras_bt_device_profile profile)
{
struct cras_ionode *node;
DL_FOREACH(bt_iodev->nodes, node) {
struct bt_node *n = (struct bt_node *)node;
if (n->profile & profile)
return node;
}
return NULL;
}
int cras_bt_io_append(struct cras_iodev *bt_iodev,
struct cras_iodev *dev,
enum cras_bt_device_profile profile)
{
struct cras_ionode *node;
struct bt_io *btio = (struct bt_io *)bt_iodev;
if (cras_bt_io_get_profile(bt_iodev, profile))
return -EEXIST;
node = add_profile_dev(bt_iodev, dev, profile);
if (!node)
return -ENOMEM;
if (profile == CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE &&
cras_bt_device_can_switch_to_a2dp(btio->device)) {
bt_switch_to_profile(btio->device,
CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
cras_bt_device_switch_profile_on_open(btio->device, bt_iodev);
syslog(LOG_ERR, "Switch to A2DP on append");
}
return 0;
}
int cras_bt_io_on_profile(struct cras_iodev *bt_iodev,
enum cras_bt_device_profile profile)
{
struct bt_node *btnode = (struct bt_node *)bt_iodev->active_node;
return !!(profile & btnode->profile);
}
int cras_bt_io_update_buffer_size(struct cras_iodev *bt_iodev)
{
struct cras_iodev *dev = active_profile_dev(bt_iodev);
if (!dev)
return -EINVAL;
bt_iodev->buffer_size = dev->buffer_size;
return 0;
}
unsigned int cras_bt_io_try_remove(struct cras_iodev *bt_iodev,
struct cras_iodev *dev)
{
struct cras_ionode *node;
struct bt_node *active, *btnode;
unsigned int try_profile = 0;
active = (struct bt_node *)bt_iodev->active_node;
if (active->profile_dev == dev) {
DL_FOREACH(bt_iodev->nodes, node) {
btnode = (struct bt_node *)node;
/* Skip the active node and the node we're trying
* to remove. */
if (btnode == active || btnode->profile_dev == dev)
continue;
try_profile = btnode->profile;
break;
}
} else {
try_profile = active->profile;
}
return try_profile;
}
int cras_bt_io_remove(struct cras_iodev *bt_iodev,
struct cras_iodev *dev)
{
struct cras_ionode *node;
struct bt_node *btnode;
DL_FOREACH(bt_iodev->nodes, node) {
btnode = (struct bt_node *)node;
if (btnode->profile_dev != dev)
continue;
/* If this is the active node, reset it. Otherwise delete
* this node. */
if (node == bt_iodev->active_node) {
btnode->profile_dev = NULL;
btnode->profile = 0;
} else {
DL_DELETE(bt_iodev->nodes, node);
free(node);
}
}
/* The node of active profile could have been removed, update it.
* Return err when fail to locate the active profile dev. */
update_active_node(bt_iodev, 0);
btnode = (struct bt_node *)bt_iodev->active_node;
if ((btnode->profile == 0) || (btnode->profile_dev == NULL))
return -EINVAL;
return 0;
}