/* * Copyright (C) 2008-2011 The Android Open Source Project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #define LOG_TAG "a2dp_audio_hw" //#define LOG_NDEBUG 0 #include <errno.h> #include <pthread.h> #include <stdint.h> #include <sys/time.h> #include <cutils/log.h> #include <cutils/str_parms.h> #include <hardware/hardware.h> #include <system/audio.h> #include <hardware/audio.h> #include <hardware_legacy/power.h> #include "liba2dp.h" #define A2DP_WAKE_LOCK_NAME "A2dpOutputStream" #define MAX_WRITE_RETRIES 5 #define A2DP_SUSPENDED_PARM "A2dpSuspended" #define BLUETOOOTH_ENABLED_PARM "bluetooth_enabled" #define OUT_SINK_ADDR_PARM "a2dp_sink_address" /* NOTE: If you need to hold the adev_a2dp->lock AND the astream_out->lock, you MUST take adev_a2dp lock first!! */ struct astream_out; struct adev_a2dp { struct audio_hw_device device; audio_mode_t mode; bool bt_enabled; bool suspended; pthread_mutex_t lock; struct astream_out *output; }; struct astream_out { struct audio_stream_out stream; uint32_t sample_rate; size_t buffer_size; uint32_t channels; int format; int fd; bool standby; int start_count; int retry_count; void* data; pthread_mutex_t lock; audio_devices_t device; uint64_t last_write_time; uint32_t buffer_duration_us; bool bt_enabled; bool suspended; char a2dp_addr[20]; }; static uint64_t system_time(void) { struct timespec t; t.tv_sec = t.tv_nsec = 0; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec*1000000000LL + t.tv_nsec; } /** audio_stream_out implementation **/ static uint32_t out_get_sample_rate(const struct audio_stream *stream) { const struct astream_out *out = (const struct astream_out *)stream; return out->sample_rate; } static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) { struct astream_out *out = (struct astream_out *)stream; LOGE("(%s:%d) %s: Implement me!", __FILE__, __LINE__, __func__); return 0; } static size_t out_get_buffer_size(const struct audio_stream *stream) { const struct astream_out *out = (const struct astream_out *)stream; return out->buffer_size; } static uint32_t out_get_channels(const struct audio_stream *stream) { const struct astream_out *out = (const struct astream_out *)stream; return out->channels; } static int out_get_format(const struct audio_stream *stream) { const struct astream_out *out = (const struct astream_out *)stream; return out->format; } static int out_set_format(struct audio_stream *stream, int format) { struct astream_out *out = (struct astream_out *)stream; LOGE("(%s:%d) %s: Implement me!", __FILE__, __LINE__, __func__); return 0; } static int out_dump(const struct audio_stream *stream, int fd) { return 0; } static uint32_t out_get_latency(const struct audio_stream_out *stream) { const struct astream_out *out = (const struct astream_out *)stream; return (out->buffer_duration_us / 1000) + 200; } static int out_set_volume(struct audio_stream_out *stream, float left, float right) { return -ENOSYS; } static int out_get_render_position(const struct audio_stream_out *stream, uint32_t *dsp_frames) { return -ENOSYS; } static int _out_init_locked(struct astream_out *out, const char *addr) { int ret; if (out->data) return 0; /* XXX: shouldn't this use the sample_rate/channel_count from 'out'? */ ret = a2dp_init(44100, 2, &out->data); if (ret < 0) { LOGE("a2dp_init failed err: %d\n", ret); out->data = NULL; return ret; } /* XXX: is this even necessary? */ if (addr) strlcpy(out->a2dp_addr, addr, sizeof(out->a2dp_addr)); a2dp_set_sink(out->data, out->a2dp_addr); return 0; } static bool _out_validate_parms(struct astream_out *out, int format, uint32_t chans, uint32_t rate) { if ((format && (format != out->format)) || (chans && (chans != out->channels)) || (rate && (rate != out->sample_rate))) return false; return true; } static int out_standby_stream_locked(struct astream_out *out) { int ret = 0; if (out->standby || !out->data) return 0; LOGV_IF(!out->bt_enabled, "Standby skip stop: enabled %d", out->bt_enabled); if (out->bt_enabled) ret = a2dp_stop(out->data); release_wake_lock(A2DP_WAKE_LOCK_NAME); out->standby = true; return ret; } static int out_close_stream_locked(struct astream_out *out) { out_standby_stream_locked(out); if (out->data) { LOGV("%s: calling a2dp_cleanup()", __func__); a2dp_cleanup(out->data); out->data = NULL; } return 0; } static int out_standby(struct audio_stream *stream) { struct astream_out *out = (struct astream_out *)stream; pthread_mutex_lock(&out->lock); out_standby_stream_locked(out); pthread_mutex_unlock(&out->lock); return 0; } static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) { struct astream_out *out = (struct astream_out *)stream; struct str_parms *parms; char *str; char value[32]; int ret; parms = str_parms_create_str(kvpairs); pthread_mutex_lock(&out->lock); ret = str_parms_get_str(parms, OUT_SINK_ADDR_PARM, value, sizeof(value)); if (ret >= 0) { /* strlen(00:00:00:00:00:00) == 17 */ if (strlen(value) == 17) { strlcpy(out->a2dp_addr, value, sizeof(out->a2dp_addr)); if (out->data) a2dp_set_sink(out->data, out->a2dp_addr); } else ret = -EINVAL; } pthread_mutex_unlock(&out->lock); str_parms_destroy(parms); return ret; } static audio_devices_t out_get_device(const struct audio_stream *stream) { const struct astream_out *out = (const struct astream_out *)stream; return out->device; } static int out_set_device(struct audio_stream *stream, audio_devices_t device) { struct astream_out *out = (struct astream_out *)stream; if (!audio_is_a2dp_device(device)) return -EINVAL; /* XXX: if out->device ever starts getting used for anything, need to * grab the out->lock */ out->device = device; return 0; } static char * out_get_parameters(const struct audio_stream *stream, const char *keys) { struct astream_out *out = (struct astream_out *)stream; struct str_parms *parms; struct str_parms *out_parms; char *str; char value[20]; int ret; parms = str_parms_create_str(keys); out_parms = str_parms_create(); pthread_mutex_lock(&out->lock); ret = str_parms_get_str(parms, OUT_SINK_ADDR_PARM, value, sizeof(value)); if (ret >= 0) str_parms_add_str(out_parms, OUT_SINK_ADDR_PARM, out->a2dp_addr); pthread_mutex_unlock(&out->lock); str = str_parms_to_str(out_parms); str_parms_destroy(out_parms); str_parms_destroy(parms); return str; } static ssize_t out_write(struct audio_stream_out *stream, const void* buffer, size_t bytes) { struct astream_out *out = (struct astream_out *)stream; int ret; int cnt = bytes; int retries = MAX_WRITE_RETRIES; uint64_t now; uint32_t elapsed_us; const uint8_t *buf = buffer; pthread_mutex_lock(&out->lock); if (!out->bt_enabled || out->suspended) { LOGV("a2dp %s: bluetooth disabled bt_en %d, suspended %d", out->bt_enabled, out->suspended); ret = -1; goto err_bt_disabled; } if (out->standby) { acquire_wake_lock(PARTIAL_WAKE_LOCK, A2DP_WAKE_LOCK_NAME); out->standby = false; out->last_write_time = system_time(); } ret = _out_init_locked(out, NULL); if (ret < 0) goto err_init; while (cnt > 0 && retries > 0) { ret = a2dp_write(out->data, buf, cnt); if (ret < 0) { LOGE("%s: a2dp_write failed (%d)\n", __func__, ret); goto err_write; } else if (ret == 0) { retries--; continue; } cnt -= ret; buf += ret; } /* XXX: PLEASE FIX ME!!!! */ /* if A2DP sink runs abnormally fast, sleep a little so that * audioflinger mixer thread does no spin and starve other threads. */ /* NOTE: It is likely that the A2DP headset is being disconnected */ now = system_time(); elapsed_us = (now - out->last_write_time) / 1000UL; if (elapsed_us < (out->buffer_duration_us / 4)) { LOGV("A2DP sink runs too fast"); usleep(out->buffer_duration_us - elapsed_us); } out->last_write_time = now; pthread_mutex_unlock(&out->lock); return bytes; err_write: err_init: err_bt_disabled: out_standby_stream_locked(out); pthread_mutex_unlock(&out->lock); /* XXX: simulate audio output timing in case of error?!?! */ usleep(out->buffer_duration_us); return ret; } static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { return 0; } static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { return 0; } static int _out_bt_enable(struct astream_out *out, bool enable) { int ret = 0; pthread_mutex_lock(&out->lock); out->bt_enabled = enable; if (!enable) ret = out_close_stream_locked(out); pthread_mutex_unlock(&out->lock); return ret; } static int _out_a2dp_suspend(struct astream_out *out, bool suspend) { pthread_mutex_lock(&out->lock); out->suspended = suspend; out_standby_stream_locked(out); pthread_mutex_unlock(&out->lock); return 0; } static int adev_open_output_stream(struct audio_hw_device *dev, uint32_t devices, int *format, uint32_t *channels, uint32_t *sample_rate, struct audio_stream_out **stream_out) { struct adev_a2dp *adev = (struct adev_a2dp *)dev; struct astream_out *out; int ret; pthread_mutex_lock(&adev->lock); /* one output stream at a time */ if (adev->output) { LOGV("output exists"); ret = -EBUSY; goto err_output_exists; } out = calloc(1, sizeof(struct astream_out)); if (!out) { ret = -ENOMEM; goto err_alloc; } pthread_mutex_init(&out->lock, NULL); out->stream.common.get_sample_rate = out_get_sample_rate; out->stream.common.set_sample_rate = out_set_sample_rate; out->stream.common.get_buffer_size = out_get_buffer_size; out->stream.common.get_channels = out_get_channels; out->stream.common.get_format = out_get_format; out->stream.common.set_format = out_set_format; out->stream.common.standby = out_standby; out->stream.common.dump = out_dump; out->stream.common.set_parameters = out_set_parameters; out->stream.common.get_parameters = out_get_parameters; out->stream.common.set_device = out_set_device; out->stream.common.get_device = out_get_device; out->stream.common.add_audio_effect = out_add_audio_effect; out->stream.common.remove_audio_effect = out_remove_audio_effect; out->stream.get_latency = out_get_latency; out->stream.set_volume = out_set_volume; out->stream.write = out_write; out->stream.get_render_position = out_get_render_position; out->sample_rate = 44100; out->buffer_size = 512 * 20; out->channels = AUDIO_CHANNEL_OUT_STEREO; out->format = AUDIO_FORMAT_PCM_16_BIT; out->fd = -1; out->device = devices; out->bt_enabled = adev->bt_enabled; out->suspended = adev->suspended; /* for now, buffer_duration_us is precalculated and never changed. * if the sample rate or the format ever changes on the fly, we'd have * to recalculate this */ out->buffer_duration_us = ((out->buffer_size * 1000 ) / audio_stream_frame_size(&out->stream.common) / out->sample_rate) * 1000; if (!_out_validate_parms(out, format ? *format : 0, channels ? *channels : 0, sample_rate ? *sample_rate : 0)) { LOGV("invalid parameters"); ret = -EINVAL; goto err_validate_parms; } /* XXX: check return code? */ if (adev->bt_enabled) _out_init_locked(out, "00:00:00:00:00:00"); adev->output = out; if (format) *format = out->format; if (channels) *channels = out->channels; if (sample_rate) *sample_rate = out->sample_rate; pthread_mutex_unlock(&adev->lock); *stream_out = &out->stream; return 0; err_validate_parms: free(out); err_alloc: err_output_exists: pthread_mutex_unlock(&adev->lock); *stream_out = NULL; return ret; } /* needs the adev->lock held */ static void adev_close_output_stream_locked(struct adev_a2dp *dev, struct astream_out *stream) { struct adev_a2dp *adev = (struct adev_a2dp *)dev; struct astream_out *out = (struct astream_out *)stream; /* invalid stream? */ if (!adev->output || adev->output != out) { LOGE("%s: unknown stream %p (ours is %p)", __func__, out, adev->output); return; } pthread_mutex_lock(&out->lock); out_close_stream_locked(out); pthread_mutex_unlock(&out->lock); adev->output = NULL; free(out); } static void adev_close_output_stream(struct audio_hw_device *dev, struct audio_stream_out *stream) { struct adev_a2dp *adev = (struct adev_a2dp *)dev; struct astream_out *out = (struct astream_out *)stream; pthread_mutex_lock(&adev->lock); adev_close_output_stream_locked(adev, out); pthread_mutex_unlock(&adev->lock); } static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) { struct adev_a2dp *adev = (struct adev_a2dp *)dev; struct str_parms *parms; char *str; char value[8]; int ret; parms = str_parms_create_str(kvpairs); pthread_mutex_lock(&adev->lock); ret = str_parms_get_str(parms, BLUETOOOTH_ENABLED_PARM, value, sizeof(value)); if (ret >= 0) { adev->bt_enabled = !strcmp(value, "true"); if (adev->output) _out_bt_enable(adev->output, adev->bt_enabled); } ret = str_parms_get_str(parms, A2DP_SUSPENDED_PARM, value, sizeof(value)); if (ret >= 0) { adev->suspended = !strcmp(value, "true"); if (adev->output) _out_a2dp_suspend(adev->output, adev->suspended); } pthread_mutex_unlock(&adev->lock); str_parms_destroy(parms); return ret; } static char * adev_get_parameters(const struct audio_hw_device *dev, const char *keys) { struct adev_a2dp *adev = (struct adev_a2dp *)dev; struct str_parms *parms; struct str_parms *out_parms; char *str; char value[8]; int ret; parms = str_parms_create_str(keys); out_parms = str_parms_create(); pthread_mutex_lock(&adev->lock); ret = str_parms_get_str(parms, BLUETOOOTH_ENABLED_PARM, value, sizeof(value)); if (ret >= 0) str_parms_add_str(out_parms, BLUETOOOTH_ENABLED_PARM, adev->bt_enabled ? "true" : "false"); ret = str_parms_get_str(parms, A2DP_SUSPENDED_PARM, value, sizeof(value)); if (ret >= 0) str_parms_add_str(out_parms, A2DP_SUSPENDED_PARM, adev->suspended ? "true" : "false"); pthread_mutex_unlock(&adev->lock); str = str_parms_to_str(out_parms); str_parms_destroy(out_parms); str_parms_destroy(parms); return str; } static int adev_init_check(const struct audio_hw_device *dev) { return 0; } static int adev_set_voice_volume(struct audio_hw_device *dev, float volume) { return -ENOSYS; } static int adev_set_master_volume(struct audio_hw_device *dev, float volume) { return -ENOSYS; } static int adev_set_mode(struct audio_hw_device *dev, int mode) { /* TODO: do we care for the mode? */ return 0; } static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) { return -ENOSYS; } static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) { return -ENOSYS; } static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, uint32_t sample_rate, int format, int channel_count) { /* no input */ return 0; } static int adev_open_input_stream(struct audio_hw_device *dev, uint32_t devices, int *format, uint32_t *channels, uint32_t *sample_rate, audio_in_acoustics_t acoustics, struct audio_stream_in **stream_in) { return -ENOSYS; } static void adev_close_input_stream(struct audio_hw_device *dev, struct audio_stream_in *in) { return; } static int adev_dump(const audio_hw_device_t *device, int fd) { return 0; } static int adev_close(hw_device_t *device) { struct adev_a2dp *adev = (struct adev_a2dp *)device; pthread_mutex_lock(&adev->lock); if (adev->output) adev_close_output_stream_locked(adev, adev->output); pthread_mutex_unlock(&adev->lock); free(adev); return 0; } static uint32_t adev_get_supported_devices(const struct audio_hw_device *dev) { return AUDIO_DEVICE_OUT_ALL_A2DP; } static int adev_open(const hw_module_t* module, const char* name, hw_device_t** device) { struct adev_a2dp *adev; int ret; if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) return -EINVAL; adev = calloc(1, sizeof(struct adev_a2dp)); if (!adev) return -ENOMEM; adev->bt_enabled = true; adev->suspended = false; pthread_mutex_init(&adev->lock, NULL); adev->output = NULL; adev->device.common.tag = HARDWARE_DEVICE_TAG; adev->device.common.version = 0; adev->device.common.module = (struct hw_module_t *) module; adev->device.common.close = adev_close; adev->device.get_supported_devices = adev_get_supported_devices; adev->device.init_check = adev_init_check; adev->device.set_voice_volume = adev_set_voice_volume; adev->device.set_master_volume = adev_set_master_volume; adev->device.set_mode = adev_set_mode; adev->device.set_mic_mute = adev_set_mic_mute; adev->device.get_mic_mute = adev_get_mic_mute; adev->device.set_parameters = adev_set_parameters; adev->device.get_parameters = adev_get_parameters; adev->device.get_input_buffer_size = adev_get_input_buffer_size; adev->device.open_output_stream = adev_open_output_stream; adev->device.close_output_stream = adev_close_output_stream; adev->device.open_input_stream = adev_open_input_stream; adev->device.close_input_stream = adev_close_input_stream; adev->device.dump = adev_dump; *device = &adev->device.common; return 0; err_str_parms_create: free(adev); return ret; } static struct hw_module_methods_t hal_module_methods = { .open = adev_open, }; struct audio_module HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = AUDIO_HARDWARE_MODULE_ID, .name = "A2DP Audio HW HAL", .author = "The Android Open Source Project", .methods = &hal_module_methods, }, };