/* 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_utf8.h" #include "cras_iodev.h" #include "cras_iodev_list.h" #include "sfh.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; n->base.stable_id = dev->info.stable_id; n->base.stable_id_new = dev->info.stable_id_new; n->base.max_software_gain = 0; 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_enable_dev(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(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; /* The parent bt_iodev could set software_volume_needed flag for cases * that software volume provides better experience across profiles * (HFP and A2DP). Otherwise, use the profile specific implementation * to adjust volume. */ if (dev->set_volume && !iodev->software_volume_needed) dev->set_volume(dev); } static int frames_queued(const struct cras_iodev *iodev, struct timespec *tstamp) { struct cras_iodev *dev = active_profile_dev(iodev); if (!dev) return -EINVAL; return dev->frames_queued(dev, tstamp); } 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, unsigned dev_enabled) { 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; /* Set volume for the new profile. */ 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->info.stable_id_new = dev->info.stable_id_new; iodev->open_dev = open_dev; iodev->frames_queued = frames_queued; 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 = 1; iodev->set_volume = set_bt_volume; iodev->no_stream = cras_iodev_default_no_stream_playback; /* 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->base.stable_id = SuperFastHash( cras_bt_device_object_path(device), strlen(cras_bt_device_object_path(device)), strlen(cras_bt_device_object_path(device))); active->base.stable_id_new = active->base.stable_id; 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(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, 1); btnode = (struct bt_node *)bt_iodev->active_node; if ((btnode->profile == 0) || (btnode->profile_dev == NULL)) return -EINVAL; return 0; }