| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * Derived from goldfish/audio/audio_hw.c |
| * Changes made to adding support of AUDIO_DEVICE_OUT_BUS |
| */ |
| |
| #define LOG_TAG "audio_hw_generic_caremu" |
| // #define LOG_NDEBUG 0 |
| |
| #include "audio_extn.h" |
| #include "audio_hw.h" |
| #include "include/audio_hw_control.h" |
| |
| #include <assert.h> |
| #include <cutils/hashmap.h> |
| #include <cutils/properties.h> |
| #include <cutils/str_parms.h> |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <hardware/hardware.h> |
| #include <inttypes.h> |
| #include <log/log.h> |
| #include <math.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/time.h> |
| #include <system/audio.h> |
| #include <unistd.h> |
| |
| #include "ext_pcm.h" |
| |
| #define PCM_CARD 0 |
| #define PCM_DEVICE 0 |
| |
| #define DEFAULT_OUT_PERIOD_MS 15 |
| #define DEFAULT_OUT_PERIOD_COUNT 4 |
| |
| #define DEFAULT_IN_PERIOD_MS 15 |
| #define DEFAULT_IN_PERIOD_COUNT 4 |
| |
| #ifndef ARRAY_SIZE |
| #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) |
| #endif |
| |
| static const char* PROP_KEY_OUT_PERIOD_MS[2] = { |
| "ro.boot.vendor.caremu.audiohal.out_period_ms", |
| "ro.vendor.caremu.audiohal.out_period_ms", |
| }; |
| static const char* PROP_KEY_OUT_PERIOD_COUNT[2] = { |
| "ro.boot.vendor.caremu.audiohal.out_period_count", |
| "ro.vendor.caremu.audiohal.out_period_count", |
| }; |
| static const char* PROP_KEY_IN_PERIOD_MS[2] = { |
| "ro.boot.vendor.caremu.audiohal.in_period_ms", |
| "ro.vendor.caremu.audiohal.in_period_ms", |
| }; |
| static const char* PROP_KEY_IN_PERIOD_COUNT[2] = { |
| "ro.boot.vendor.caremu.audiohal.in_period_count", |
| "ro.vendor.caremu.audiohal.in_period_count", |
| }; |
| |
| #define PI 3.14159265 |
| #define TWO_PI (2*PI) |
| |
| // 150 Hz |
| #define DEFAULT_FREQUENCY 150 |
| // Increase in changes to tone frequency |
| #define TONE_FREQUENCY_INCREASE 20 |
| // Max tone frequency to auto assign, don't want to generate too high of a pitch |
| #define MAX_TONE_FREQUENCY 500 |
| |
| // -14dB to match the volume curve in PlaybackActivityMonitor |
| #define DUCKING_MULTIPLIER 0.2 |
| |
| #define _bool_str(x) ((x)?"true":"false") |
| |
| static const char * const PROP_KEY_SIMULATE_MULTI_ZONE_AUDIO = "ro.vendor.caremu.audiohal.simulateMultiZoneAudio"; |
| static const char * const AAE_PARAMETER_KEY_FOR_SELECTED_ZONE = "com.android.car.emulator.selected_zone"; |
| #define PRIMARY_ZONE_ID 0 |
| #define INVALID_ZONE_ID -1 |
| // Note the primary zone goes to left speaker so route other zone to right speaker |
| #define DEFAULT_ZONE_TO_LEFT_SPEAKER (PRIMARY_ZONE_ID + 1) |
| |
| static const char * const TONE_ADDRESS_KEYWORD = "_tone_"; |
| static const char * const AUDIO_ZONE_KEYWORD = "_audio_zone_"; |
| |
| static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; |
| |
| #define SIZE_OF_PARSE_BUFFER 32 |
| #define SIZE_OF_THREAD_NAME_BUFFER 16 |
| |
| static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state); |
| |
| static int audio_get_property(const char** keys, size_t num_keys, int32_t default_value) { |
| static char prop_value[PROP_VALUE_MAX] = {0}; |
| for (size_t i = 0; i < num_keys; ++i) { |
| if (property_get(keys[i], prop_value, NULL) > 0) { |
| return property_get_int32(keys[i], default_value); |
| } |
| } |
| |
| return default_value; |
| } |
| |
| static int get_out_period_ms() { |
| static int out_period_ms = -1; |
| if (out_period_ms == -1) { |
| out_period_ms = audio_get_property(PROP_KEY_OUT_PERIOD_MS, |
| ARRAY_SIZE(PROP_KEY_OUT_PERIOD_MS), |
| DEFAULT_OUT_PERIOD_MS); |
| } |
| return out_period_ms; |
| } |
| |
| static int get_out_period_count() { |
| static int out_period_count = -1; |
| if (out_period_count == -1) { |
| out_period_count = audio_get_property(PROP_KEY_OUT_PERIOD_COUNT, |
| ARRAY_SIZE(PROP_KEY_OUT_PERIOD_COUNT), |
| DEFAULT_OUT_PERIOD_COUNT); |
| } |
| return out_period_count; |
| } |
| |
| static int get_in_period_ms() { |
| static int in_period_ms = -1; |
| if (in_period_ms == -1) { |
| in_period_ms = audio_get_property(PROP_KEY_IN_PERIOD_MS, |
| ARRAY_SIZE(PROP_KEY_IN_PERIOD_MS), |
| DEFAULT_IN_PERIOD_MS); |
| } |
| return in_period_ms; |
| } |
| |
| static int get_in_period_count() { |
| static int in_period_count = -1; |
| if (in_period_count == -1) { |
| in_period_count = audio_get_property(PROP_KEY_IN_PERIOD_COUNT, |
| ARRAY_SIZE(PROP_KEY_IN_PERIOD_COUNT), |
| DEFAULT_IN_PERIOD_COUNT); |
| } |
| return in_period_count; |
| } |
| |
| static struct generic_stream_out * get_audio_device(const char *address, const char *caller) { |
| pthread_mutex_lock(&lock); |
| if(device_handle == 0) { |
| ALOGE("%s no device handle available", caller); |
| pthread_mutex_unlock(&lock); |
| return NULL; |
| } |
| |
| struct generic_stream_out *out = hashmapGet(device_handle->out_bus_stream_map, address); |
| pthread_mutex_unlock(&lock); |
| |
| return out; |
| } |
| |
| void set_device_address_is_ducked(const char *device_address, bool is_ducked) { |
| struct generic_stream_out *out = get_audio_device(device_address, __func__); |
| |
| if (!out) { |
| ALOGW("%s no device found with address %s", __func__, device_address); |
| return; |
| } |
| |
| pthread_mutex_lock(&out->lock); |
| out->is_ducked = is_ducked; |
| pthread_mutex_unlock(&out->lock); |
| } |
| |
| void set_device_address_is_muted(const char *device_address, bool is_muted){ |
| struct generic_stream_out *out = get_audio_device(device_address, __func__); |
| |
| if (!out) { |
| ALOGW("%s no device found with address %s", __func__, device_address); |
| return; |
| } |
| |
| pthread_mutex_lock(&out->lock); |
| out->is_muted = is_muted; |
| pthread_mutex_unlock(&out->lock); |
| } |
| |
| static void set_shortened_thread_name(pthread_t thread, const char *name) { |
| char shortenedName[SIZE_OF_THREAD_NAME_BUFFER]; |
| strncpy(shortenedName, name, SIZE_OF_THREAD_NAME_BUFFER); |
| pthread_setname_np(thread, shortenedName); |
| } |
| |
| static struct pcm_config pcm_config_out = { |
| .channels = 2, |
| .rate = 0, |
| .period_size = 0, |
| .format = PCM_FORMAT_S16_LE, |
| .start_threshold = 0, |
| }; |
| |
| static int get_int_value(const struct str_parms *str_parms, const char *key, int *return_value) { |
| char value[SIZE_OF_PARSE_BUFFER]; |
| int results = str_parms_get_str(str_parms, key, value, SIZE_OF_PARSE_BUFFER); |
| if (results >= 0) { |
| char *end = NULL; |
| errno = 0; |
| long val = strtol(value, &end, 10); |
| if ((errno == 0) && (end != NULL) && (*end == '\0') && ((int) val == val)) { |
| *return_value = val; |
| } else { |
| results = -EINVAL; |
| } |
| } |
| return results; |
| } |
| |
| static struct pcm_config pcm_config_in = { |
| .channels = 2, |
| .rate = 0, |
| .period_size = 0, |
| .format = PCM_FORMAT_S16_LE, |
| .start_threshold = 0, |
| .stop_threshold = INT_MAX, |
| }; |
| |
| static unsigned int audio_device_ref_count = 0; |
| |
| static bool is_zone_selected_to_play(struct audio_hw_device *dev, int zone_id) { |
| // play if current zone is enable or zone equal to primary zone |
| bool is_selected_zone = true; |
| if (zone_id != PRIMARY_ZONE_ID) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| pthread_mutex_lock(&adev->lock); |
| is_selected_zone = adev->last_zone_selected_to_play == zone_id; |
| pthread_mutex_unlock(&adev->lock); |
| } |
| return is_selected_zone; |
| } |
| |
| static uint32_t out_get_sample_rate(const struct audio_stream *stream) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| return out->req_config.sample_rate; |
| } |
| |
| static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) { |
| return -ENOSYS; |
| } |
| |
| static size_t out_get_buffer_size(const struct audio_stream *stream) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| int size = out->pcm_config.period_size * |
| audio_stream_out_frame_size(&out->stream); |
| |
| return size; |
| } |
| |
| static audio_channel_mask_t out_get_channels(const struct audio_stream *stream) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| return out->req_config.channel_mask; |
| } |
| |
| static audio_format_t out_get_format(const struct audio_stream *stream) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| return out->req_config.format; |
| } |
| |
| static int out_set_format(struct audio_stream *stream, audio_format_t format) { |
| return -ENOSYS; |
| } |
| |
| static int out_dump(const struct audio_stream *stream, int fd) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| |
| pthread_mutex_lock(&out->lock); |
| dprintf(fd, "\tout_dump:\n" |
| "\t\taddress: %s\n" |
| "\t\tsample rate: %u\n" |
| "\t\tbuffer size: %zu\n" |
| "\t\tchannel mask: %08x\n" |
| "\t\tformat: %d\n" |
| "\t\tdevice: %08x\n" |
| "\t\tamplitude ratio: %f\n" |
| "\t\tenabled channels: %d\n" |
| "\t\tis ducked: %s\n" |
| "\t\tis muted: %s\n" |
| "\t\taudio dev: %p\n\n", |
| out->bus_address, |
| out_get_sample_rate(stream), |
| out_get_buffer_size(stream), |
| out_get_channels(stream), |
| out_get_format(stream), |
| out->device, |
| out->amplitude_ratio, |
| out->enabled_channels, |
| _bool_str(out->is_ducked), |
| _bool_str(out->is_muted), |
| out->dev); |
| pthread_mutex_unlock(&out->lock); |
| return 0; |
| } |
| |
| static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| struct str_parms *parms; |
| int ret = 0; |
| |
| pthread_mutex_lock(&out->lock); |
| if (!out->standby) { |
| //Do not support changing params while stream running |
| ret = -ENOSYS; |
| } else { |
| parms = str_parms_create_str(kvpairs); |
| int val = 0; |
| ret = get_int_value(parms, AUDIO_PARAMETER_STREAM_ROUTING, &val); |
| if (ret >= 0) { |
| out->device = (int)val; |
| ret = 0; |
| } |
| str_parms_destroy(parms); |
| } |
| pthread_mutex_unlock(&out->lock); |
| return ret; |
| } |
| |
| static char *out_get_parameters(const struct audio_stream *stream, const char *keys) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| struct str_parms *query = str_parms_create_str(keys); |
| char *str; |
| char value[256]; |
| struct str_parms *reply = str_parms_create(); |
| int ret; |
| |
| ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); |
| if (ret >= 0) { |
| pthread_mutex_lock(&out->lock); |
| str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, out->device); |
| pthread_mutex_unlock(&out->lock); |
| str = strdup(str_parms_to_str(reply)); |
| } else { |
| str = strdup(keys); |
| } |
| |
| str_parms_destroy(query); |
| str_parms_destroy(reply); |
| return str; |
| } |
| |
| static uint32_t out_get_latency(const struct audio_stream_out *stream) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| return (out->pcm_config.period_size * 1000) / out->pcm_config.rate; |
| } |
| |
| static int out_set_volume(struct audio_stream_out *stream, |
| float left, float right) { |
| return -ENOSYS; |
| } |
| |
| static int get_zone_id_from_address(const char *address) { |
| int zone_id = INVALID_ZONE_ID; |
| char *zone_start = strstr(address, AUDIO_ZONE_KEYWORD); |
| if (zone_start) { |
| char *end = NULL; |
| zone_id = strtol(zone_start + strlen(AUDIO_ZONE_KEYWORD), &end, 10); |
| if (end == NULL || zone_id < 0) { |
| return INVALID_ZONE_ID; |
| } |
| } |
| return zone_id; |
| } |
| |
| static void *out_write_worker(void *args) { |
| struct generic_stream_out *out = (struct generic_stream_out *)args; |
| struct ext_pcm *ext_pcm = NULL; |
| uint8_t *buffer = NULL; |
| int buffer_frames; |
| int buffer_size; |
| bool restart = false; |
| bool shutdown = false; |
| int zone_id = PRIMARY_ZONE_ID; |
| // If it is a audio zone keyword bus address then get zone id |
| if (strstr(out->bus_address, AUDIO_ZONE_KEYWORD)) { |
| zone_id = get_zone_id_from_address(out->bus_address); |
| if (zone_id == INVALID_ZONE_ID) { |
| ALOGE("%s Found invalid zone id, defaulting device %s to zone %d", __func__, |
| out->bus_address, DEFAULT_ZONE_TO_LEFT_SPEAKER); |
| zone_id = DEFAULT_ZONE_TO_LEFT_SPEAKER; |
| } |
| } |
| ALOGD("Out worker:%s zone id %d", out->bus_address, zone_id); |
| |
| while (true) { |
| pthread_mutex_lock(&out->lock); |
| while (out->worker_standby || restart) { |
| restart = false; |
| if (ext_pcm) { |
| ext_pcm_close(ext_pcm); // Frees pcm |
| ext_pcm = NULL; |
| free(buffer); |
| buffer=NULL; |
| } |
| if (out->worker_exit) { |
| break; |
| } |
| pthread_cond_wait(&out->worker_wake, &out->lock); |
| } |
| |
| if (out->worker_exit) { |
| if (!out->worker_standby) { |
| ALOGE("Out worker:%s not in standby before exiting", out->bus_address); |
| } |
| shutdown = true; |
| } |
| |
| while (!shutdown && audio_vbuffer_live(&out->buffer) == 0) { |
| pthread_cond_wait(&out->worker_wake, &out->lock); |
| } |
| |
| if (shutdown) { |
| pthread_mutex_unlock(&out->lock); |
| break; |
| } |
| |
| if (!ext_pcm) { |
| ext_pcm = ext_pcm_open(PCM_CARD, PCM_DEVICE, |
| PCM_OUT | PCM_MONOTONIC, &out->pcm_config); |
| if (!ext_pcm_is_ready(ext_pcm)) { |
| ALOGE("pcm_open(out) failed: %s: address %s channels %d format %d rate %d", |
| ext_pcm_get_error(ext_pcm), |
| out->bus_address, |
| out->pcm_config.channels, |
| out->pcm_config.format, |
| out->pcm_config.rate); |
| pthread_mutex_unlock(&out->lock); |
| break; |
| } |
| buffer_frames = out->pcm_config.period_size; |
| buffer_size = ext_pcm_frames_to_bytes(ext_pcm, buffer_frames); |
| buffer = malloc(buffer_size); |
| if (!buffer) { |
| ALOGE("could not allocate write buffer"); |
| pthread_mutex_unlock(&out->lock); |
| break; |
| } |
| } |
| int frames = audio_vbuffer_read(&out->buffer, buffer, buffer_frames); |
| pthread_cond_signal(&out->write_wake); |
| pthread_mutex_unlock(&out->lock); |
| |
| if (is_zone_selected_to_play(out->dev, zone_id)) { |
| int write_error = ext_pcm_write(ext_pcm, out->bus_address, |
| buffer, ext_pcm_frames_to_bytes(ext_pcm, frames)); |
| if (write_error) { |
| ALOGE("pcm_write failed %s address %s", |
| ext_pcm_get_error(ext_pcm), out->bus_address); |
| restart = true; |
| } else { |
| ALOGV("pcm_write succeed address %s", out->bus_address); |
| } |
| } |
| } |
| if (buffer) { |
| free(buffer); |
| } |
| |
| return NULL; |
| } |
| |
| // Call with out->lock held |
| static void get_current_output_position(struct generic_stream_out *out, |
| uint64_t *position, struct timespec * timestamp) { |
| struct timespec curtime = { .tv_sec = 0, .tv_nsec = 0 }; |
| clock_gettime(CLOCK_MONOTONIC, &curtime); |
| const int64_t now_us = (curtime.tv_sec * 1000000000LL + curtime.tv_nsec) / 1000; |
| if (timestamp) { |
| *timestamp = curtime; |
| } |
| int64_t position_since_underrun; |
| if (out->standby) { |
| position_since_underrun = 0; |
| } else { |
| const int64_t first_us = (out->underrun_time.tv_sec * 1000000000LL + |
| out->underrun_time.tv_nsec) / 1000; |
| position_since_underrun = (now_us - first_us) * |
| out_get_sample_rate(&out->stream.common) / |
| 1000000; |
| if (position_since_underrun < 0) { |
| position_since_underrun = 0; |
| } |
| } |
| *position = out->underrun_position + position_since_underrun; |
| |
| // The device will reuse the same output stream leading to periods of |
| // underrun. |
| if (*position > out->frames_written) { |
| ALOGW("Not supplying enough data to HAL, expected position %" PRIu64 " , only wrote " |
| "%" PRIu64, |
| *position, out->frames_written); |
| |
| *position = out->frames_written; |
| out->underrun_position = *position; |
| out->underrun_time = curtime; |
| out->frames_total_buffered = 0; |
| } |
| } |
| |
| // Applies gain naively, assumes AUDIO_FORMAT_PCM_16_BIT and stereo output |
| static void out_apply_gain(struct generic_stream_out *out, const void *buffer, size_t bytes) { |
| int16_t *int16_buffer = (int16_t *)buffer; |
| size_t int16_size = bytes / sizeof(int16_t); |
| for (int i = 0; i < int16_size; i++) { |
| if ((i % 2) && !(out->enabled_channels & RIGHT_CHANNEL)) { |
| int16_buffer[i] = 0; |
| } else if (!(i % 2) && !(out->enabled_channels & LEFT_CHANNEL)) { |
| int16_buffer[i] = 0; |
| } else { |
| float multiplied = int16_buffer[i] * out->amplitude_ratio; |
| if (out->is_ducked) { |
| multiplied = multiplied * DUCKING_MULTIPLIER; |
| } |
| |
| if (multiplied > INT16_MAX) int16_buffer[i] = INT16_MAX; |
| else if (multiplied < INT16_MIN) int16_buffer[i] = INT16_MIN; |
| else int16_buffer[i] = (int16_t)multiplied; |
| } |
| } |
| } |
| |
| static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, size_t bytes) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| ALOGV("%s: to device %s", __func__, out->bus_address); |
| const size_t frame_size = audio_stream_out_frame_size(stream); |
| const size_t frames = bytes / frame_size; |
| |
| set_shortened_thread_name(pthread_self(), __func__); |
| |
| pthread_mutex_lock(&out->lock); |
| |
| if (out->worker_standby) { |
| out->worker_standby = false; |
| } |
| |
| uint64_t current_position; |
| struct timespec current_time; |
| |
| get_current_output_position(out, ¤t_position, ¤t_time); |
| if (out->standby) { |
| out->standby = false; |
| out->underrun_time = current_time; |
| out->frames_rendered = 0; |
| out->frames_total_buffered = 0; |
| } |
| |
| size_t frames_written = frames; |
| |
| const int available_frames_in_buffer = audio_vbuffer_dead(&out->buffer); |
| const int frames_sleep = |
| available_frames_in_buffer > frames ? 0 : frames - available_frames_in_buffer; |
| const uint64_t sleep_time_us = |
| frames_sleep * 1000000LL / out_get_sample_rate(&stream->common); |
| |
| if (sleep_time_us > 0) { |
| pthread_mutex_unlock(&out->lock); |
| usleep(sleep_time_us); |
| pthread_mutex_lock(&out->lock); |
| } |
| |
| if (out->dev->master_mute || out->is_muted) { |
| ALOGV("%s: ignored due to mute", __func__); |
| } else { |
| out_apply_gain(out, buffer, bytes); |
| frames_written = 0; |
| |
| bool write_incomplete = true; |
| do { |
| frames_written += audio_vbuffer_write( |
| &out->buffer, |
| (const char *)buffer + frames_written * frame_size, |
| frames - frames_written); |
| pthread_cond_signal(&out->worker_wake); |
| write_incomplete = frames_written < frames; |
| if (write_incomplete) { |
| // Wait for write worker to consume the buffer |
| pthread_cond_wait(&out->write_wake, &out->lock); |
| } |
| } while (write_incomplete); |
| } |
| |
| /* Implementation just consumes bytes if we start getting backed up */ |
| out->frames_written += frames; |
| out->frames_rendered += frames; |
| out->frames_total_buffered += frames; |
| |
| pthread_mutex_unlock(&out->lock); |
| |
| if (frames_written < frames) { |
| ALOGW("%s Hardware backing HAL too slow, could only write %zu of %zu frames", |
| __func__, frames_written, frames); |
| } |
| |
| /* Always consume all bytes */ |
| return bytes; |
| } |
| |
| static int out_get_presentation_position(const struct audio_stream_out *stream, |
| uint64_t *frames, struct timespec *timestamp) { |
| int ret = -EINVAL; |
| if (stream == NULL || frames == NULL || timestamp == NULL) { |
| return -EINVAL; |
| } |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| |
| pthread_mutex_lock(&out->lock); |
| get_current_output_position(out, frames, timestamp); |
| pthread_mutex_unlock(&out->lock); |
| |
| return 0; |
| } |
| |
| static int out_get_render_position(const struct audio_stream_out *stream, uint32_t *dsp_frames) { |
| if (stream == NULL || dsp_frames == NULL) { |
| return -EINVAL; |
| } |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| pthread_mutex_lock(&out->lock); |
| *dsp_frames = out->frames_rendered; |
| pthread_mutex_unlock(&out->lock); |
| return 0; |
| } |
| |
| // Must be called with out->lock held |
| static void do_out_standby(struct generic_stream_out *out) { |
| int frames_sleep = 0; |
| uint64_t sleep_time_us = 0; |
| if (out->standby) { |
| return; |
| } |
| while (true) { |
| get_current_output_position(out, &out->underrun_position, NULL); |
| frames_sleep = out->frames_written - out->underrun_position; |
| |
| if (frames_sleep == 0) { |
| break; |
| } |
| |
| sleep_time_us = frames_sleep * 1000000LL / |
| out_get_sample_rate(&out->stream.common); |
| |
| pthread_mutex_unlock(&out->lock); |
| usleep(sleep_time_us); |
| pthread_mutex_lock(&out->lock); |
| } |
| out->worker_standby = true; |
| out->standby = true; |
| } |
| |
| static int out_standby(struct audio_stream *stream) { |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| pthread_mutex_lock(&out->lock); |
| do_out_standby(out); |
| pthread_mutex_unlock(&out->lock); |
| return 0; |
| } |
| |
| static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { |
| // out_add_audio_effect is a no op |
| return 0; |
| } |
| |
| static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { |
| // out_remove_audio_effect is a no op |
| return 0; |
| } |
| |
| static int out_get_next_write_timestamp(const struct audio_stream_out *stream, |
| int64_t *timestamp) { |
| return -ENOSYS; |
| } |
| |
| static uint32_t in_get_sample_rate(const struct audio_stream *stream) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| return in->req_config.sample_rate; |
| } |
| |
| static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) { |
| return -ENOSYS; |
| } |
| |
| static int refine_output_parameters(uint32_t *sample_rate, audio_format_t *format, |
| audio_channel_mask_t *channel_mask) { |
| static const uint32_t sample_rates [] = { |
| 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000 |
| }; |
| static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t); |
| bool inval = false; |
| if (*format != AUDIO_FORMAT_PCM_16_BIT) { |
| *format = AUDIO_FORMAT_PCM_16_BIT; |
| inval = true; |
| } |
| |
| int channel_count = popcount(*channel_mask); |
| if (channel_count != 1 && channel_count != 2) { |
| *channel_mask = AUDIO_CHANNEL_IN_STEREO; |
| inval = true; |
| } |
| |
| int i; |
| for (i = 0; i < sample_rates_count; i++) { |
| if (*sample_rate < sample_rates[i]) { |
| *sample_rate = sample_rates[i]; |
| inval=true; |
| break; |
| } |
| else if (*sample_rate == sample_rates[i]) { |
| break; |
| } |
| else if (i == sample_rates_count-1) { |
| // Cap it to the highest rate we support |
| *sample_rate = sample_rates[i]; |
| inval=true; |
| } |
| } |
| |
| if (inval) { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int refine_input_parameters(uint32_t *sample_rate, audio_format_t *format, |
| audio_channel_mask_t *channel_mask) { |
| static const uint32_t sample_rates [] = { |
| 8000, 11025, 16000, 22050, 44100, 48000 |
| }; |
| static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t); |
| bool inval = false; |
| // Only PCM_16_bit is supported. If this is changed, stereo to mono drop |
| // must be fixed in in_read |
| if (*format != AUDIO_FORMAT_PCM_16_BIT) { |
| *format = AUDIO_FORMAT_PCM_16_BIT; |
| inval = true; |
| } |
| |
| int channel_count = popcount(*channel_mask); |
| if (channel_count != 1 && channel_count != 2) { |
| *channel_mask = AUDIO_CHANNEL_IN_STEREO; |
| inval = true; |
| } |
| |
| int i; |
| for (i = 0; i < sample_rates_count; i++) { |
| if (*sample_rate < sample_rates[i]) { |
| *sample_rate = sample_rates[i]; |
| inval=true; |
| break; |
| } |
| else if (*sample_rate == sample_rates[i]) { |
| break; |
| } |
| else if (i == sample_rates_count-1) { |
| // Cap it to the highest rate we support |
| *sample_rate = sample_rates[i]; |
| inval=true; |
| } |
| } |
| |
| if (inval) { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format, |
| audio_channel_mask_t channel_mask) { |
| size_t size; |
| size_t device_rate; |
| int channel_count = popcount(channel_mask); |
| if (refine_input_parameters(&sample_rate, &format, &channel_mask) != 0) |
| return 0; |
| |
| size = sample_rate * get_in_period_ms() / 1000; |
| // Audioflinger expects audio buffers to be multiple of 16 frames |
| size = ((size + 15) / 16) * 16; |
| size *= sizeof(short) * channel_count; |
| |
| return size; |
| } |
| |
| static size_t in_get_buffer_size(const struct audio_stream *stream) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| int size = get_input_buffer_size(in->req_config.sample_rate, |
| in->req_config.format, |
| in->req_config.channel_mask); |
| |
| return size; |
| } |
| |
| static audio_channel_mask_t in_get_channels(const struct audio_stream *stream) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| return in->req_config.channel_mask; |
| } |
| |
| static audio_format_t in_get_format(const struct audio_stream *stream) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| return in->req_config.format; |
| } |
| |
| static int in_set_format(struct audio_stream *stream, audio_format_t format) { |
| return -ENOSYS; |
| } |
| |
| static int in_dump(const struct audio_stream *stream, int fd) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| |
| pthread_mutex_lock(&in->lock); |
| dprintf(fd, "\tin_dump:\n" |
| "\t\tsample rate: %u\n" |
| "\t\tbuffer size: %zu\n" |
| "\t\tchannel mask: %08x\n" |
| "\t\tformat: %d\n" |
| "\t\tdevice: %08x\n" |
| "\t\taudio dev: %p\n\n", |
| in_get_sample_rate(stream), |
| in_get_buffer_size(stream), |
| in_get_channels(stream), |
| in_get_format(stream), |
| in->device, |
| in->dev); |
| pthread_mutex_unlock(&in->lock); |
| return 0; |
| } |
| |
| static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| struct str_parms *parms; |
| int ret = 0; |
| |
| pthread_mutex_lock(&in->lock); |
| if (!in->standby) { |
| ret = -ENOSYS; |
| } else { |
| parms = str_parms_create_str(kvpairs); |
| int val = 0; |
| ret = get_int_value(parms, AUDIO_PARAMETER_STREAM_ROUTING, &val); |
| if (ret >= 0) { |
| in->device = (int)val; |
| ret = 0; |
| } |
| |
| str_parms_destroy(parms); |
| } |
| pthread_mutex_unlock(&in->lock); |
| return ret; |
| } |
| |
| static char *in_get_parameters(const struct audio_stream *stream, const char *keys) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| struct str_parms *query = str_parms_create_str(keys); |
| char *str; |
| char value[256]; |
| struct str_parms *reply = str_parms_create(); |
| int ret; |
| |
| ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); |
| if (ret >= 0) { |
| str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, in->device); |
| str = strdup(str_parms_to_str(reply)); |
| } else { |
| str = strdup(keys); |
| } |
| |
| str_parms_destroy(query); |
| str_parms_destroy(reply); |
| return str; |
| } |
| |
| static int in_set_gain(struct audio_stream_in *stream, float gain) { |
| // TODO(hwwang): support adjusting input gain |
| return 0; |
| } |
| |
| // Call with in->lock held |
| static void get_current_input_position(struct generic_stream_in *in, |
| int64_t * position, struct timespec * timestamp) { |
| struct timespec t = { .tv_sec = 0, .tv_nsec = 0 }; |
| clock_gettime(CLOCK_MONOTONIC, &t); |
| const int64_t now_us = (t.tv_sec * 1000000000LL + t.tv_nsec) / 1000; |
| if (timestamp) { |
| *timestamp = t; |
| } |
| int64_t position_since_standby; |
| if (in->standby) { |
| position_since_standby = 0; |
| } else { |
| const int64_t first_us = (in->standby_exit_time.tv_sec * 1000000000LL + |
| in->standby_exit_time.tv_nsec) / 1000; |
| position_since_standby = (now_us - first_us) * |
| in_get_sample_rate(&in->stream.common) / |
| 1000000; |
| if (position_since_standby < 0) { |
| position_since_standby = 0; |
| } |
| } |
| *position = in->standby_position + position_since_standby; |
| } |
| |
| // Must be called with in->lock held |
| static void do_in_standby(struct generic_stream_in *in) { |
| if (in->standby) { |
| return; |
| } |
| in->worker_standby = true; |
| get_current_input_position(in, &in->standby_position, NULL); |
| in->standby = true; |
| } |
| |
| static int in_standby(struct audio_stream *stream) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| pthread_mutex_lock(&in->lock); |
| do_in_standby(in); |
| pthread_mutex_unlock(&in->lock); |
| return 0; |
| } |
| |
| // Generates pure tone for FM_TUNER and bus_device |
| static int pseudo_pcm_read(void *data, unsigned int count, struct oscillator *oscillator) { |
| unsigned int length = count / sizeof(int16_t); |
| int16_t *sdata = (int16_t *)data; |
| for (int index = 0; index < length; index++) { |
| sdata[index] = (int16_t)(sin(oscillator->phase) * 4096); |
| oscillator->phase += oscillator->phase_increment; |
| oscillator->phase = oscillator->phase > TWO_PI ? |
| oscillator->phase - TWO_PI : oscillator->phase; |
| } |
| |
| return count; |
| } |
| |
| static void *in_read_worker(void *args) { |
| struct generic_stream_in *in = (struct generic_stream_in *)args; |
| struct pcm *pcm = NULL; |
| uint8_t *buffer = NULL; |
| size_t buffer_frames; |
| int buffer_size; |
| |
| bool restart = false; |
| bool shutdown = false; |
| while (true) { |
| pthread_mutex_lock(&in->lock); |
| while (in->worker_standby || restart) { |
| restart = false; |
| if (pcm) { |
| pcm_close(pcm); // Frees pcm |
| pcm = NULL; |
| free(buffer); |
| buffer=NULL; |
| } |
| if (in->worker_exit) { |
| break; |
| } |
| pthread_cond_wait(&in->worker_wake, &in->lock); |
| } |
| |
| if (in->worker_exit) { |
| if (!in->worker_standby) { |
| ALOGE("In worker not in standby before exiting"); |
| } |
| shutdown = true; |
| } |
| if (shutdown) { |
| pthread_mutex_unlock(&in->lock); |
| break; |
| } |
| if (!pcm) { |
| pcm = pcm_open(PCM_CARD, PCM_DEVICE, |
| PCM_IN | PCM_MONOTONIC, &in->pcm_config); |
| if (!pcm_is_ready(pcm)) { |
| ALOGE("pcm_open(in) failed: %s: channels %d format %d rate %d", |
| pcm_get_error(pcm), |
| in->pcm_config.channels, |
| in->pcm_config.format, |
| in->pcm_config.rate); |
| pthread_mutex_unlock(&in->lock); |
| break; |
| } |
| buffer_frames = in->pcm_config.period_size; |
| buffer_size = pcm_frames_to_bytes(pcm, buffer_frames); |
| buffer = malloc(buffer_size); |
| if (!buffer) { |
| ALOGE("could not allocate worker read buffer"); |
| pthread_mutex_unlock(&in->lock); |
| break; |
| } |
| } |
| pthread_mutex_unlock(&in->lock); |
| int ret = pcm_read(pcm, buffer, pcm_frames_to_bytes(pcm, buffer_frames)); |
| if (ret != 0) { |
| ALOGW("pcm_read failed %s", pcm_get_error(pcm)); |
| restart = true; |
| } |
| |
| pthread_mutex_lock(&in->lock); |
| size_t frames_written = audio_vbuffer_write(&in->buffer, buffer, buffer_frames); |
| pthread_mutex_unlock(&in->lock); |
| |
| if (frames_written != buffer_frames) { |
| ALOGV("in_read_worker only could write %zu / %zu frames", |
| frames_written, buffer_frames); |
| } |
| } |
| if (buffer) { |
| free(buffer); |
| } |
| return NULL; |
| } |
| |
| static bool address_has_tone_keyword(char * address) { |
| return strstr(address, TONE_ADDRESS_KEYWORD) != NULL; |
| } |
| |
| static bool is_tone_generator_device(struct generic_stream_in *in) { |
| return in->device == AUDIO_DEVICE_IN_FM_TUNER || ((in->device == AUDIO_DEVICE_IN_BUS) && |
| address_has_tone_keyword(in->bus_address)); |
| } |
| |
| static bool is_microphone_device(struct generic_stream_in *in) { |
| return in->device == AUDIO_DEVICE_IN_BACK_MIC || |
| in->device == AUDIO_DEVICE_IN_BUILTIN_MIC; |
| } |
| |
| static ssize_t in_read(struct audio_stream_in *stream, void *buffer, size_t bytes) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| struct generic_audio_device *adev = in->dev; |
| const size_t frames = bytes / audio_stream_in_frame_size(stream); |
| int ret = 0; |
| bool read_mute = false; |
| bool mic_mute = false; |
| bool is_tone_generator = false; |
| size_t read_bytes = 0; |
| |
| set_shortened_thread_name(pthread_self(), __func__); |
| |
| adev_get_mic_mute(&adev->device, &mic_mute); |
| pthread_mutex_lock(&in->lock); |
| |
| if (in->worker_standby) { |
| in->worker_standby = false; |
| } |
| |
| // Only mute read if mic is muted and device is mic. |
| // Other devices, e.g. FM_TUNER, are not muted by mic mute |
| read_mute = mic_mute && is_microphone_device(in); |
| |
| is_tone_generator = is_tone_generator_device(in); |
| // Tone generators fill the buffer via pseudo_pcm_read directly |
| if (!is_tone_generator) { |
| pthread_cond_signal(&in->worker_wake); |
| } |
| |
| int64_t current_position; |
| struct timespec current_time; |
| |
| get_current_input_position(in, ¤t_position, ¤t_time); |
| if (in->standby) { |
| in->standby = false; |
| in->standby_exit_time = current_time; |
| in->standby_frames_read = 0; |
| } |
| |
| const int64_t frames_available = |
| current_position - in->standby_position - in->standby_frames_read; |
| assert(frames_available >= 0); |
| |
| const size_t frames_wait = |
| ((uint64_t)frames_available > frames) ? 0 : frames - frames_available; |
| |
| int64_t sleep_time_us = frames_wait * 1000000LL / in_get_sample_rate(&stream->common); |
| |
| pthread_mutex_unlock(&in->lock); |
| |
| if (sleep_time_us > 0) { |
| usleep(sleep_time_us); |
| } |
| |
| pthread_mutex_lock(&in->lock); |
| int read_frames = 0; |
| if (in->standby) { |
| ALOGW("Input put to sleep while read in progress"); |
| goto exit; |
| } |
| in->standby_frames_read += frames; |
| |
| if (is_tone_generator) { |
| int read_bytes = pseudo_pcm_read(buffer, bytes, &in->oscillator); |
| read_frames = read_bytes / audio_stream_in_frame_size(stream); |
| } else if (popcount(in->req_config.channel_mask) == 1 && |
| in->pcm_config.channels == 2) { |
| // Need to resample to mono |
| if (in->stereo_to_mono_buf_size < bytes*2) { |
| in->stereo_to_mono_buf = realloc(in->stereo_to_mono_buf, bytes*2); |
| if (!in->stereo_to_mono_buf) { |
| ALOGE("Failed to allocate stereo_to_mono_buff"); |
| goto exit; |
| } |
| } |
| |
| read_frames = audio_vbuffer_read(&in->buffer, in->stereo_to_mono_buf, frames); |
| |
| // Currently only pcm 16 is supported. |
| uint16_t *src = (uint16_t *)in->stereo_to_mono_buf; |
| uint16_t *dst = (uint16_t *)buffer; |
| size_t i; |
| // Resample stereo 16 to mono 16 by dropping one channel. |
| // The stereo stream is interleaved L-R-L-R |
| for (i = 0; i < frames; i++) { |
| *dst = *src; |
| src += 2; |
| dst += 1; |
| } |
| } else { |
| read_frames = audio_vbuffer_read(&in->buffer, buffer, frames); |
| } |
| |
| exit: |
| read_bytes = read_frames*audio_stream_in_frame_size(stream); |
| |
| if (read_mute) { |
| read_bytes = 0; |
| } |
| |
| if (read_bytes < bytes) { |
| memset (&((uint8_t *)buffer)[read_bytes], 0, bytes-read_bytes); |
| } |
| |
| pthread_mutex_unlock(&in->lock); |
| |
| return bytes; |
| } |
| |
| static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) { |
| return 0; |
| } |
| |
| static int in_get_capture_position(const struct audio_stream_in *stream, |
| int64_t *frames, int64_t *time) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| pthread_mutex_lock(&in->lock); |
| struct timespec current_time; |
| get_current_input_position(in, frames, ¤t_time); |
| *time = (current_time.tv_sec * 1000000000LL + current_time.tv_nsec); |
| pthread_mutex_unlock(&in->lock); |
| return 0; |
| } |
| |
| static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { |
| // in_add_audio_effect is a no op |
| return 0; |
| } |
| |
| static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { |
| // in_add_audio_effect is a no op |
| return 0; |
| } |
| |
| static int adev_open_output_stream(struct audio_hw_device *dev, |
| audio_io_handle_t handle, audio_devices_t devices, audio_output_flags_t flags, |
| struct audio_config *config, struct audio_stream_out **stream_out, const char *address) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| struct generic_stream_out *out; |
| int ret = 0; |
| |
| if (refine_output_parameters(&config->sample_rate, &config->format, &config->channel_mask)) { |
| ALOGE("Error opening output stream format %d, channel_mask %04x, sample_rate %u", |
| config->format, config->channel_mask, config->sample_rate); |
| ret = -EINVAL; |
| goto error; |
| } |
| |
| out = (struct generic_stream_out *)calloc(1, sizeof(struct generic_stream_out)); |
| |
| if (!out) |
| return -ENOMEM; |
| |
| 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.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->stream.get_presentation_position = out_get_presentation_position; |
| out->stream.get_next_write_timestamp = out_get_next_write_timestamp; |
| |
| pthread_mutex_init(&out->lock, (const pthread_mutexattr_t *) NULL); |
| out->dev = adev; |
| out->device = devices; |
| memcpy(&out->req_config, config, sizeof(struct audio_config)); |
| memcpy(&out->pcm_config, &pcm_config_out, sizeof(struct pcm_config)); |
| out->pcm_config.rate = config->sample_rate; |
| out->pcm_config.period_size = out->pcm_config.rate * get_out_period_ms() / 1000; |
| out->pcm_config.start_threshold = out->pcm_config.period_count * out->pcm_config.period_size; |
| |
| out->standby = true; |
| out->underrun_position = 0; |
| out->underrun_time.tv_sec = 0; |
| out->underrun_time.tv_nsec = 0; |
| out->last_write_time_us = 0; |
| out->frames_total_buffered = 0; |
| out->frames_written = 0; |
| out->frames_rendered = 0; |
| |
| ret = audio_vbuffer_init(&out->buffer, |
| out->pcm_config.period_size*out->pcm_config.period_count, |
| out->pcm_config.channels * |
| pcm_format_to_bits(out->pcm_config.format) >> 3); |
| if (ret == 0) { |
| pthread_cond_init(&out->worker_wake, NULL); |
| out->worker_standby = true; |
| out->worker_exit = false; |
| |
| out->enabled_channels = BOTH_CHANNELS; |
| // For targets where output streams are closed regularly, currently ducked/muted addresses |
| // should be tracked so that the address of new streams can be checked to determine the |
| // default state |
| out->is_ducked = 0; |
| out->is_muted = 0; |
| if (address) { |
| out->bus_address = calloc(strlen(address) + 1, sizeof(char)); |
| strncpy(out->bus_address, address, strlen(address)); |
| hashmapPut(adev->out_bus_stream_map, out->bus_address, out); |
| /* TODO: read struct audio_gain from audio_policy_configuration */ |
| out->gain_stage = (struct audio_gain) { |
| .min_value = -3200, |
| .max_value = 600, |
| .step_value = 100, |
| }; |
| out->amplitude_ratio = 1.0; |
| if (property_get_bool(PROP_KEY_SIMULATE_MULTI_ZONE_AUDIO, false)) { |
| out->enabled_channels = strstr(out->bus_address, AUDIO_ZONE_KEYWORD) |
| ? RIGHT_CHANNEL: LEFT_CHANNEL; |
| ALOGD("%s Routing %s to %s channel", __func__, |
| out->bus_address, out->enabled_channels == RIGHT_CHANNEL ? "Right" : "Left"); |
| } |
| } |
| pthread_create(&out->worker_thread, NULL, out_write_worker, out); |
| set_shortened_thread_name(out->worker_thread, address); |
| *stream_out = &out->stream; |
| ALOGD("%s bus: %s", __func__, out->bus_address); |
| } |
| |
| error: |
| return ret; |
| } |
| |
| static void adev_close_output_stream(struct audio_hw_device *dev, |
| struct audio_stream_out *stream) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| struct generic_stream_out *out = (struct generic_stream_out *)stream; |
| ALOGD("%s bus:%s", __func__, out->bus_address); |
| pthread_mutex_lock(&out->lock); |
| do_out_standby(out); |
| |
| out->worker_exit = true; |
| pthread_cond_signal(&out->worker_wake); |
| pthread_mutex_unlock(&out->lock); |
| |
| pthread_join(out->worker_thread, NULL); |
| pthread_mutex_destroy(&out->lock); |
| audio_vbuffer_destroy(&out->buffer); |
| |
| if (out->bus_address) { |
| hashmapRemove(adev->out_bus_stream_map, out->bus_address); |
| free(out->bus_address); |
| } |
| free(stream); |
| } |
| |
| static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| pthread_mutex_lock(&adev->lock); |
| struct str_parms *parms = str_parms_create_str(kvpairs); |
| int value = 0; |
| int results = get_int_value(parms, AAE_PARAMETER_KEY_FOR_SELECTED_ZONE, &value); |
| if (results >= 0) { |
| adev->last_zone_selected_to_play = value; |
| results = 0; |
| ALOGD("%s Changed play zone id to %d", __func__, adev->last_zone_selected_to_play); |
| } |
| results = audio_extn_hfp_set_parameters(adev, parms); |
| str_parms_destroy(parms); |
| pthread_mutex_unlock(&adev->lock); |
| return results; |
| } |
| |
| static char *adev_get_parameters(const struct audio_hw_device * dev, const char *keys) { |
| return NULL; |
| } |
| |
| 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) { |
| // adev_set_voice_volume is a no op (simulates phones) |
| return 0; |
| } |
| |
| static int adev_set_master_volume(struct audio_hw_device *dev, float volume) { |
| return -ENOSYS; |
| } |
| |
| static int adev_get_master_volume(struct audio_hw_device *dev, float *volume) { |
| return -ENOSYS; |
| } |
| |
| static int adev_set_master_mute(struct audio_hw_device *dev, bool muted) { |
| ALOGD("%s: %s", __func__, _bool_str(muted)); |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| pthread_mutex_lock(&adev->lock); |
| adev->master_mute = muted; |
| pthread_mutex_unlock(&adev->lock); |
| return 0; |
| } |
| |
| static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| pthread_mutex_lock(&adev->lock); |
| *muted = adev->master_mute; |
| pthread_mutex_unlock(&adev->lock); |
| ALOGD("%s: %s", __func__, _bool_str(*muted)); |
| return 0; |
| } |
| |
| static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode) { |
| // adev_set_mode is a no op (simulates phones) |
| return 0; |
| } |
| |
| static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| pthread_mutex_lock(&adev->lock); |
| adev->mic_mute = state; |
| pthread_mutex_unlock(&adev->lock); |
| return 0; |
| } |
| |
| static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| pthread_mutex_lock(&adev->lock); |
| *state = adev->mic_mute; |
| pthread_mutex_unlock(&adev->lock); |
| return 0; |
| } |
| |
| static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, |
| const struct audio_config *config) { |
| return get_input_buffer_size(config->sample_rate, config->format, config->channel_mask); |
| } |
| |
| static void adev_close_input_stream(struct audio_hw_device *dev, |
| struct audio_stream_in *stream) { |
| struct generic_stream_in *in = (struct generic_stream_in *)stream; |
| pthread_mutex_lock(&in->lock); |
| do_in_standby(in); |
| |
| in->worker_exit = true; |
| pthread_cond_signal(&in->worker_wake); |
| pthread_mutex_unlock(&in->lock); |
| pthread_join(in->worker_thread, NULL); |
| |
| if (in->stereo_to_mono_buf != NULL) { |
| free(in->stereo_to_mono_buf); |
| in->stereo_to_mono_buf_size = 0; |
| } |
| |
| if (in->bus_address) { |
| free(in->bus_address); |
| } |
| |
| pthread_mutex_destroy(&in->lock); |
| audio_vbuffer_destroy(&in->buffer); |
| free(stream); |
| } |
| |
| static void increase_next_tone_frequency(struct generic_audio_device *adev) { |
| adev->next_tone_frequency_to_assign += TONE_FREQUENCY_INCREASE; |
| if (adev->next_tone_frequency_to_assign > MAX_TONE_FREQUENCY) { |
| adev->next_tone_frequency_to_assign = DEFAULT_FREQUENCY; |
| } |
| } |
| |
| static int create_or_fetch_tone_frequency(struct generic_audio_device *adev, |
| char *address, int update_frequency) { |
| int *frequency = hashmapGet(adev->in_bus_tone_frequency_map, address); |
| if (frequency == NULL) { |
| frequency = calloc(1, sizeof(int)); |
| *frequency = update_frequency; |
| hashmapPut(adev->in_bus_tone_frequency_map, strdup(address), frequency); |
| ALOGD("%s assigned frequency %d to %s", __func__, *frequency, address); |
| } |
| return *frequency; |
| } |
| |
| static int adev_open_input_stream(struct audio_hw_device *dev, |
| audio_io_handle_t handle, audio_devices_t devices, struct audio_config *config, |
| struct audio_stream_in **stream_in, audio_input_flags_t flags __unused, const char *address, |
| audio_source_t source) { |
| ALOGV("%s: audio_source_t: %d", __func__, source); |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| struct generic_stream_in *in; |
| int ret = 0; |
| if (refine_input_parameters(&config->sample_rate, &config->format, &config->channel_mask)) { |
| ALOGE("Error opening input stream format %d, channel_mask %04x, sample_rate %u", |
| config->format, config->channel_mask, config->sample_rate); |
| ret = -EINVAL; |
| goto error; |
| } |
| |
| in = (struct generic_stream_in *)calloc(1, sizeof(struct generic_stream_in)); |
| if (!in) { |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| in->stream.common.get_sample_rate = in_get_sample_rate; |
| in->stream.common.set_sample_rate = in_set_sample_rate; // no op |
| in->stream.common.get_buffer_size = in_get_buffer_size; |
| in->stream.common.get_channels = in_get_channels; |
| in->stream.common.get_format = in_get_format; |
| in->stream.common.set_format = in_set_format; // no op |
| in->stream.common.standby = in_standby; |
| in->stream.common.dump = in_dump; |
| in->stream.common.set_parameters = in_set_parameters; |
| in->stream.common.get_parameters = in_get_parameters; |
| in->stream.common.add_audio_effect = in_add_audio_effect; // no op |
| in->stream.common.remove_audio_effect = in_remove_audio_effect; // no op |
| in->stream.set_gain = in_set_gain; // no op |
| in->stream.read = in_read; |
| in->stream.get_input_frames_lost = in_get_input_frames_lost; // no op |
| in->stream.get_capture_position = in_get_capture_position; |
| |
| pthread_mutex_init(&in->lock, (const pthread_mutexattr_t *) NULL); |
| in->dev = adev; |
| in->device = devices; |
| memcpy(&in->req_config, config, sizeof(struct audio_config)); |
| memcpy(&in->pcm_config, &pcm_config_in, sizeof(struct pcm_config)); |
| in->pcm_config.rate = config->sample_rate; |
| in->pcm_config.period_size = in->pcm_config.rate * get_in_period_ms() / 1000; |
| |
| in->stereo_to_mono_buf = NULL; |
| in->stereo_to_mono_buf_size = 0; |
| |
| in->standby = true; |
| in->standby_position = 0; |
| in->standby_exit_time.tv_sec = 0; |
| in->standby_exit_time.tv_nsec = 0; |
| in->standby_frames_read = 0; |
| |
| ret = audio_vbuffer_init(&in->buffer, |
| in->pcm_config.period_size*in->pcm_config.period_count, |
| in->pcm_config.channels * |
| pcm_format_to_bits(in->pcm_config.format) >> 3); |
| if (ret == 0) { |
| pthread_cond_init(&in->worker_wake, NULL); |
| in->worker_standby = true; |
| in->worker_exit = false; |
| pthread_create(&in->worker_thread, NULL, in_read_worker, in); |
| set_shortened_thread_name(in->worker_thread, address ? address : "mic"); |
| } |
| |
| if (address) { |
| in->bus_address = strdup(address); |
| if (is_tone_generator_device(in)) { |
| int update_frequency = adev->next_tone_frequency_to_assign; |
| int frequency = create_or_fetch_tone_frequency(adev, address, update_frequency); |
| if (update_frequency == frequency) { |
| increase_next_tone_frequency(adev); |
| } |
| in->oscillator.phase = 0.0f; |
| in->oscillator.phase_increment = (TWO_PI*(frequency)) |
| / ((float) in_get_sample_rate(&in->stream.common)); |
| } |
| } |
| |
| *stream_in = &in->stream; |
| |
| error: |
| return ret; |
| } |
| |
| static int adev_dump(const audio_hw_device_t *dev, int fd) { |
| return 0; |
| } |
| |
| static int adev_set_audio_port_config(struct audio_hw_device *dev, |
| const struct audio_port_config *config) { |
| int ret = 0; |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| const char *bus_address = config->ext.device.address; |
| struct generic_stream_out *out = hashmapGet(adev->out_bus_stream_map, bus_address); |
| if (out) { |
| pthread_mutex_lock(&out->lock); |
| int gainIndex = (config->gain.values[0] - out->gain_stage.min_value) / |
| out->gain_stage.step_value; |
| int totalSteps = (out->gain_stage.max_value - out->gain_stage.min_value) / |
| out->gain_stage.step_value; |
| int minDb = out->gain_stage.min_value / 100; |
| int maxDb = out->gain_stage.max_value / 100; |
| // curve: 10^((minDb + (maxDb - minDb) * gainIndex / totalSteps) / 20) |
| // 2x10, where 10 comes from the log 10 conversion from power ratios, |
| // which are the square (2) of amplitude |
| out->amplitude_ratio = pow(10, |
| (minDb + (maxDb - minDb) * (gainIndex / (float)totalSteps)) / 20); |
| pthread_mutex_unlock(&out->lock); |
| ALOGD("%s: set audio gain: %f on %s", |
| __func__, out->amplitude_ratio, bus_address); |
| } else { |
| ALOGE("%s: can not find output stream by bus_address:%s", __func__, bus_address); |
| ret = -EINVAL; |
| } |
| return ret; |
| } |
| |
| static int adev_create_audio_patch(struct audio_hw_device *dev, |
| unsigned int num_sources, |
| const struct audio_port_config *sources, |
| unsigned int num_sinks, |
| const struct audio_port_config *sinks, |
| audio_patch_handle_t *handle) { |
| struct generic_audio_device *audio_dev = (struct generic_audio_device *)dev; |
| for (int i = 0; i < num_sources; i++) { |
| ALOGD("%s: source[%d] type=%d address=%s", __func__, i, sources[i].type, |
| sources[i].type == AUDIO_PORT_TYPE_DEVICE |
| ? sources[i].ext.device.address |
| : ""); |
| } |
| for (int i = 0; i < num_sinks; i++) { |
| ALOGD("%s: sink[%d] type=%d address=%s", __func__, i, sinks[i].type, |
| sinks[i].type == AUDIO_PORT_TYPE_DEVICE ? sinks[i].ext.device.address |
| : "N/A"); |
| } |
| if (num_sources == 1 && num_sinks == 1 && |
| sources[0].type == AUDIO_PORT_TYPE_DEVICE && |
| sinks[0].type == AUDIO_PORT_TYPE_DEVICE) { |
| pthread_mutex_lock(&audio_dev->lock); |
| audio_dev->last_patch_id += 1; |
| pthread_mutex_unlock(&audio_dev->lock); |
| *handle = audio_dev->last_patch_id; |
| ALOGD("%s: handle: %d", __func__, *handle); |
| } |
| return 0; |
| } |
| |
| static int adev_release_audio_patch(struct audio_hw_device *dev, |
| audio_patch_handle_t handle) { |
| ALOGD("%s: handle: %d", __func__, handle); |
| return 0; |
| } |
| |
| static int adev_close(hw_device_t *dev) { |
| struct generic_audio_device *adev = (struct generic_audio_device *)dev; |
| int ret = 0; |
| if (!adev) |
| return 0; |
| |
| pthread_mutex_lock(&lock); |
| |
| if (audio_device_ref_count == 0) { |
| ALOGE("adev_close called when ref_count 0"); |
| ret = -EINVAL; |
| goto error; |
| } |
| |
| if ((--audio_device_ref_count) == 0) { |
| if (adev->mixer) { |
| mixer_close(adev->mixer); |
| } |
| if (adev->out_bus_stream_map) { |
| hashmapFree(adev->out_bus_stream_map); |
| } |
| if (adev->in_bus_tone_frequency_map) { |
| hashmapFree(adev->in_bus_tone_frequency_map); |
| } |
| |
| device_handle = 0; |
| free(adev); |
| } |
| |
| error: |
| pthread_mutex_unlock(&lock); |
| return ret; |
| } |
| |
| /* copied from libcutils/str_parms.c */ |
| static bool str_eq(void *key_a, void *key_b) { |
| return !strcmp((const char *)key_a, (const char *)key_b); |
| } |
| |
| /** |
| * use djb hash unless we find it inadequate. |
| * copied from libcutils/str_parms.c |
| */ |
| #ifdef __clang__ |
| __attribute__((no_sanitize("integer"))) |
| #endif |
| static int str_hash_fn(void *str) { |
| uint32_t hash = 5381; |
| char *p; |
| for (p = str; p && *p; p++) { |
| hash = ((hash << 5) + hash) + *p; |
| } |
| return (int)hash; |
| } |
| |
| static int adev_open(const hw_module_t *module, |
| const char *name, hw_device_t **device) { |
| static struct generic_audio_device *adev; |
| |
| if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) |
| return -EINVAL; |
| |
| pthread_mutex_lock(&lock); |
| if (audio_device_ref_count != 0) { |
| *device = &adev->device.common; |
| audio_device_ref_count++; |
| ALOGV("%s: returning existing instance of adev", __func__); |
| ALOGV("%s: exit", __func__); |
| goto unlock; |
| } |
| |
| pcm_config_in.period_count = get_in_period_count(); |
| pcm_config_out.period_count = get_out_period_count(); |
| |
| adev = calloc(1, sizeof(struct generic_audio_device)); |
| |
| pthread_mutex_init(&adev->lock, (const pthread_mutexattr_t *) NULL); |
| |
| adev->device.common.tag = HARDWARE_DEVICE_TAG; |
| adev->device.common.version = AUDIO_DEVICE_API_VERSION_3_0; |
| adev->device.common.module = (struct hw_module_t *) module; |
| adev->device.common.close = adev_close; |
| |
| adev->device.init_check = adev_init_check; // no op |
| adev->device.set_voice_volume = adev_set_voice_volume; // no op |
| adev->device.set_master_volume = adev_set_master_volume; // no op |
| adev->device.get_master_volume = adev_get_master_volume; // no op |
| adev->device.set_master_mute = adev_set_master_mute; |
| adev->device.get_master_mute = adev_get_master_mute; |
| adev->device.set_mode = adev_set_mode; // no op |
| 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; // no op |
| adev->device.get_parameters = adev_get_parameters; // no op |
| 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; |
| |
| // New in AUDIO_DEVICE_API_VERSION_3_0 |
| adev->device.set_audio_port_config = adev_set_audio_port_config; |
| adev->device.create_audio_patch = adev_create_audio_patch; |
| adev->device.release_audio_patch = adev_release_audio_patch; |
| |
| *device = &adev->device.common; |
| |
| adev->mixer = mixer_open(PCM_CARD); |
| |
| ALOGD("%s Mixer name %s", __func__, mixer_get_name(adev->mixer)); |
| struct mixer_ctl *ctl; |
| |
| // Set default mixer ctls |
| // Enable channels and set volume |
| for (int i = 0; i < (int)mixer_get_num_ctls(adev->mixer); i++) { |
| ctl = mixer_get_ctl(adev->mixer, i); |
| ALOGD("mixer %d name %s", i, mixer_ctl_get_name(ctl)); |
| if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Volume") || |
| !strcmp(mixer_ctl_get_name(ctl), "Capture Volume")) { |
| for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) { |
| ALOGD("set ctl %d to %d", z, 100); |
| mixer_ctl_set_percent(ctl, z, 100); |
| } |
| continue; |
| } |
| if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Switch") || |
| !strcmp(mixer_ctl_get_name(ctl), "Capture Switch")) { |
| for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) { |
| ALOGD("set ctl %d to %d", z, 1); |
| mixer_ctl_set_value(ctl, z, 1); |
| } |
| continue; |
| } |
| } |
| |
| // Initialize the bus address to output stream map |
| adev->out_bus_stream_map = hashmapCreate(5, str_hash_fn, str_eq); |
| |
| // Initialize the bus address to input stream map |
| adev->in_bus_tone_frequency_map = hashmapCreate(5, str_hash_fn, str_eq); |
| |
| adev->next_tone_frequency_to_assign = DEFAULT_FREQUENCY; |
| |
| adev->last_zone_selected_to_play = DEFAULT_ZONE_TO_LEFT_SPEAKER; |
| |
| adev->hfp_running = false; |
| |
| device_handle = adev; |
| |
| audio_device_ref_count++; |
| |
| unlock: |
| pthread_mutex_unlock(&lock); |
| return 0; |
| } |
| |
| static struct hw_module_methods_t hal_module_methods = { |
| .open = adev_open, |
| }; |
| |
| struct audio_module HAL_MODULE_INFO_SYM = { |
| .common = { |
| .tag = HARDWARE_MODULE_TAG, |
| .module_api_version = AUDIO_MODULE_API_VERSION_0_1, |
| .hal_api_version = HARDWARE_HAL_API_VERSION, |
| .id = AUDIO_HARDWARE_MODULE_ID, |
| .name = "Generic car audio HW HAL", |
| .author = "The Android Open Source Project", |
| .methods = &hal_module_methods, |
| }, |
| }; |