/* 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 #include #include "cras_bt_io.h" #include "cras_bt_device.h" #include "cras_hfp_iodev.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.capture_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; } } /* Switches the active profile to A2DP if it can. */ static void bt_possibly_switch_to_a2dp(struct bt_io *btio) { if (!cras_bt_device_has_a2dp(btio->device)) return; cras_bt_device_set_active_profile(btio->device, CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE); cras_bt_device_switch_profile(btio->device, &btio->base); } /* 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 based * 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 open_dev(struct cras_iodev *iodev) { struct bt_io *btio = (struct bt_io *)iodev; struct cras_iodev *dev = active_profile_dev(iodev); int rc; /* 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 && dev->open_dev) { rc = dev->open_dev(dev); if (rc == 0) return 0; /* If input iodev open fails, switch profile back to A2DP. */ if (iodev->direction == CRAS_STREAM_INPUT) bt_possibly_switch_to_a2dp(btio); return rc; } return 0; } static int update_supported_formats(struct cras_iodev *iodev) { struct cras_iodev *dev = active_profile_dev(iodev); int rc, length, i; if (!dev) return -EINVAL; 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]; /* Record max supported channels into cras_iodev_info. */ iodev->info.max_supported_channels = dev->info.max_supported_channels; return 0; } static int configure_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. */ if (dev->format == NULL) { dev->format = (struct cras_audio_format *)malloc( sizeof(*dev->format)); if (!dev->format) return -ENOMEM; *dev->format = *iodev->format; } rc = dev->configure_dev(dev); if (rc) return rc; iodev->buffer_size = dev->buffer_size; iodev->min_buffer_level = dev->min_buffer_level; if (dev->start) dev->state = CRAS_IODEV_STATE_OPEN; else dev->state = CRAS_IODEV_STATE_NO_STREAM_RUN; 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); if (!dev) return -EINVAL; /* If input iodev is in open state and being closed, switch profile * from HFP to A2DP. */ if (cras_iodev_is_open(iodev) && device_using_profile( btio->device, CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY | CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY) && (iodev->direction == CRAS_STREAM_INPUT)) bt_possibly_switch_to_a2dp(btio); rc = dev->close_dev(dev); if (rc < 0) return rc; cras_iodev_free_format(iodev); dev->state = CRAS_IODEV_STATE_CLOSE; return 0; } static void set_bt_volume(struct cras_iodev *iodev) { struct cras_iodev *dev = active_profile_dev(iodev); if (!dev) return; 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; struct cras_iodev *dev; int rc; if (device_using_profile(btio->device, active->profile)) goto leave; /* 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); } } leave: dev = active_profile_dev(iodev); if (dev && dev->update_active_node) dev->update_active_node(dev, node_idx, dev_enabled); /* Update supported formats here to get the supported formats from the * new updated active profile dev. */ rc = update_supported_formats(iodev); if (rc) { syslog(LOG_ERR, "Failed to update supported formats, rc=%d", rc); } } static int output_underrun(struct cras_iodev *iodev) { struct cras_iodev *dev = active_profile_dev(iodev); if (!dev) return -EINVAL; if (dev->output_underrun) { dev->min_cb_level = iodev->min_cb_level; dev->max_cb_level = iodev->max_cb_level; dev->buffer_size = iodev->buffer_size; return dev->output_underrun(dev); } return 0; } static int no_stream(struct cras_iodev *iodev, int enable) { struct cras_iodev *dev = active_profile_dev(iodev); int rc; if (!dev) return -EINVAL; if (dev->no_stream) { /* * Copy iodev->min_cb_level and iodev->max_cb_level from the * parent (i.e. bt_io). no_stream() of hfp_alsa_iodev will * use them. * A2DP and HFP dev will use buffer and callback sizes to fill * zeros in no stream state. */ dev->min_cb_level = iodev->min_cb_level; dev->max_cb_level = iodev->max_cb_level; dev->buffer_size = iodev->buffer_size; rc = dev->no_stream(dev, enable); if (rc < 0) return rc; } if (enable) dev->state = CRAS_IODEV_STATE_NO_STREAM_RUN; else dev->state = CRAS_IODEV_STATE_NORMAL_RUN; return 0; } static int is_free_running(const struct cras_iodev *iodev) { struct cras_iodev *dev = active_profile_dev(iodev); if (!dev) return -EINVAL; if (dev->is_free_running) return dev->is_free_running(dev); return 0; } static int start(const struct cras_iodev *iodev) { struct cras_iodev *dev = active_profile_dev(iodev); int rc; if (!dev) return -EINVAL; if (dev->start) { rc = dev->start(dev); if (rc) return rc; } dev->state = CRAS_IODEV_STATE_NORMAL_RUN; return 0; } static unsigned int frames_to_play_in_sleep(struct cras_iodev *iodev, unsigned int *hw_level, struct timespec *hw_tstamp) { struct cras_iodev *dev = active_profile_dev(iodev); if (!dev || !dev->frames_to_play_in_sleep) return cras_iodev_default_frames_to_play_in_sleep( iodev, hw_level, hw_tstamp); return dev->frames_to_play_in_sleep(dev, hw_level, hw_tstamp); } static int get_valid_frames(struct cras_iodev *iodev, struct timespec *hw_tstamp) { struct cras_iodev *dev = active_profile_dev(iodev); if (!dev) return -EINVAL; if (dev->get_valid_frames) return dev->get_valid_frames(dev, hw_tstamp); return cras_iodev_frames_queued(iodev, hw_tstamp); } 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->configure_dev = configure_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->no_stream = no_stream; iodev->output_underrun = output_underrun; iodev->is_free_running = is_free_running; iodev->get_valid_frames = get_valid_frames; iodev->start = start; iodev->frames_to_play_in_sleep = frames_to_play_in_sleep; /* Input also checks |software_volume_needed| flag for using software * gain. Keep it as false for BT input. * TODO(hychao): after wide band speech mode is supported, consider * enable software gain. */ if (dev->direction == CRAS_STREAM_OUTPUT) { iodev->software_volume_needed = !cras_bt_device_get_use_hardware_volume(device); iodev->set_volume = set_bt_volume; } /* Create the fake node 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) goto error; active->base.dev = iodev; active->base.idx = btio->next_node_id++; active->base.type = dev->active_node->type; active->base.volume = 100; active->base.stable_id = cras_bt_device_get_stable_id(device); active->base.ui_gain_scaler = 1.0f; /* * If the same headset is connected in wideband mode, we shall assign * a separate stable_id so the node priority/preference mechanism in * Chrome UI doesn't break. */ if ((active->base.type == CRAS_NODE_TYPE_BLUETOOTH) && (dev->direction == CRAS_STREAM_INPUT)) active->base.stable_id = SuperFastHash((const char *)&active->base.type, sizeof(active->base.type), active->base.stable_id); active->profile = profile; active->profile_dev = dev; 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_free_resources(struct cras_iodev *bt_iodev) { struct cras_ionode *node; struct bt_node *n; free(bt_iodev->supported_rates); free(bt_iodev->supported_channel_counts); free(bt_iodev->supported_formats); DL_FOREACH (bt_iodev->nodes, node) { n = (struct bt_node *)node; cras_iodev_rm_node(bt_iodev, node); free(n); } cras_iodev_free_resources(bt_iodev); } void cras_bt_io_destroy(struct cras_iodev *bt_iodev) { int rc; struct bt_io *btio = (struct bt_io *)bt_iodev; 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; cras_bt_io_free_resources(bt_iodev); 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); } enum cras_bt_device_profile cras_bt_io_profile_to_log(struct cras_iodev *bt_iodev) { struct bt_node *btnode = (struct bt_node *)bt_iodev->active_node; if (btnode->profile & CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE) return CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE; if (hfp_iodev_is_hsp(btnode->profile_dev)) return CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY; else return CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY; } 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; }