| /* |
| * 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" |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <log/log.h> |
| #include <cutils/str_parms.h> |
| |
| #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 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; |
| |
| const unsigned in_pos = pipeline_in->position; |
| const unsigned out_pos = pipeline_out->position; |
| const unsigned mix_pos = MIN(out_pos, in_pos); |
| const int16_t* in = pipeline_in->buffer; |
| int16_t* out = pipeline_out->buffer; |
| int16_t* out_end = out + mix_pos; |
| |
| switch (mix_pos % 8) { |
| case 7: goto rem7; |
| case 6: goto rem6; |
| case 5: goto rem5; |
| case 4: goto rem4; |
| case 3: goto rem3; |
| case 2: goto rem2; |
| case 1: goto rem1; |
| case 0: break; |
| } |
| |
| #define ONE_ITER \ |
| mixed = *in + *out; \ |
| clamper = \ |
| (mixed > INT16_MAX) * (mixed - INT16_MAX) + \ |
| (mixed < INT16_MIN) * (mixed - INT16_MIN); \ |
| *out = mixed - clamper; \ |
| ++in; \ |
| ++out |
| |
| while (out < out_end) { |
| int32_t mixed; |
| int32_t clamper; |
| |
| ONE_ITER; |
| rem7: ONE_ITER; |
| rem6: ONE_ITER; |
| rem5: ONE_ITER; |
| rem4: ONE_ITER; |
| rem3: ONE_ITER; |
| rem2: ONE_ITER; |
| rem1: ONE_ITER; |
| } |
| |
| #undef ONE_ITER |
| |
| if (in_pos > out_pos) { |
| memcpy(&pipeline_out->buffer[out_pos], |
| &pipeline_in->buffer[out_pos], |
| sizeof(pipeline_in->buffer[0]) * (in_pos - out_pos)); |
| pipeline_out->position = in_pos; |
| } |
| |
| pipeline_in->position = 0; |
| return true; |
| } |
| |
| static void *mixer_thread_loop(void *context) { |
| ALOGD("%s: __enter__", __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) { |
| pcm_write(ext_pcm->pcm, (void *)ext_pcm->mixer_pipeline.buffer, |
| ext_pcm->mixer_pipeline.position * sizeof(int16_t)); |
| } |
| ext_pcm->mixer_pipeline.position = 0; |
| pthread_mutex_unlock(&ext_pcm->mixer_lock); |
| usleep(MIXER_INTERVAL_MS * 1000); |
| } while (1); |
| } |
| |
| 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 byteCount = MIN(count, |
| (MIXER_BUFFER_SIZE - pipeline->position) * sizeof(int16_t)); |
| unsigned int int16Count = byteCount / sizeof(int16_t); |
| if (int16Count > 0) { |
| memcpy(&pipeline->buffer[pipeline->position], data, byteCount); |
| pipeline->position += int16Count; |
| } |
| 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); |
| } |
| pthread_mutex_unlock(&ext_pcm_init_lock); |
| |
| pthread_mutex_lock(&shared_ext_pcm->lock); |
| shared_ext_pcm->ref_count += 1; |
| pthread_mutex_unlock(&shared_ext_pcm->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) { |
| if (ext_pcm == NULL || ext_pcm->pcm == NULL) { |
| return -EINVAL; |
| } |
| |
| pthread_mutex_lock(&ext_pcm->lock); |
| ext_pcm->ref_count -= 1; |
| pthread_mutex_unlock(&ext_pcm->lock); |
| |
| pthread_mutex_lock(&ext_pcm_init_lock); |
| if (ext_pcm->ref_count <= 0) { |
| 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); |
| pthread_kill(ext_pcm->mixer_thread, SIGINT); |
| free(ext_pcm); |
| shared_ext_pcm = NULL; |
| } |
| pthread_mutex_unlock(&ext_pcm_init_lock); |
| 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); |
| } |