| /* Copyright (c) 2013 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 <sys/socket.h> |
| #include <syslog.h> |
| |
| #include "cras_audio_area.h" |
| #include "cras_hfp_iodev.h" |
| #include "cras_hfp_info.h" |
| #include "cras_hfp_slc.h" |
| #include "cras_iodev.h" |
| #include "cras_system_state.h" |
| #include "cras_util.h" |
| #include "sfh.h" |
| #include "utlist.h" |
| |
| |
| struct hfp_io { |
| struct cras_iodev base; |
| struct cras_bt_device *device; |
| struct hfp_slc_handle *slc; |
| struct hfp_info *info; |
| }; |
| |
| static int update_supported_formats(struct cras_iodev *iodev) |
| { |
| // 16 bit, mono, 8kHz |
| iodev->format->format = SND_PCM_FORMAT_S16_LE; |
| |
| free(iodev->supported_rates); |
| iodev->supported_rates = (size_t *)malloc(2 * sizeof(size_t)); |
| iodev->supported_rates[0] = 8000; |
| iodev->supported_rates[1] = 0; |
| |
| free(iodev->supported_channel_counts); |
| iodev->supported_channel_counts = (size_t *)malloc(2 * sizeof(size_t)); |
| iodev->supported_channel_counts[0] = 1; |
| iodev->supported_channel_counts[1] = 0; |
| |
| free(iodev->supported_formats); |
| iodev->supported_formats = |
| (snd_pcm_format_t *)malloc(2 * sizeof(snd_pcm_format_t)); |
| iodev->supported_formats[0] = SND_PCM_FORMAT_S16_LE; |
| iodev->supported_formats[1] = 0; |
| |
| return 0; |
| } |
| |
| static int frames_queued(const struct cras_iodev *iodev, |
| struct timespec *tstamp) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| |
| if (!hfp_info_running(hfpio->info)) |
| return -1; |
| |
| /* Do not enable timestamp mechanism on HFP device because last time |
| * stamp might be a long time ago and it is not really useful. */ |
| clock_gettime(CLOCK_MONOTONIC_RAW, tstamp); |
| return hfp_buf_queued(hfpio->info, iodev); |
| } |
| |
| /* Modify the hfpio's buffer_size when the SCO packet size has changed. */ |
| static void hfp_packet_size_changed(void *data) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)data; |
| struct cras_iodev *iodev = &hfpio->base; |
| |
| if (!cras_iodev_is_open(iodev)) |
| return; |
| iodev->buffer_size = hfp_buf_size(hfpio->info, iodev); |
| cras_bt_device_iodev_buffer_size_changed(hfpio->device); |
| } |
| |
| static int open_dev(struct cras_iodev *iodev) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| int sk, err, mtu; |
| |
| /* Assert format is set before opening device. */ |
| if (iodev->format == NULL) |
| return -EINVAL; |
| iodev->format->format = SND_PCM_FORMAT_S16_LE; |
| cras_iodev_init_audio_area(iodev, iodev->format->num_channels); |
| |
| if (hfp_info_running(hfpio->info)) |
| goto add_dev; |
| |
| sk = cras_bt_device_sco_connect(hfpio->device); |
| if (sk < 0) |
| goto error; |
| |
| mtu = cras_bt_device_sco_mtu(hfpio->device, sk); |
| |
| /* Start hfp_info */ |
| err = hfp_info_start(sk, mtu, hfpio->info); |
| if (err) |
| goto error; |
| |
| add_dev: |
| hfp_info_add_iodev(hfpio->info, iodev); |
| hfp_set_call_status(hfpio->slc, 1); |
| |
| iodev->buffer_size = hfp_buf_size(hfpio->info, iodev); |
| |
| return 0; |
| error: |
| syslog(LOG_ERR, "Failed to open HFP iodev"); |
| return -1; |
| } |
| |
| static int close_dev(struct cras_iodev *iodev) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| |
| hfp_info_rm_iodev(hfpio->info, iodev); |
| if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info)) { |
| hfp_info_stop(hfpio->info); |
| hfp_set_call_status(hfpio->slc, 0); |
| } |
| |
| cras_iodev_free_format(iodev); |
| cras_iodev_free_audio_area(iodev); |
| return 0; |
| } |
| |
| static void set_hfp_volume(struct cras_iodev *iodev) |
| { |
| size_t volume; |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| |
| volume = cras_system_get_volume(); |
| if (iodev->active_node) |
| volume = cras_iodev_adjust_node_volume(iodev->active_node, volume); |
| |
| hfp_event_speaker_gain(hfpio->slc, volume); |
| } |
| |
| static int delay_frames(const struct cras_iodev *iodev) |
| { |
| struct timespec tstamp; |
| |
| return frames_queued(iodev, &tstamp); |
| } |
| |
| static int get_buffer(struct cras_iodev *iodev, |
| struct cras_audio_area **area, |
| unsigned *frames) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| uint8_t *dst = NULL; |
| |
| if (!hfp_info_running(hfpio->info)) |
| return -1; |
| |
| hfp_buf_acquire(hfpio->info, iodev, &dst, frames); |
| |
| iodev->area->frames = *frames; |
| /* HFP is mono only. */ |
| iodev->area->channels[0].step_bytes = |
| cras_get_format_bytes(iodev->format); |
| iodev->area->channels[0].buf = dst; |
| |
| *area = iodev->area; |
| return 0; |
| } |
| |
| static int put_buffer(struct cras_iodev *iodev, unsigned nwritten) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| |
| if (!hfp_info_running(hfpio->info)) |
| return -1; |
| |
| hfp_buf_release(hfpio->info, iodev, nwritten); |
| return 0; |
| } |
| |
| static int flush_buffer(struct cras_iodev *iodev) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| unsigned nframes; |
| |
| if (iodev->direction == CRAS_STREAM_INPUT) { |
| nframes = hfp_buf_queued(hfpio->info, iodev); |
| hfp_buf_release(hfpio->info, iodev, nframes); |
| } |
| return 0; |
| } |
| |
| static void update_active_node(struct cras_iodev *iodev, unsigned node_idx, |
| unsigned dev_enabled) |
| { |
| } |
| |
| void hfp_free_resources(struct hfp_io *hfpio) |
| { |
| struct cras_ionode *node; |
| node = hfpio->base.active_node; |
| if (node) { |
| cras_iodev_rm_node(&hfpio->base, node); |
| free(node); |
| } |
| free(hfpio->base.supported_channel_counts); |
| free(hfpio->base.supported_rates); |
| } |
| |
| struct cras_iodev *hfp_iodev_create( |
| enum CRAS_STREAM_DIRECTION dir, |
| struct cras_bt_device *device, |
| struct hfp_slc_handle *slc, |
| enum cras_bt_device_profile profile, |
| struct hfp_info *info) |
| { |
| struct hfp_io *hfpio; |
| struct cras_iodev *iodev; |
| struct cras_ionode *node; |
| const char *name; |
| |
| hfpio = (struct hfp_io *)calloc(1, sizeof(*hfpio)); |
| if (!hfpio) |
| goto error; |
| |
| iodev = &hfpio->base; |
| iodev->direction = dir; |
| |
| hfpio->device = device; |
| hfpio->slc = slc; |
| |
| /* Set iodev's name to device readable name or the address. */ |
| name = cras_bt_device_name(device); |
| if (!name) |
| name = cras_bt_device_object_path(device); |
| |
| snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name); |
| iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0; |
| iodev->info.stable_id = SuperFastHash( |
| cras_bt_device_object_path(device), |
| strlen(cras_bt_device_object_path(device)), |
| strlen(cras_bt_device_object_path(device))); |
| iodev->info.stable_id_new = iodev->info.stable_id; |
| |
| 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->set_volume = set_hfp_volume; |
| |
| node = (struct cras_ionode *)calloc(1, sizeof(*node)); |
| node->dev = iodev; |
| strcpy(node->name, iodev->info.name); |
| |
| node->plugged = 1; |
| node->type = CRAS_NODE_TYPE_BLUETOOTH; |
| node->volume = 100; |
| gettimeofday(&node->plugged_time, NULL); |
| |
| cras_bt_device_append_iodev(device, iodev, profile); |
| cras_iodev_add_node(iodev, node); |
| cras_iodev_set_active_node(iodev, node); |
| |
| hfpio->info = info; |
| hfp_register_packet_size_changed_callback(info, |
| hfp_packet_size_changed, |
| hfpio); |
| |
| return iodev; |
| |
| error: |
| if (hfpio) { |
| hfp_free_resources(hfpio); |
| free(hfpio); |
| } |
| return NULL; |
| } |
| |
| void hfp_iodev_destroy(struct cras_iodev *iodev) |
| { |
| struct hfp_io *hfpio = (struct hfp_io *)iodev; |
| |
| cras_bt_device_rm_iodev(hfpio->device, iodev); |
| hfp_unregister_packet_size_changed_callback(hfpio->info, hfpio); |
| hfp_free_resources(hfpio); |
| free(hfpio); |
| } |