/* * Copyright (C) 2018 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_caremu" // #define LOG_NDEBUG 0 #include #include #include #include #include #include #include #include "ext_pcm.h" static pthread_mutex_t ext_pcm_init_lock = PTHREAD_MUTEX_INITIALIZER; static struct ext_pcm *shared_ext_pcm = NULL; // Sleep 10ms between each mixing, this interval value is arbitrary chosen #define MIXER_INTERVAL_MS 10 #define MS_TO_US 1000 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) /* 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 bool mixer_thread_mix(__unused void *key, void *value, void *context) { struct ext_mixer_pipeline *pipeline_out = (struct ext_mixer_pipeline *)context; struct ext_mixer_pipeline *pipeline_in = (struct ext_mixer_pipeline *)value; pipeline_out->position = MAX(pipeline_out->position, pipeline_in->position); for (int i = 0; i < pipeline_out->position; i++) { float mixed = pipeline_out->buffer[i] + pipeline_in->buffer[i]; if (mixed > INT16_MAX) pipeline_out->buffer[i] = INT16_MAX; else if (mixed < INT16_MIN) pipeline_out->buffer[i] = INT16_MIN; else pipeline_out->buffer[i] = (int16_t)mixed; } memset(pipeline_in, 0, sizeof(struct ext_mixer_pipeline)); return true; } static void *mixer_thread_loop(void *context) { pthread_setname_np(pthread_self(), "car_mixer_loop"); ALOGD("%s: starting mixer loop", __func__); struct ext_pcm *ext_pcm = (struct ext_pcm *)context; do { pthread_mutex_lock(&ext_pcm->mixer_lock); ext_pcm->mixer_pipeline.position = 0; // Combine the output from every pipeline into one output buffer hashmapForEach(ext_pcm->mixer_pipeline_map, mixer_thread_mix, &ext_pcm->mixer_pipeline); if (ext_pcm->mixer_pipeline.position > 0) { int ret = pcm_write(ext_pcm->pcm, (void *)ext_pcm->mixer_pipeline.buffer, ext_pcm->mixer_pipeline.position * sizeof(int16_t)); if (ret != 0) { ALOGE("%s error[%d] writing data to pcm", __func__, ret); } } memset(&ext_pcm->mixer_pipeline, 0, sizeof(struct ext_mixer_pipeline)); pthread_cond_broadcast(&ext_pcm->mixer_wake); pthread_mutex_unlock(&ext_pcm->mixer_lock); pthread_mutex_lock(&ext_pcm_init_lock); bool keep_running = ext_pcm->run_mixer; pthread_mutex_unlock(&ext_pcm_init_lock); if (!keep_running) { break; } usleep(MIXER_INTERVAL_MS * MS_TO_US); } while (1); ALOGD("%s: exiting mixer loop", __func__); return NULL; } static int mixer_pipeline_write(struct ext_pcm *ext_pcm, const char *bus_address, const void *data, unsigned int count) { pthread_mutex_lock(&ext_pcm->mixer_lock); struct ext_mixer_pipeline *pipeline = hashmapGet( ext_pcm->mixer_pipeline_map, bus_address); if (!pipeline) { pipeline = calloc(1, sizeof(struct ext_mixer_pipeline)); hashmapPut(ext_pcm->mixer_pipeline_map, bus_address, pipeline); } unsigned int byteWritten = 0; bool write_incomplete = true; do { const unsigned int byteCount = MIN(count - byteWritten, (MIXER_BUFFER_SIZE - pipeline->position) * sizeof(int16_t)); const unsigned int int16Count = byteCount / sizeof(int16_t); if (int16Count > 0) { memcpy(&pipeline->buffer[pipeline->position], (const char*)data + byteWritten, byteCount); pipeline->position += int16Count; } byteWritten += byteCount; write_incomplete = byteWritten < count; if (write_incomplete) { // wait for mixer thread to consume the pipeline buffer pthread_cond_wait(&ext_pcm->mixer_wake, &ext_pcm->mixer_lock); } } while (write_incomplete); pthread_mutex_unlock(&ext_pcm->mixer_lock); return 0; } struct ext_pcm *ext_pcm_open(unsigned int card, unsigned int device, unsigned int flags, struct pcm_config *config) { pthread_mutex_lock(&ext_pcm_init_lock); if (shared_ext_pcm == NULL) { shared_ext_pcm = calloc(1, sizeof(struct ext_pcm)); pthread_mutex_init(&shared_ext_pcm->lock, (const pthread_mutexattr_t *) NULL); shared_ext_pcm->pcm = pcm_open(card, device, flags, config); pthread_mutex_init(&shared_ext_pcm->mixer_lock, (const pthread_mutexattr_t *)NULL); pthread_create(&shared_ext_pcm->mixer_thread, (const pthread_attr_t *)NULL, mixer_thread_loop, shared_ext_pcm); shared_ext_pcm->mixer_pipeline_map = hashmapCreate(8, str_hash_fn, str_eq); shared_ext_pcm->run_mixer = true; } shared_ext_pcm->ref_count += 1; pthread_mutex_unlock(&ext_pcm_init_lock); return shared_ext_pcm; } static bool mixer_free_pipeline(__unused void *key, void *value, void *context) { struct ext_mixer_pipeline *pipeline = (struct ext_mixer_pipeline *)value; free(pipeline); return true; } int ext_pcm_close(struct ext_pcm *ext_pcm) { ALOGD("%s closing pcm", __func__); if (ext_pcm == NULL || ext_pcm->pcm == NULL) { return -EINVAL; } pthread_mutex_lock(&ext_pcm_init_lock); int count = ext_pcm->ref_count -= 1; if (count <= 0) { ext_pcm->run_mixer = false; // On pcm open new shared_ext_pcm will be created shared_ext_pcm = NULL; pthread_mutex_unlock(&ext_pcm_init_lock); void* ret_val = NULL; int ret = pthread_join(ext_pcm->mixer_thread, &ret_val); if (ret != 0) { ALOGE("%s error[%d] when joining thread", __func__, ret); // Try killing if timeout failed pthread_kill(ext_pcm->mixer_thread, SIGINT); } pthread_mutex_lock(&ext_pcm_init_lock); pthread_mutex_destroy(&ext_pcm->lock); pcm_close(ext_pcm->pcm); pthread_mutex_destroy(&ext_pcm->mixer_lock); hashmapForEach(ext_pcm->mixer_pipeline_map, mixer_free_pipeline, (void *)NULL); hashmapFree(ext_pcm->mixer_pipeline_map); free(ext_pcm); } pthread_mutex_unlock(&ext_pcm_init_lock); ALOGD("%s finished closing pcm", __func__); return 0; } int ext_pcm_is_ready(struct ext_pcm *ext_pcm) { if (ext_pcm == NULL || ext_pcm->pcm == NULL) { return 0; } return pcm_is_ready(ext_pcm->pcm); } int ext_pcm_write(struct ext_pcm *ext_pcm, const char *address, const void *data, unsigned int count) { if (ext_pcm == NULL || ext_pcm->pcm == NULL) { return -EINVAL; } return mixer_pipeline_write(ext_pcm, address, data, count); } const char *ext_pcm_get_error(struct ext_pcm *ext_pcm) { if (ext_pcm == NULL || ext_pcm->pcm == NULL) { return NULL; } return pcm_get_error(ext_pcm->pcm); } unsigned int ext_pcm_frames_to_bytes(struct ext_pcm *ext_pcm, unsigned int frames) { if (ext_pcm == NULL || ext_pcm->pcm == NULL) { return -EINVAL; } return pcm_frames_to_bytes(ext_pcm->pcm, frames); }