diff options
author | Joonas Kylmälä <joonas.kylmala@iki.fi> | 2020-04-10 10:41:00 -0400 |
---|---|---|
committer | Joonas Kylmälä <joonas.kylmala@iki.fi> | 2020-04-10 10:41:00 -0400 |
commit | 697e4dd0e6b483da2e7b17e1a82b0b47104dcc7f (patch) | |
tree | 5ccd90eed0e8980c65588ab0aae51a1eaab9a629 /audio/audio_hw.c | |
download | device_samsung_midas-common-697e4dd0e6b483da2e7b17e1a82b0b47104dcc7f.tar.gz device_samsung_midas-common-697e4dd0e6b483da2e7b17e1a82b0b47104dcc7f.tar.bz2 device_samsung_midas-common-697e4dd0e6b483da2e7b17e1a82b0b47104dcc7f.zip |
Initial commit
Signed-off-by: Joonas Kylmälä <joonas.kylmala@iki.fi>
Diffstat (limited to 'audio/audio_hw.c')
-rw-r--r-- | audio/audio_hw.c | 1674 |
1 files changed, 1674 insertions, 0 deletions
diff --git a/audio/audio_hw.c b/audio/audio_hw.c new file mode 100644 index 0000000..62a2daa --- /dev/null +++ b/audio/audio_hw.c @@ -0,0 +1,1674 @@ +/* + * Copyright (C) 2012 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. + */ + +#define LOG_TAG "audio_hw_generic" + +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/time.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#include <log/log.h> +#include <cutils/str_parms.h> + +#include <hardware/hardware.h> +#include <system/audio.h> +#include <hardware/audio.h> +#include <tinyalsa/asoundlib.h> + +#define PCM_CARD 0 +#define PCM_DEVICE 0 + + +#define OUT_PERIOD_MS 15 +#define OUT_PERIOD_COUNT 4 + +#define IN_PERIOD_MS 15 +#define IN_PERIOD_COUNT 4 + +struct generic_audio_device { + struct audio_hw_device device; // Constant after init + pthread_mutex_t lock; + bool mic_mute; // Proteced by this->lock + struct mixer* mixer; // Proteced by this->lock +}; + +/* If not NULL, this is a pointer to the fallback module. + * This really is the original default audio device /dev/eac which we will use + * if no alsa devices are detected. + */ +static struct audio_module* sFallback; +static pthread_once_t sFallbackOnce = PTHREAD_ONCE_INIT; +static void fallback_init(void); +static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state); +static int adev_get_microphones(const audio_hw_device_t *dev, + struct audio_microphone_characteristic_t *mic_array, + size_t *mic_count); + + +typedef struct audio_vbuffer { + pthread_mutex_t lock; + uint8_t * data; + size_t frame_size; + size_t frame_count; + size_t head; + size_t tail; + size_t live; +} audio_vbuffer_t; + +static int audio_vbuffer_init (audio_vbuffer_t * audio_vbuffer, size_t frame_count, + size_t frame_size) { + if (!audio_vbuffer) { + return -EINVAL; + } + audio_vbuffer->frame_size = frame_size; + audio_vbuffer->frame_count = frame_count; + size_t bytes = frame_count * frame_size; + audio_vbuffer->data = calloc(bytes, 1); + if (!audio_vbuffer->data) { + return -ENOMEM; + } + audio_vbuffer->head = 0; + audio_vbuffer->tail = 0; + audio_vbuffer->live = 0; + pthread_mutex_init (&audio_vbuffer->lock, (const pthread_mutexattr_t *) NULL); + return 0; +} + +static int audio_vbuffer_destroy (audio_vbuffer_t * audio_vbuffer) { + if (!audio_vbuffer) { + return -EINVAL; + } + free(audio_vbuffer->data); + pthread_mutex_destroy(&audio_vbuffer->lock); + return 0; +} + +static int audio_vbuffer_live (audio_vbuffer_t * audio_vbuffer) { + if (!audio_vbuffer) { + return -EINVAL; + } + pthread_mutex_lock (&audio_vbuffer->lock); + int live = audio_vbuffer->live; + pthread_mutex_unlock (&audio_vbuffer->lock); + return live; +} + +#define MIN(a,b) (((a)<(b))?(a):(b)) +static size_t audio_vbuffer_write (audio_vbuffer_t * audio_vbuffer, const void * buffer, size_t frame_count) { + size_t frames_written = 0; + pthread_mutex_lock (&audio_vbuffer->lock); + + while (frame_count != 0) { + int frames = 0; + if (audio_vbuffer->live == 0 || audio_vbuffer->head > audio_vbuffer->tail) { + frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->head); + } else if (audio_vbuffer->head < audio_vbuffer->tail) { + frames = MIN(frame_count, audio_vbuffer->tail - (audio_vbuffer->head)); + } else { + // Full + break; + } + memcpy(&audio_vbuffer->data[audio_vbuffer->head*audio_vbuffer->frame_size], + &((uint8_t*)buffer)[frames_written*audio_vbuffer->frame_size], + frames*audio_vbuffer->frame_size); + audio_vbuffer->live += frames; + frames_written += frames; + frame_count -= frames; + audio_vbuffer->head = (audio_vbuffer->head + frames) % audio_vbuffer->frame_count; + } + + pthread_mutex_unlock (&audio_vbuffer->lock); + return frames_written; +} + +static size_t audio_vbuffer_read (audio_vbuffer_t * audio_vbuffer, void * buffer, size_t frame_count) { + size_t frames_read = 0; + pthread_mutex_lock (&audio_vbuffer->lock); + + while (frame_count != 0) { + int frames = 0; + if (audio_vbuffer->live == audio_vbuffer->frame_count || + audio_vbuffer->tail > audio_vbuffer->head) { + frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->tail); + } else if (audio_vbuffer->tail < audio_vbuffer->head) { + frames = MIN(frame_count, audio_vbuffer->head - audio_vbuffer->tail); + } else { + break; + } + memcpy(&((uint8_t*)buffer)[frames_read*audio_vbuffer->frame_size], + &audio_vbuffer->data[audio_vbuffer->tail*audio_vbuffer->frame_size], + frames*audio_vbuffer->frame_size); + audio_vbuffer->live -= frames; + frames_read += frames; + frame_count -= frames; + audio_vbuffer->tail = (audio_vbuffer->tail + frames) % audio_vbuffer->frame_count; + } + + pthread_mutex_unlock (&audio_vbuffer->lock); + return frames_read; +} + +struct generic_stream_out { + struct audio_stream_out stream; // Constant after init + pthread_mutex_t lock; + struct generic_audio_device *dev; // Constant after init + audio_devices_t device; // Protected by this->lock + struct audio_config req_config; // Constant after init + struct pcm_config pcm_config; // Constant after init + audio_vbuffer_t buffer; // Constant after init + + // Time & Position Keeping + bool standby; // Protected by this->lock + uint64_t underrun_position; // Protected by this->lock + struct timespec underrun_time; // Protected by this->lock + uint64_t last_write_time_us; // Protected by this->lock + uint64_t frames_total_buffered; // Protected by this->lock + uint64_t frames_written; // Protected by this->lock + uint64_t frames_rendered; // Protected by this->lock + + // Worker + pthread_t worker_thread; // Constant after init + pthread_cond_t worker_wake; // Protected by this->lock + bool worker_standby; // Protected by this->lock + bool worker_exit; // Protected by this->lock +}; + +struct generic_stream_in { + struct audio_stream_in stream; // Constant after init + pthread_mutex_t lock; + struct generic_audio_device *dev; // Constant after init + audio_devices_t device; // Protected by this->lock + struct audio_config req_config; // Constant after init + struct pcm *pcm; // Protected by this->lock + struct pcm_config pcm_config; // Constant after init + int16_t *stereo_to_mono_buf; // Protected by this->lock + size_t stereo_to_mono_buf_size; // Protected by this->lock + audio_vbuffer_t buffer; // Protected by this->lock + + // Time & Position Keeping + bool standby; // Protected by this->lock + int64_t standby_position; // Protected by this->lock + struct timespec standby_exit_time;// Protected by this->lock + int64_t standby_frames_read; // Protected by this->lock + + // Worker + pthread_t worker_thread; // Constant after init + pthread_cond_t worker_wake; // Protected by this->lock + bool worker_standby; // Protected by this->lock + bool worker_exit; // Protected by this->lock +}; + +static struct pcm_config pcm_config_out = { + .channels = 2, + .rate = 0, + .period_size = 0, + .period_count = OUT_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, +}; + +static struct pcm_config pcm_config_in = { + .channels = 2, + .rate = 0, + .period_size = 0, + .period_count = IN_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, + .stop_threshold = INT_MAX, +}; + +static pthread_mutex_t adev_init_lock = PTHREAD_MUTEX_INITIALIZER; +static unsigned int audio_device_ref_count = 0; + +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\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", + out_get_sample_rate(stream), + out_get_buffer_size(stream), + out_get_channels(stream), + out_get_format(stream), + out->device, + 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; + char value[32]; + int ret = -ENOSYS; + int success; + long val; + char *end; + + if (kvpairs == NULL || kvpairs[0] == 0) { + return 0; + } + pthread_mutex_lock(&out->lock); + if (out->standby) { + parms = str_parms_create_str(kvpairs); + success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, + value, sizeof(value)); + if (success >= 0) { + errno = 0; + val = strtol(value, &end, 10); + if (errno == 0 && (end != NULL) && (*end == '\0') && ((int)val == val)) { + out->device = (int)val; + ret = 0; + } + } + + // NO op for AUDIO_PARAMETER_DEVICE_CONNECT and AUDIO_PARAMETER_DEVICE_DISCONNECT + success = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_CONNECT, + value, sizeof(value)); + if (success >= 0) { + ret = 0; + } + success = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_DISCONNECT, + value, sizeof(value)); + if (success >= 0) { + ret = 0; + } + + if (ret != 0) { + ALOGD("%s Unsupported parameter %s", __FUNCTION__, kvpairs); + } + + 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 = NULL; + char value[256]; + struct str_parms *reply = str_parms_create(); + int ret; + bool get = false; + + 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); + get = true; + } + + if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) { + value[0] = 0; + strcat(value, "AUDIO_FORMAT_PCM_16_BIT"); + str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value); + get = true; + } + + if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) { + value[0] = 0; + strcat(value, "AUDIO_FORMAT_PCM_16_BIT"); + str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value); + get = true; + } + + if (get) { + str = strdup(str_parms_to_str(reply)); + } + else { + ALOGD("%s Unsupported paramter: %s", __FUNCTION__, 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 void *out_write_worker(void * args) +{ + struct generic_stream_out *out = (struct generic_stream_out *)args; + struct pcm *pcm = NULL; + uint8_t *buffer = NULL; + int buffer_frames; + int buffer_size; + bool restart = false; + bool shutdown = false; + while (true) { + pthread_mutex_lock(&out->lock); + while (out->worker_standby || restart) { + restart = false; + if (pcm) { + pcm_close(pcm); // Frees pcm + 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 not in standby before exiting"); + } + 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 (!pcm) { + pcm = pcm_open(PCM_CARD, PCM_DEVICE, + PCM_OUT | PCM_MONOTONIC, &out->pcm_config); + if (!pcm_is_ready(pcm)) { + ALOGE("pcm_open(out) failed: %s: channels %d format %d rate %d", + pcm_get_error(pcm), + 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 = pcm_frames_to_bytes(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_mutex_unlock(&out->lock); + int ret = pcm_write(pcm, buffer, pcm_frames_to_bytes(pcm, frames)); + if (ret != 0) { + ALOGE("pcm_write failed %s", pcm_get_error(pcm)); + restart = true; + } + } + if (buffer) { + free(buffer); + } + + return NULL; +} + +// Call with in->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; + } +} + + +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; + const size_t frames = bytes / audio_stream_out_frame_size(stream); + + 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); + const uint64_t now_us = (current_time.tv_sec * 1000000000LL + + current_time.tv_nsec) / 1000; + if (out->standby) { + out->standby = false; + out->underrun_time = current_time; + out->frames_rendered = 0; + out->frames_total_buffered = 0; + } + + size_t frames_written = audio_vbuffer_write(&out->buffer, buffer, frames); + pthread_cond_signal(&out->worker_wake); + + /* Implementation just consumes bytes if we start getting backed up */ + out->frames_written += frames; + out->frames_rendered += frames; + out->frames_total_buffered += frames; + + // We simulate the audio device blocking when it's write buffers become + // full. + + // At the beginning or after an underrun, try to fill up the vbuffer. + // This will be throttled by the PlaybackThread + int frames_sleep = out->frames_total_buffered < out->buffer.frame_count ? 0 : frames; + + uint64_t sleep_time_us = frames_sleep * 1000000LL / + out_get_sample_rate(&stream->common); + + // If the write calls are delayed, subtract time off of the sleep to + // compensate + uint64_t time_since_last_write_us = now_us - out->last_write_time_us; + if (time_since_last_write_us < sleep_time_us) { + sleep_time_us -= time_since_last_write_us; + } else { + sleep_time_us = 0; + } + out->last_write_time_us = now_us + sleep_time_us; + + pthread_mutex_unlock(&out->lock); + + if (sleep_time_us > 0) { + usleep(sleep_time_us); + } + + if (frames_written < frames) { + ALOGW("Hardware backing HAL too slow, could only write %zu of %zu frames", 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) + +{ + 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 int check_input_parameters(uint32_t sample_rate, audio_format_t format, + audio_channel_mask_t channel_mask) +{ + return refine_input_parameters(&sample_rate, &format, &channel_mask); +} + +static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format, + audio_channel_mask_t channel_mask) +{ + size_t size; + int channel_count = popcount(channel_mask); + if (check_input_parameters(sample_rate, format, channel_mask) != 0) + return 0; + + size = sample_rate*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; + char value[32]; + int ret = -ENOSYS; + int success; + long val; + char *end; + + if (kvpairs == NULL || kvpairs[0] == 0) { + return 0; + } + pthread_mutex_lock(&in->lock); + if (in->standby) { + parms = str_parms_create_str(kvpairs); + + success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, + value, sizeof(value)); + if (success >= 0) { + errno = 0; + val = strtol(value, &end, 10); + if ((errno == 0) && (end != NULL) && (*end == '\0') && ((int)val == val)) { + in->device = (int)val; + ret = 0; + } + } + // NO op for AUDIO_PARAMETER_DEVICE_CONNECT and AUDIO_PARAMETER_DEVICE_DISCONNECT + success = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_CONNECT, + value, sizeof(value)); + if (success >= 0) { + ret = 0; + } + success = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_DISCONNECT, + value, sizeof(value)); + if (success >= 0) { + ret = 0; + } + + if (ret != 0) { + ALOGD("%s: Unsupported parameter %s", __FUNCTION__, kvpairs); + } + + 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 = NULL; + char value[256]; + struct str_parms *reply = str_parms_create(); + int ret; + bool get = false; + + 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); + get = true; + } + + if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) { + value[0] = 0; + strcat(value, "AUDIO_FORMAT_PCM_16_BIT"); + str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value); + get = true; + } + + if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) { + value[0] = 0; + strcat(value, "AUDIO_FORMAT_PCM_16_BIT"); + str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value); + get = true; + } + + if (get) { + str = strdup(str_parms_to_str(reply)); + } + else { + ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys); + } + + str_parms_destroy(query); + str_parms_destroy(reply); + return str; +} + +static int in_set_gain(struct audio_stream_in *stream, float gain) +{ + // in_set_gain is a no op + 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; +} + +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; + continue; + } + + 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) { + ALOGW("in_read_worker only could write %zu / %zu frames", frames_written, buffer_frames); + } + } + if (buffer) { + free(buffer); + } + return NULL; +} + +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); + bool mic_mute = false; + size_t read_bytes = 0; + + adev_get_mic_mute(&adev->device, &mic_mute); + pthread_mutex_lock(&in->lock); + + if (in->worker_standby) { + in->worker_standby = false; + } + 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 (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 (mic_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_get_active_microphones(const struct audio_stream_in *stream, + struct audio_microphone_characteristic_t *mic_array, + size_t *mic_count) +{ + return adev_get_microphones(NULL, mic_array, mic_count); +} + +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 __unused) +{ + 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*OUT_PERIOD_MS/1000; + + 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; + pthread_create(&out->worker_thread, NULL, out_write_worker, out); + + } + *stream_out = &out->stream; + + +error: + + return ret; +} + +static void adev_close_output_stream(struct audio_hw_device *dev, + struct audio_stream_out *stream) +{ + struct generic_stream_out *out = (struct generic_stream_out *)stream; + 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); + free(stream); +} + +static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) +{ + return 0; +} + +static char * adev_get_parameters(const struct audio_hw_device *dev, + const char *keys) +{ + return strdup(""); +} + +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) +{ + return -ENOSYS; +} + +static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted) +{ + return -ENOSYS; +} + +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; + } + + pthread_mutex_destroy(&in->lock); + audio_vbuffer_destroy(&in->buffer); + free(stream); +} + + +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 __unused, + audio_source_t source __unused) +{ + 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; + in->stream.get_active_microphones = in_get_active_microphones; + + 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*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); + } + + *stream_in = &in->stream; + +error: + return ret; +} + + +static int adev_dump(const audio_hw_device_t *dev, int fd) +{ + return 0; +} + +static int adev_get_microphones(const audio_hw_device_t *dev, + struct audio_microphone_characteristic_t *mic_array, + size_t *mic_count) +{ + if (mic_count == NULL) { + return -ENOSYS; + } + + if (*mic_count == 0) { + *mic_count = 1; + return 0; + } + + if (mic_array == NULL) { + return -ENOSYS; + } + + strncpy(mic_array->device_id, "mic_default", AUDIO_MICROPHONE_ID_MAX_LEN - 1); + mic_array->device = AUDIO_DEVICE_IN_BUILTIN_MIC; + strncpy(mic_array->address, AUDIO_BOTTOM_MICROPHONE_ADDRESS, + AUDIO_DEVICE_MAX_ADDRESS_LEN - 1); + memset(mic_array->channel_mapping, AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED, + sizeof(mic_array->channel_mapping)); + mic_array->location = AUDIO_MICROPHONE_LOCATION_UNKNOWN; + mic_array->group = 0; + mic_array->index_in_the_group = 0; + mic_array->sensitivity = AUDIO_MICROPHONE_SENSITIVITY_UNKNOWN; + mic_array->max_spl = AUDIO_MICROPHONE_SPL_UNKNOWN; + mic_array->min_spl = AUDIO_MICROPHONE_SPL_UNKNOWN; + mic_array->directionality = AUDIO_MICROPHONE_DIRECTIONALITY_UNKNOWN; + mic_array->num_frequency_responses = 0; + mic_array->geometric_location.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN; + mic_array->geometric_location.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN; + mic_array->geometric_location.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN; + mic_array->orientation.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN; + mic_array->orientation.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN; + mic_array->orientation.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN; + + *mic_count = 1; + 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(&adev_init_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); + } + free(adev); + } + +error: + pthread_mutex_unlock(&adev_init_lock); + return ret; +} + +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_once(&sFallbackOnce, fallback_init); + if (sFallback != NULL) { + return sFallback->common.methods->open(&sFallback->common, name, device); + } + + pthread_mutex_lock(&adev_init_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; + } + 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_2_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; // no op + adev->device.get_master_mute = adev_get_master_mute; // no op + 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; + adev->device.get_microphones = adev_get_microphones; + + *device = &adev->device.common; + + adev->mixer = mixer_open(PCM_CARD); + 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; + } + } + + audio_device_ref_count++; + +unlock: + pthread_mutex_unlock(&adev_init_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 audio HW HAL", + .author = "The Android Open Source Project", + .methods = &hal_module_methods, + }, +}; + +/* This function detects whether or not we should be using an alsa audio device + * or fall back to the legacy default_audio driver. + */ +static void +fallback_init(void) +{ + void* module; + + FILE *fptr = fopen ("/proc/asound/pcm", "r"); + if (fptr != NULL) { + // asound/pcm is empty if there are no devices + int c = fgetc(fptr); + fclose(fptr); + if (c != EOF) { + ALOGD("Emulator host-side ALSA audio emulation detected."); + return; + } + } + + ALOGD("Emulator without host-side ALSA audio emulation detected."); +#if __LP64__ + module = dlopen("/vendor/lib64/hw/audio.primary.i9305_legacy.so", + RTLD_LAZY|RTLD_LOCAL); +#else + module = dlopen("/vendor/lib/hw/audio.primary.i9305_legacy.so", + RTLD_LAZY|RTLD_LOCAL); +#endif + if (module != NULL) { + sFallback = (struct audio_module *)(dlsym(module, HAL_MODULE_INFO_SYM_AS_STR)); + if (sFallback == NULL) { + dlclose(module); + } + } + if (sFallback == NULL) { + ALOGE("Could not find legacy fallback module!?"); + } +} |