From 4d6c6cc6e65fdfb2f6ed4a66c062bbf7b1706f0b Mon Sep 17 00:00:00 2001 From: Paul Kocialkowski Date: Fri, 12 Oct 2012 21:23:29 +0200 Subject: Mixer: Initial commit, implements controls handling Signed-off-by: Paul Kocialkowski --- Android.mk | 5 +- audio_hw.c | 31 ++- audio_hw.h | 2 + audio_in.c | 2 +- audio_out.c | 2 +- mixer.c | 892 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mixer.h | 108 ++++++++ 7 files changed, 1036 insertions(+), 6 deletions(-) create mode 100644 mixer.c create mode 100644 mixer.h diff --git a/Android.mk b/Android.mk index 2158a48..59f0605 100644 --- a/Android.mk +++ b/Android.mk @@ -22,10 +22,12 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ audio_hw.c \ audio_out.c \ - audio_in.c + audio_in.c \ + mixer.c LOCAL_C_INCLUDES += \ external/tinyalsa/include \ + external/expat/lib \ system/media/audio_utils/include \ system/media/audio_effects/include \ hardware/tinyalsa-audio/include @@ -34,6 +36,7 @@ LOCAL_SHARED_LIBRARIES := \ libc \ libcutils \ libutils \ + libexpat \ libtinyalsa \ libaudioutils \ libdl diff --git a/audio_hw.c b/audio_hw.c index 30d8931..685bc4a 100644 --- a/audio_hw.c +++ b/audio_hw.c @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#define LOG_TAG "audio_hw" +#define LOG_TAG "TinyALSA-Audio Hardware" #include #include @@ -28,6 +28,7 @@ #include #include "audio_hw.h" +#include "mixer.h" /* * Functions @@ -141,10 +142,20 @@ static int audio_hw_dump(const audio_hw_device_t *device, int fd) int audio_hw_close(hw_device_t *device) { + struct tinyalsa_audio_device *tinyalsa_audio_device; + LOGD("%s(%p)", __func__, device); - if(device != NULL) + if(device != NULL) { + tinyalsa_audio_device = (struct tinyalsa_audio_device *) device; + + if(tinyalsa_audio_device != NULL && tinyalsa_audio_device->mixer != NULL) { + tinyalsa_mixer_close(tinyalsa_audio_device->mixer); + tinyalsa_audio_device->mixer = NULL; + } + free(device); + } return 0; } @@ -152,8 +163,10 @@ int audio_hw_close(hw_device_t *device) int audio_hw_open(const hw_module_t *module, const char *name, hw_device_t **device) { - struct tinyalsa_audio_device *tinyalsa_audio_device; + struct tinyalsa_audio_device *tinyalsa_audio_device = NULL; + struct tinyalsa_mixer *tinyalsa_mixer = NULL; struct audio_hw_device *dev; + int rc; LOGD("%s(%p, %s, %p)", __func__, module, name, device); @@ -190,6 +203,18 @@ int audio_hw_open(const hw_module_t *module, const char *name, dev->dump = audio_hw_dump; + tinyalsa_mixer_open(&tinyalsa_mixer, TINYALSA_MIXER_CONFIG_FILE); + if(tinyalsa_mixer == NULL) { + LOGE("Failed to open mixer!"); + } else { + rc = tinyalsa_mixer_set_route(tinyalsa_mixer, AUDIO_DEVICE_OUT_DEFAULT, AUDIO_MODE_NORMAL); + if(rc < 0) { + LOGE("Failed to set default mixer route"); + } + } + + tinyalsa_audio_device->mixer = tinyalsa_mixer; + *device = &(dev->common); return 0; diff --git a/audio_hw.h b/audio_hw.h index 2419883..e04af95 100644 --- a/audio_hw.h +++ b/audio_hw.h @@ -37,6 +37,8 @@ struct tinyalsa_audio_device { struct tinyalsa_audio_stream_out *stream_out; struct tinyalsa_audio_stream_in *stream_in; + + struct tinyalsa_mixer *mixer; }; int audio_hw_open_output_stream(struct audio_hw_device *dev, diff --git a/audio_in.c b/audio_in.c index 4fd1527..e416de8 100644 --- a/audio_in.c +++ b/audio_in.c @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#define LOG_TAG "audio_in" +#define LOG_TAG "TinyALSA-Audio Input" #include #include diff --git a/audio_out.c b/audio_out.c index 2b3c854..2ea2db8 100644 --- a/audio_out.c +++ b/audio_out.c @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#define LOG_TAG "audio_out" +#define LOG_TAG "TinyALSA-Audio Output" #include #include diff --git a/mixer.c b/mixer.c new file mode 100644 index 0000000..065b939 --- /dev/null +++ b/mixer.c @@ -0,0 +1,892 @@ +/* + * Copyright (C) 2012 Paul Kocialkowski + * + * This is based on Galaxy Nexus audio.primary.tuna implementation: + * Copyright 2011, The Android Open-Source Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define LOG_TAG "TinyALSA-Audio Mixer" + +#include +#include +#include +#include + +#include + +#include + +#define EFFECT_UUID_NULL EFFECT_UUID_NULL_MIXER +#define EFFECT_UUID_NULL_STR EFFECT_UUID_NULL_STR_MIXER +#include "audio_hw.h" +#include "mixer.h" + +/* + * List + */ + +struct list_head *list_head_alloc(void) +{ + struct list_head *list = (struct list_head *) + calloc(1, sizeof(struct list_head)); + + return list; +} + +void list_head_free(struct list_head *list) +{ + struct list_head *list_prev; + + while(list != NULL) { + list_prev = list; + list = list->next; + + free(list_prev); + } +} + +/* + * Mixer data + */ + +struct tinyalsa_mixer_data *tinyalsa_mixer_data_alloc(void) +{ + struct tinyalsa_mixer_data *mixer_data = (struct tinyalsa_mixer_data *) + calloc(1, sizeof(struct tinyalsa_mixer_data)); + + return mixer_data; +} + +void tinyalsa_mixer_data_free(struct tinyalsa_mixer_data *mixer_data) +{ + if(mixer_data == NULL) + return; + + if(mixer_data->name != NULL) { + free(mixer_data->name); + mixer_data->name = NULL; + } + + if(mixer_data->value != NULL) { + free(mixer_data->value); + mixer_data->value = NULL; + } + + if(mixer_data->attr != NULL) { + free(mixer_data->attr); + mixer_data->attr = NULL; + } + + free(mixer_data); +} + +/* + * Mixer config + */ + +void tinyalsa_mixer_config_start(void *data, const XML_Char *elem, + const XML_Char **attr) +{ + struct tinyalsa_mixer_config_data *config_data; + struct tinyalsa_mixer_data *mixer_data; + struct list_head *list; + int i; + + if(data == NULL || elem == NULL || attr == NULL) + return; + + config_data = (struct tinyalsa_mixer_config_data *) data; + + if(strcmp(elem, "tinyalsa-audio") == 0) { + for(i=0 ; attr[i] != NULL && attr[i+1] != NULL ; i++) { + if(strcmp(attr[i], "device") == 0) { + i++; + LOGD("Parsing config for device: %s", attr[i]); + } + } + } else if(strcmp(elem, "output") == 0) { + config_data->direction = AUDIO_DEVICE_OUT_ALL; + + for(i=0 ; attr[i] != NULL && attr[i+1] ; i++) { + if(strcmp(attr[i], "card") == 0) { + i++; + config_data->io_props.card = atoi(attr[i]); + } else if(strcmp(attr[i], "device") == 0) { + i++; + config_data->io_props.device = atoi(attr[i]); + } else if(strcmp(attr[i], "rate") == 0) { + i++; + config_data->io_props.rate = atoi(attr[i]); + } else if(strcmp(attr[i], "channels") == 0) { + i++; + switch(atoi(attr[i])) { + case 1: + config_data->io_props.channels = AUDIO_CHANNEL_OUT_MONO; + break; + case 2: + config_data->io_props.channels = AUDIO_CHANNEL_OUT_STEREO; + break; + case 4: + config_data->io_props.channels = AUDIO_CHANNEL_OUT_SURROUND; + break; + case 6: + config_data->io_props.channels = AUDIO_CHANNEL_OUT_5POINT1; + break; + case 8: + config_data->io_props.channels = AUDIO_CHANNEL_OUT_7POINT1; + break; + default: + LOGE("Unknown channel attr: %s", attr[i]); + break; + } + } else if(strcmp(attr[i], "format") == 0) { + i++; + if(strcmp(attr[i], "PCM_8") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_8_BIT; + } else if(strcmp(attr[i], "PCM_16") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_16_BIT; + } else if(strcmp(attr[i], "PCM_32") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_32_BIT; + } else if(strcmp(attr[i], "PCM_8_24") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_8_24_BIT; + } else { + LOGE("Unknown format attr: %s", attr[i]); + } + } else if(strcmp(attr[i], "period_size") == 0) { + i++; + config_data->io_props.period_size = atoi(attr[i]); + } else if(strcmp(attr[i], "period_count") == 0) { + i++; + config_data->io_props.period_count = atoi(attr[i]); + } else { + LOGE("Unknown output attr: %s", attr[i]); + } + } + } else if(strcmp(elem, "input") == 0) { + config_data->direction = AUDIO_DEVICE_IN_ALL; + + for(i=0 ; attr[i] != NULL && attr[i+1] ; i++) { + if(strcmp(attr[i], "card") == 0) { + i++; + config_data->io_props.card = atoi(attr[i]); + } else if(strcmp(attr[i], "device") == 0) { + i++; + config_data->io_props.device = atoi(attr[i]); + } else if(strcmp(attr[i], "rate") == 0) { + i++; + config_data->io_props.rate = atoi(attr[i]); + } else if(strcmp(attr[i], "channels") == 0) { + i++; + switch(atoi(attr[i])) { + case 1: + config_data->io_props.channels = AUDIO_CHANNEL_IN_MONO; + break; + case 2: + config_data->io_props.channels = AUDIO_CHANNEL_IN_STEREO; + break; + default: + LOGE("Unknown channel attr: %s", attr[i]); + break; + } + } else if(strcmp(attr[i], "format") == 0) { + i++; + if(strcmp(attr[i], "PCM_8") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_8_BIT; + } else if(strcmp(attr[i], "PCM_16") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_16_BIT; + } else if(strcmp(attr[i], "PCM_32") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_32_BIT; + } else if(strcmp(attr[i], "PCM_8_24") == 0) { + config_data->io_props.format = AUDIO_FORMAT_PCM_8_24_BIT; + } else { + LOGE("Unknown format attr: %s", attr[i]); + } + } else if(strcmp(attr[i], "period_size") == 0) { + i++; + config_data->io_props.period_size = atoi(attr[i]); + } else if(strcmp(attr[i], "period_count") == 0) { + i++; + config_data->io_props.period_count = atoi(attr[i]); + } else { + LOGE("Unknown input attr: %s", attr[i]); + } + } + } else if(strcmp(elem, "mode") == 0) { + for(i=0 ; attr[i] != NULL && attr[i+1] != NULL ; i++) { + if(strcmp(attr[i], "type") == 0) { + i++; + if(strcmp(attr[i], "normal") == 0) { + config_data->device_props.mode = AUDIO_MODE_NORMAL; + } else if(strcmp(attr[i], "ringtone") == 0) { + config_data->device_props.mode = AUDIO_MODE_RINGTONE; + } else if(strcmp(attr[i], "in-call") == 0) { + config_data->device_props.mode = AUDIO_MODE_IN_CALL; + } else if(strcmp(attr[i], "in-communication") == 0) { + config_data->device_props.mode = AUDIO_MODE_IN_COMMUNICATION; + } else { + LOGE("Unknown mode attr: %s", attr[i]); + } + } + } + } else if(strcmp(elem, "device") == 0) { + for(i=0 ; attr[i] != NULL && attr[i+1] != NULL ; i++) { + if(strcmp(attr[i], "type") == 0) { + i++; + if((config_data->direction & (~AUDIO_DEVICE_OUT_ALL)) == 0) { + if(strcmp(attr[i], "default") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_DEFAULT; + } else if(strcmp(attr[i], "earpiece") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_EARPIECE; + } else if(strcmp(attr[i], "speaker") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_SPEAKER; + } else if(strcmp(attr[i], "wired-headset") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_WIRED_HEADSET; + } else if(strcmp(attr[i], "wired-headphone") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_WIRED_HEADPHONE; + } else if(strcmp(attr[i], "bt-sco") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_BLUETOOTH_SCO; + } else if(strcmp(attr[i], "bt-sco-headset") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET; + } else if(strcmp(attr[i], "bt-sco-carkit") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT; + } else if(strcmp(attr[i], "bt-a2dp") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP; + } else if(strcmp(attr[i], "bt-a2dp-headphones") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; + } else if(strcmp(attr[i], "bt-a2dp-speaker") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; + } else if(strcmp(attr[i], "aux-digital") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_AUX_DIGITAL; + } else if(strcmp(attr[i], "analog-dock-headset") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET; + } else if(strcmp(attr[i], "digital-dock-headset") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET; + } else if(strcmp(attr[i], "fm") == 0) { + config_data->device_props.type = AUDIO_DEVICE_OUT_FM; + } else { + LOGE("Unknown device attr: %s", attr[i]); + } + } else if((config_data->direction & (~AUDIO_DEVICE_IN_ALL)) == 0) { + if(strcmp(attr[i], "communication") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_COMMUNICATION; + } else if(strcmp(attr[i], "ambient") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_AMBIENT; + } else if(strcmp(attr[i], "builtin-mic") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_BUILTIN_MIC; + } else if(strcmp(attr[i], "bt-sco-headset") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET; + } else if(strcmp(attr[i], "wired-headset") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_WIRED_HEADSET; + } else if(strcmp(attr[i], "aux-digital") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_AUX_DIGITAL; + } else if(strcmp(attr[i], "voice-call") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_VOICE_CALL; + } else if(strcmp(attr[i], "back-mic") == 0) { + config_data->device_props.type = AUDIO_DEVICE_IN_BACK_MIC; + } else { + LOGE("Unknown device attr: %s", attr[i]); + } + } + } else if(strcmp(attr[i], "mode") == 0) { + i++; + if(strcmp(attr[i], "normal") == 0) { + config_data->device_props.mode = AUDIO_MODE_NORMAL; + } else if(strcmp(attr[i], "ringtone") == 0) { + config_data->device_props.mode = AUDIO_MODE_RINGTONE; + } else if(strcmp(attr[i], "in-call") == 0) { + config_data->device_props.mode = AUDIO_MODE_IN_CALL; + } else if(strcmp(attr[i], "in-communication") == 0) { + config_data->device_props.mode = AUDIO_MODE_IN_COMMUNICATION; + } else { + LOGE("Unknown mode attr: %s", attr[i]); + } + } else { + LOGE("Unknown device attr: %s", attr[i]); + } + + if(config_data->device_props.type != 0) { + config_data->device = calloc(1, sizeof(struct tinyalsa_mixer_device)); + memcpy(&config_data->device->props, &config_data->device_props, sizeof(config_data->device_props)); + } else { + LOGE("Missing attrs for elem: %s", elem); + } + } + } else if(strcmp(elem, "path") == 0) { + for(i=0 ; attr[i] != NULL && attr[i+1] != NULL ; i++) { + if(strcmp(attr[i], "type") == 0) { + i++; + if(strcmp(attr[i], "enable") == 0) { + config_data->list_start = &config_data->device->enable; + } else if(strcmp(attr[i], "disable") == 0) { + config_data->list_start = &config_data->device->disable; + } else { + LOGE("Unknown path attr: %s", attr[i]); + } + } else { + LOGE("Unknown path attr: %s", attr[i]); + } + } + } else if(strcmp(elem, "ctrl") == 0) { + if(config_data->device != NULL && config_data->list_start != NULL) { + list = list_head_alloc(); + mixer_data = tinyalsa_mixer_data_alloc(); + + mixer_data->type = MIXER_DATA_TYPE_CTRL; + list->data = (void *) mixer_data; + } else { + LOGE("Missing device/path for elem: %s", elem); + return; + } + + for(i=0 ; attr[i] != NULL && attr[i+1] != NULL ; i++) { + if(strcmp(attr[i], "name") == 0) { + i++; + mixer_data->name = strdup((char *) attr[i]); + } else if(strcmp(attr[i], "attr") == 0) { + i++; + mixer_data->attr = strdup((char *) attr[i]); + } else if(strcmp(attr[i], "value") == 0) { + i++; + mixer_data->value = strdup((char *) attr[i]); + } else { + LOGE("Unknown ctrl attr: %s", attr[i]); + } + } + + if(mixer_data->name != NULL && mixer_data->value != NULL) { + if(*config_data->list_start == NULL) { + *config_data->list_start = list; + } else { + config_data->list->next = list; + list->prev = config_data->list; + } + + config_data->list = list; + } else { + tinyalsa_mixer_data_free(mixer_data); + list_head_free(list); + } + } +} + +void tinyalsa_mixer_config_end(void *data, const XML_Char *elem) +{ + struct tinyalsa_mixer_config_data *config_data; + struct list_head *list_prev; + struct list_head *list; + + if(data == NULL || elem == NULL) + return; + + config_data = (struct tinyalsa_mixer_config_data *) data; + + if(strcmp(elem, "output") == 0) { + memcpy(&config_data->mixer->output.props, &config_data->io_props, sizeof(config_data->io_props)); + memset(&config_data->io_props, 0, sizeof(config_data->io_props)); + config_data->direction = 0; + } else if(strcmp(elem, "input") == 0) { + memcpy(&config_data->mixer->input.props, &config_data->io_props, sizeof(config_data->io_props)); + memset(&config_data->io_props, 0, sizeof(config_data->io_props)); + config_data->direction = 0; + } else if(strcmp(elem, "device") == 0) { + // direction == 0 will fallback to out + if((config_data->direction & (~AUDIO_DEVICE_OUT_ALL)) == 0) { + list = list_head_alloc(); + list->data = (void *) config_data->device; + + if(config_data->mixer->output.devices == NULL) { + config_data->mixer->output.devices = list; + } else { + list_prev = config_data->mixer->output.devices; + + while(list_prev->next != NULL) + list_prev = list_prev->next; + + list_prev->next = list; + list->prev = list_prev; + } + } else if((config_data->direction & (~AUDIO_DEVICE_IN_ALL)) == 0) { + list = list_head_alloc(); + list->data = (void *) config_data->device; + + if(config_data->mixer->input.devices == NULL) { + config_data->mixer->input.devices = list; + } else { + list_prev = config_data->mixer->input.devices; + + while(list_prev->next != NULL) + list_prev = list_prev->next; + + list_prev->next = list; + list->prev = list_prev; + } + } + + config_data->device = NULL; + config_data->list = NULL; + } else if(strcmp(elem, "path") == 0) { + config_data->list_start = 0; + config_data->list = 0; + } +} + +int tinyalsa_mixer_config_parse(struct tinyalsa_mixer *mixer, char *config_file) +{ + struct tinyalsa_mixer_config_data config_data; + char buf[80]; + XML_Parser p; + FILE *f; + + int eof = 0; + int len = 0; + + if(mixer == NULL || config_file == NULL) + return -1; + + f = fopen(config_file, "r"); + if(!f) { + LOGE("Failed to open tinyalsa-audio config file!"); + return -1; + } + + p = XML_ParserCreate(NULL); + if(!p) { + LOGE("Failed to create XML parser!"); + goto error_file; + } + + memset(&config_data, 0, sizeof(config_data)); + config_data.mixer = mixer; + + XML_SetUserData(p, &config_data); + XML_SetElementHandler(p, tinyalsa_mixer_config_start, tinyalsa_mixer_config_end); + + while(!eof) { + len = fread(buf, 1, sizeof(buf), f); + if(ferror(f)) { + LOGE("Failed to read config file!"); + goto error_xml_parser; + } + + eof = feof(f); + + if(XML_Parse(p, buf, len, eof) == XML_STATUS_ERROR) { + LOGE("Failed to parse line %d: %s", + (int) XML_GetCurrentLineNumber(p), + (char *) XML_ErrorString(XML_GetErrorCode(p))); + goto error_xml_parser; + } + } + + XML_ParserFree(p); + fclose(f); + + return 0; + +error_xml_parser: + XML_ParserFree(p); + +error_file: + fclose(f); + + return -1; +} + +/* + * Interface + */ + +int tinyalsa_mixer_set_route_ctrl(struct tinyalsa_mixer *mixer, + struct tinyalsa_mixer_data *mixer_data) +{ + struct mixer_ctl *ctl; + int value = 0; + int type; + int rc; + int i; + + if(mixer_data->type != MIXER_DATA_TYPE_CTRL) + return -1; + + ctl = mixer_get_ctl_by_name(mixer->mixer, mixer_data->name); + type = mixer_ctl_get_type(ctl); + + LOGD("Setting %s to %s", mixer_data->name, mixer_data->value); + + switch(type) { + case MIXER_CTL_TYPE_BOOL: + value = strcmp(mixer_data->value, "on") == 0 ? + 1 : 0; + break; + case MIXER_CTL_TYPE_INT: + value = atoi(mixer_data->value); + break; + case MIXER_CTL_TYPE_BYTE: + value = atoi(mixer_data->value) & 0xff; + break; + } + + if(type == MIXER_CTL_TYPE_BOOL || type == MIXER_CTL_TYPE_INT || + type == MIXER_CTL_TYPE_BYTE) { + for(i=0 ; i < mixer_ctl_get_num_values(ctl) ; i++) { + rc = mixer_ctl_set_value(ctl, i, value); + if(rc < 0) + return -1; + } + } else if(type == MIXER_CTL_TYPE_ENUM || type == MIXER_CTL_TYPE_UNKNOWN) { + rc = mixer_ctl_set_enum_by_string(ctl, mixer_data->value); + if(rc < 0) + return -1; + } + + return 0; +} + +int tinyalsa_mixer_set_route_list(struct tinyalsa_mixer *mixer, struct list_head *list) +{ + struct tinyalsa_mixer_data *mixer_data = NULL; + int rc; + + if(mixer == NULL || mixer->mixer == NULL) + return -1; + + while(list != NULL) { + mixer_data = (struct tinyalsa_mixer_data *) list->data; + + if(mixer_data->type == MIXER_DATA_TYPE_CTRL) { + if(mixer_data->attr != NULL && + strcmp(mixer_data->attr, "voice-volume") == 0) { + LOGD("Skipping voice volume control"); + } else { + rc = tinyalsa_mixer_set_route_ctrl(mixer, mixer_data); + if(rc < 0) { + LOGE("Unable to set control!"); + return -1; + } + } + } + + if(list->next != NULL) + list = list->next; + else + break; + } + + return 0; +} + +int tinyalsa_mixer_set_route(struct tinyalsa_mixer *mixer, audio_devices_t device, audio_mode_t mode) +{ + struct tinyalsa_mixer_io *mixer_io = NULL; + struct tinyalsa_mixer_device *mixer_device = NULL; + struct list_head *list = NULL; + int rc; + + LOGD("%s(%x, %x)", __func__, device, mode); + + if(mixer == NULL) + return -1; + + if(audio_is_output_device(device)) { + mixer_io = &mixer->output; + } else if(audio_is_input_device(device)) { + mixer_io = &mixer->input; + } else { + LOGE("Invalid device: 0x%x", device); + return -1; + } + + mixer->mixer = mixer_open(mixer_io->props.card); + if(mixer->mixer == NULL) { + LOGE("Unable to open mixer for card: %d", mixer_io->props.card); + return -1; + } + + list = mixer_io->devices; + + while(list != NULL) { + mixer_device = (struct tinyalsa_mixer_device *) list->data; + if(mixer_device != NULL && mixer_device->props.type == device && + mixer_device->props.mode == mode) { + break; + } else { + mixer_device = NULL; + } + + list = list->next; + } + + if(mixer_device == NULL) { + LOGE("Unable to find a matching device: 0x%x with mode: 0x%x", + device, mode); + goto error_mixer; + } + + if(mixer_io->device_current != NULL) { + rc = tinyalsa_mixer_set_route_list(mixer, mixer_io->device_current->disable); + if(rc < 0) { + LOGE("Unable to disable current device controls"); + goto error_mixer; + } + } + + rc = tinyalsa_mixer_set_route_list(mixer, mixer_device->enable); + if(rc < 0) { + LOGE("Unable to enable device controls"); + goto error_mixer; + } + + mixer_io->device_current = mixer_device; + mixer_close(mixer->mixer); + mixer->mixer = NULL; + + return 0; + +error_mixer: + mixer_close(mixer->mixer); + mixer->mixer = NULL; + + return -1; +} + +int tinyalsa_mixer_set_device_attr(struct tinyalsa_mixer *mixer, + audio_devices_t device, int enable, struct tinyalsa_mixer_io *mixer_io, + char *attr) +{ + struct tinyalsa_mixer_device *mixer_device = NULL; + struct tinyalsa_mixer_data *mixer_data = NULL; + struct list_head *list = NULL; + int rc; + + if(attr == NULL || mixer_io == NULL) + return -1; + + list = mixer_io->devices; + + mixer->mixer = mixer_open(mixer_io->props.card); + if(mixer->mixer == NULL) { + LOGE("Unable to open mixer for card: %d", mixer_io->props.card); + return -1; + } + + while(list != NULL) { + mixer_device = (struct tinyalsa_mixer_device *) list->data; + if(mixer_device != NULL && mixer_device->props.type == device) { + break; + } else { + mixer_device = NULL; + } + + list = list->next; + } + + if(mixer_device == NULL) { + LOGE("Unable to find a matching device: 0x%x", device); + goto error_mixer; + } + + if(enable) + list = mixer_device->enable; + else + list = mixer_device->disable; + + while(list != NULL) { + mixer_data = (struct tinyalsa_mixer_data *) list->data; + + if(mixer_data->type == MIXER_DATA_TYPE_CTRL) { + if(mixer_data->attr != NULL && + strcmp(mixer_data->attr, attr) == 0) { + rc = tinyalsa_mixer_set_route_ctrl(mixer, mixer_data); + if(rc < 0) { + LOGE("Unable to set control!"); + goto error_mixer; + } + + break; + } + } + + if(list->next != NULL) + list = list->next; + else + break; + } + + mixer_close(mixer->mixer); + mixer->mixer = NULL; + + return 0; + +error_mixer: + mixer_close(mixer->mixer); + mixer->mixer = NULL; + + return -1; +} + +int tinyalsa_mixer_set_output_volume(struct tinyalsa_mixer *mixer, audio_devices_t device) +{ + LOGD("%s(%p, %x)", __func__, mixer, device); + + return tinyalsa_mixer_set_device_attr(mixer, device, 1, + &mixer->output, "output-volume"); +} + +int tinyalsa_mixer_set_master_volume(struct tinyalsa_mixer *mixer, audio_devices_t device) +{ + LOGD("%s(%p, %x)", __func__, mixer, device); + + return tinyalsa_mixer_set_device_attr(mixer, device, 1, + &mixer->output, "master-volume"); +} + +int tinyalsa_mixer_set_mic_mute(struct tinyalsa_mixer *mixer, audio_devices_t device, int mute) +{ + LOGD("%s(%p, %x, %d)", __func__, mixer, device, mute); + + return tinyalsa_mixer_set_device_attr(mixer, device, mute ? 0 : 1, + &mixer->input, "mic-mute"); +} + +int tinyalsa_mixer_set_input_gain(struct tinyalsa_mixer *mixer, audio_devices_t device) +{ + LOGD("%s(%p, %x)", __func__, mixer, device); + + return tinyalsa_mixer_set_device_attr(mixer, device, 1, + &mixer->input, "input-gain"); +} + +int tinyalsa_mixer_set_voice_volume(struct tinyalsa_mixer *mixer, audio_devices_t device) +{ + LOGD("%s(%p, %x)", __func__, mixer, device); + + return tinyalsa_mixer_set_device_attr(mixer, device, 1, + &mixer->input, "voice-volume"); +} + +struct tinyalsa_mixer_io_props *tinyalsa_mixer_get_output_props(struct tinyalsa_mixer *mixer) +{ + LOGD("%s(%p)", __func__, mixer); + + return &(mixer->output.props); +} + +struct tinyalsa_mixer_io_props *tinyalsa_mixer_get_input_props(struct tinyalsa_mixer *mixer) +{ + LOGD("%s(%p)", __func__, mixer); + + return &(mixer->input.props); +} + +void tinyalsa_mixer_io_free_devices(struct tinyalsa_mixer_io *mixer_io) +{ + struct tinyalsa_mixer_device *mixer_device; + struct tinyalsa_mixer_data *mixer_data; + struct list_head *list_device; + struct list_head *list_data; + struct list_head *list_prev; + + if(mixer_io == NULL) + return; + + list_device = mixer_io->devices; + + while(list_device != NULL) { + mixer_device = (struct tinyalsa_mixer_device *) list_device->data; + + list_data = mixer_device->enable; + + while(list_data != NULL) { + mixer_data = (struct tinyalsa_mixer_data *) list_data->data; + + tinyalsa_mixer_data_free(mixer_data); + list_data->data = NULL; + + list_prev = list_data; + list_data = list_data->next; + + list_head_free(list_prev); + } + + list_data = mixer_device->disable; + + while(list_data != NULL) { + mixer_data = (struct tinyalsa_mixer_data *) list_data->data; + + tinyalsa_mixer_data_free(mixer_data); + list_data->data = NULL; + + list_prev = list_data; + list_data = list_data->next; + + list_head_free(list_prev); + } + + mixer_device->enable = NULL; + mixer_device->disable = NULL; + + free(mixer_device); + list_device->data = NULL; + + list_prev = list_device; + list_device = list_device->next; + + list_head_free(list_prev); + } +} + +void tinyalsa_mixer_close(struct tinyalsa_mixer *mixer) +{ + LOGD("%s(%p)", __func__, mixer); + + if(mixer == NULL) + return; + + tinyalsa_mixer_io_free_devices(&mixer->output); + tinyalsa_mixer_io_free_devices(&mixer->input); + + free(mixer); +} + +int tinyalsa_mixer_open(struct tinyalsa_mixer **mixer_p, char *config_file) +{ + struct tinyalsa_mixer *mixer = NULL; + int rc; + + LOGD("%s(%p, %s)", __func__, mixer_p, config_file); + + if(mixer_p == NULL || config_file == NULL) + return -1; + + mixer = calloc(1, sizeof(struct tinyalsa_mixer)); + + rc = tinyalsa_mixer_config_parse(mixer, config_file); + if(rc < 0) { + LOGE("Unable to parse mixer config!"); + goto error_mixer; + } + + *mixer_p = mixer; + + return 0; + +error_mixer: + *mixer_p = NULL; + + free(mixer); + + return -1; +} diff --git a/mixer.h b/mixer.h new file mode 100644 index 0000000..5812b76 --- /dev/null +++ b/mixer.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 Paul Kocialkowski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TINYALSA_AUDIO_MIXER_H +#define TINYALSA_AUDIO_MIXER_H + +#include + +#include +#include + +#define TINYALSA_MIXER_CONFIG_FILE "/system/etc/tinyalsa-audio.xml" + +struct list_head { + struct list_head *prev; + struct list_head *next; + + void *data; +}; + +enum tinyalsa_mixer_data_type { + MIXER_DATA_TYPE_CTRL, + MIXER_DATA_TYPE_WRITE, + MIXER_DATA_TYPE_MAX +}; + +struct tinyalsa_mixer_data { + enum tinyalsa_mixer_data_type type; + char *name; + char *value; + char *attr; +}; + +struct tinyalsa_mixer_device_props { + audio_devices_t type; + audio_mode_t mode; +}; + +struct tinyalsa_mixer_device { + struct tinyalsa_mixer_device_props props; + struct list_head *enable; + struct list_head *disable; +}; + +struct tinyalsa_mixer_io_props { + int card; + int device; + + int rate; + audio_channels_t channels; + audio_format_t format; + + int period_size; + int period_count; +}; + +struct tinyalsa_mixer_io { + struct tinyalsa_mixer_io_props props; + struct tinyalsa_mixer_device *device_current; + struct list_head *devices; +}; + +struct tinyalsa_mixer { + struct tinyalsa_mixer_io output; + struct tinyalsa_mixer_io input; + struct mixer *mixer; +}; + +struct tinyalsa_mixer_config_data { + struct tinyalsa_mixer *mixer; + struct tinyalsa_mixer_io_props io_props; + struct tinyalsa_mixer_device_props device_props; + int direction; + + struct tinyalsa_mixer_device *device; + struct list_head **list_start; + struct list_head *list; +}; + +int tinyalsa_mixer_open(struct tinyalsa_mixer **mixer_p, char *config_file); +void tinyalsa_mixer_close(struct tinyalsa_mixer *mixer); + +struct tinyalsa_mixer_io_props *tinyalsa_mixer_get_input_props(struct tinyalsa_mixer *mixer); +struct tinyalsa_mixer_io_props *tinyalsa_mixer_get_output_props(struct tinyalsa_mixer *mixer); + +int tinyalsa_mixer_set_voice_volume(struct tinyalsa_mixer *mixer, audio_devices_t device); +int tinyalsa_mixer_set_input_gain(struct tinyalsa_mixer *mixer, audio_devices_t device); +int tinyalsa_mixer_set_mic_mute(struct tinyalsa_mixer *mixer, audio_devices_t device, int mute); +int tinyalsa_mixer_set_master_volume(struct tinyalsa_mixer *mixer, audio_devices_t device); +int tinyalsa_mixer_set_output_volume(struct tinyalsa_mixer *mixer, audio_devices_t device); + +int tinyalsa_mixer_set_route(struct tinyalsa_mixer *mixer, audio_devices_t device, audio_mode_t mode); + +#endif -- cgit v1.1