blob: ae6c7f773ed13186020e1901d8718fc4c1f03eb0 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
* Inspired by TinyHW, written by Mark Brown at Wolfson Micro
*
* 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_route"
/*#define LOG_NDEBUG 0*/
#include <errno.h>
#include <expat.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <cutils/log.h>
#include <tinyalsa/asoundlib.h>
#define BUF_SIZE 1024
#define MIXER_XML_PATH "/system/etc/mixer_paths.xml"
#define INITIAL_MIXER_PATH_SIZE 8
struct mixer_state {
struct mixer_ctl *ctl;
unsigned int num_values;
int *old_value;
int *new_value;
int *reset_value;
/* If linked is true, only the first element in each array is valid */
bool old_linked;
bool new_linked;
bool reset_linked;
};
struct mixer_setting {
struct mixer_ctl *ctl;
unsigned int num_values;
int *value;
/* If linked is true, only the first element in each array is valid */
bool linked;
};
struct mixer_value {
struct mixer_ctl *ctl;
int index;
int value;
};
struct mixer_path {
char *name;
unsigned int size;
unsigned int length;
struct mixer_setting *setting;
};
struct audio_route {
struct mixer *mixer;
unsigned int num_mixer_ctls;
struct mixer_state *mixer_state;
unsigned int mixer_path_size;
unsigned int num_mixer_paths;
struct mixer_path *mixer_path;
};
struct config_parse_state {
struct audio_route *ar;
struct mixer_path *path;
int level;
};
/* path functions */
static void path_print(struct mixer_path *path)
{
unsigned int i;
unsigned int j;
ALOGE("Path: %s, length: %d", path->name, path->length);
for (i = 0; i < path->length; i++) {
ALOGE(" id=%d: ctl=%s linked=%c", i,
mixer_ctl_get_name(path->setting[i].ctl),
path->setting[i].linked ? 'y' : 'n');
for (j = 0; j < path->setting[i].num_values; j++)
ALOGE(" id=%d value=%d", j, path->setting[i].value[j]);
}
}
static void path_free(struct audio_route *ar)
{
unsigned int i;
for (i = 0; i < ar->num_mixer_paths; i++) {
if (ar->mixer_path[i].name)
free(ar->mixer_path[i].name);
if (ar->mixer_path[i].setting) {
if (ar->mixer_path[i].setting->value)
free(ar->mixer_path[i].setting->value);
free(ar->mixer_path[i].setting);
}
}
free(ar->mixer_path);
}
static struct mixer_path *path_get_by_name(struct audio_route *ar,
const char *name)
{
unsigned int i;
for (i = 0; i < ar->num_mixer_paths; i++)
if (strcmp(ar->mixer_path[i].name, name) == 0)
return &ar->mixer_path[i];
return NULL;
}
static struct mixer_path *path_create(struct audio_route *ar, const char *name)
{
struct mixer_path *new_mixer_path = NULL;
if (path_get_by_name(ar, name)) {
ALOGE("Path name '%s' already exists", name);
return NULL;
}
/* check if we need to allocate more space for mixer paths */
if (ar->mixer_path_size <= ar->num_mixer_paths) {
if (ar->mixer_path_size == 0)
ar->mixer_path_size = INITIAL_MIXER_PATH_SIZE;
else
ar->mixer_path_size *= 2;
new_mixer_path = realloc(ar->mixer_path, ar->mixer_path_size *
sizeof(struct mixer_path));
if (new_mixer_path == NULL) {
ALOGE("Unable to allocate more paths");
return NULL;
} else {
ar->mixer_path = new_mixer_path;
}
}
/* initialise the new mixer path */
ar->mixer_path[ar->num_mixer_paths].name = strdup(name);
ar->mixer_path[ar->num_mixer_paths].size = 0;
ar->mixer_path[ar->num_mixer_paths].length = 0;
ar->mixer_path[ar->num_mixer_paths].setting = NULL;
/* return the mixer path just added, then increment number of them */
return &ar->mixer_path[ar->num_mixer_paths++];
}
static int find_ctl_in_path(struct mixer_path *path, struct mixer_ctl *ctl)
{
unsigned int i;
for (i = 0; i < path->length; i++)
if (path->setting[i].ctl == ctl)
return i;
return -1;
}
static int alloc_path_setting(struct mixer_path *path)
{
struct mixer_setting *new_path_setting;
int path_index;
/* check if we need to allocate more space for path settings */
if (path->size <= path->length) {
if (path->size == 0)
path->size = INITIAL_MIXER_PATH_SIZE;
else
path->size *= 2;
new_path_setting = realloc(path->setting,
path->size * sizeof(struct mixer_setting));
if (new_path_setting == NULL) {
ALOGE("Unable to allocate more path settings");
return -1;
} else {
path->setting = new_path_setting;
}
}
path_index = path->length;
path->length++;
return path_index;
}
static int path_add_setting(struct mixer_path *path,
struct mixer_setting *setting)
{
unsigned int i;
int path_index;
if (find_ctl_in_path(path, setting->ctl) != -1) {
ALOGE("Control '%s' already exists in path '%s'",
mixer_ctl_get_name(setting->ctl), path->name);
return -1;
}
path_index = alloc_path_setting(path);
if (path_index < 0)
return -1;
path->setting[path_index].ctl = setting->ctl;
path->setting[path_index].num_values = setting->num_values;
path->setting[path_index].value = malloc(setting->num_values * sizeof(int));
path->setting[path_index].linked = setting->linked;
if (setting->linked) {
path->setting[path_index].value[0] = setting->value[0];
} else {
for (i = 0; i < setting->num_values; i++)
path->setting[path_index].value[i] = setting->value[i];
}
return 0;
}
static int path_add_value(struct mixer_path *path,
struct mixer_value *mixer_value)
{
unsigned int i;
int path_index;
unsigned int num_values;
/* Check that mixer value index is within range */
num_values = mixer_ctl_get_num_values(mixer_value->ctl);
if (mixer_value->index >= (int)num_values) {
ALOGE("mixer index %d is out of range for '%s'", mixer_value->index,
mixer_ctl_get_name(mixer_value->ctl));
return -1;
}
path_index = find_ctl_in_path(path, mixer_value->ctl);
if (path_index < 0) {
/* New path */
path_index = alloc_path_setting(path);
if (path_index < 0)
return -1;
/* initialise the new path setting */
path->setting[path_index].ctl = mixer_value->ctl;
path->setting[path_index].num_values = num_values;
path->setting[path_index].value = malloc(num_values * sizeof(int));
path->setting[path_index].linked = true;
path->setting[path_index].value[0] = mixer_value->value;
}
if (mixer_value->index == -1) {
/* Linked, so only set the first value */
path->setting[path_index].linked = true;
path->setting[path_index].value[0] = mixer_value->value;
} else {
if (path->setting[path_index].linked && (num_values > 1)) {
/* Unlinking the values, so duplicate them across */
for (i = 1; i < num_values; i++) {
path->setting[path_index].value[i] =
path->setting[path_index].value[0];
}
path->setting[path_index].linked = false;
}
path->setting[path_index].value[mixer_value->index] = mixer_value->value;
}
return 0;
}
static int path_add_path(struct mixer_path *path, struct mixer_path *sub_path)
{
unsigned int i;
for (i = 0; i < sub_path->length; i++)
if (path_add_setting(path, &sub_path->setting[i]) < 0)
return -1;
return 0;
}
static int path_apply(struct audio_route *ar, struct mixer_path *path)
{
unsigned int i;
unsigned int j;
unsigned int ctl_index;
for (i = 0; i < path->length; i++) {
struct mixer_ctl *ctl = path->setting[i].ctl;
/* locate the mixer ctl in the list */
for (ctl_index = 0; ctl_index < ar->num_mixer_ctls; ctl_index++)
if (ar->mixer_state[ctl_index].ctl == ctl)
break;
/* apply the new value(s) */
for (j = 0; j < ar->mixer_state[ctl_index].num_values; j++) {
ar->mixer_state[ctl_index].new_value[j] = path->setting[i].value[j];
if (path->setting[i].linked)
break;
}
ar->mixer_state[ctl_index].new_linked = path->setting[i].linked;
}
return 0;
}
/* mixer helper function */
static int mixer_enum_string_to_value(struct mixer_ctl *ctl, const char *string)
{
unsigned int i;
/* Search the enum strings for a particular one */
for (i = 0; i < mixer_ctl_get_num_enums(ctl); i++) {
if (strcmp(mixer_ctl_get_enum_string(ctl, i), string) == 0)
break;
}
return i;
}
static void start_tag(void *data, const XML_Char *tag_name,
const XML_Char **attr)
{
const XML_Char *attr_name = NULL;
const XML_Char *attr_id = NULL;
const XML_Char *attr_value = NULL;
struct config_parse_state *state = data;
struct audio_route *ar = state->ar;
unsigned int i;
unsigned int ctl_index;
struct mixer_ctl *ctl;
int value;
unsigned int id;
struct mixer_value mixer_value;
/* Get name, id and value attributes (these may be empty) */
for (i = 0; attr[i]; i += 2) {
if (strcmp(attr[i], "name") == 0)
attr_name = attr[i + 1];
if (strcmp(attr[i], "id") == 0)
attr_id = attr[i + 1];
else if (strcmp(attr[i], "value") == 0)
attr_value = attr[i + 1];
}
/* Look at tags */
if (strcmp(tag_name, "path") == 0) {
if (attr_name == NULL) {
ALOGE("Unnamed path!");
} else {
if (state->level == 1) {
/* top level path: create and stash the path */
state->path = path_create(ar, (char *)attr_name);
} else {
/* nested path */
struct mixer_path *sub_path = path_get_by_name(ar, attr_name);
path_add_path(state->path, sub_path);
}
}
}
else if (strcmp(tag_name, "ctl") == 0) {
/* Obtain the mixer ctl and value */
ctl = mixer_get_ctl_by_name(ar->mixer, attr_name);
switch (mixer_ctl_get_type(ctl)) {
case MIXER_CTL_TYPE_BOOL:
case MIXER_CTL_TYPE_INT:
value = atoi((char *)attr_value);
break;
case MIXER_CTL_TYPE_ENUM:
value = mixer_enum_string_to_value(ctl, (char *)attr_value);
break;
default:
value = 0;
break;
}
if (state->level == 1) {
/* top level ctl (initial setting) */
/* locate the mixer ctl in the list */
for (ctl_index = 0; ctl_index < ar->num_mixer_ctls; ctl_index++) {
if (ar->mixer_state[ctl_index].ctl == ctl)
break;
}
/* apply the new value */
if (attr_id) {
/* set only one value */
id = atoi((char *)attr_id);
if (id < ar->mixer_state[ctl_index].num_values) {
if (ar->mixer_state[ctl_index].new_linked) {
/*
* We're unlinking the values, so copy old_value[0] into
* all the new_value elements.
*/
for (i = 0; i < ar->mixer_state[ctl_index].num_values; i++) {
ar->mixer_state[ctl_index].new_value[i] =
ar->mixer_state[ctl_index].old_value[0];
}
ar->mixer_state[ctl_index].new_linked = false;
}
ar->mixer_state[ctl_index].new_value[id] = value;
} else {
ALOGE("value id out of range for mixer ctl '%s'",
mixer_ctl_get_name(ctl));
}
} else {
ar->mixer_state[ctl_index].new_value[0] = value;
ar->mixer_state[ctl_index].new_linked = true;
}
} else {
/* nested ctl (within a path) */
mixer_value.ctl = ctl;
mixer_value.value = value;
if (attr_id)
mixer_value.index = atoi((char *)attr_id);
else
mixer_value.index = -1;
path_add_value(state->path, &mixer_value);
}
}
state->level++;
}
static void end_tag(void *data, const XML_Char *tag_name)
{
struct config_parse_state *state = data;
state->level--;
}
static int alloc_mixer_state(struct audio_route *ar)
{
unsigned int i;
unsigned int j;
unsigned int num_values;
struct mixer_ctl *ctl;
bool linked;
ar->num_mixer_ctls = mixer_get_num_ctls(ar->mixer);
ar->mixer_state = malloc(ar->num_mixer_ctls * sizeof(struct mixer_state));
if (!ar->mixer_state)
return -1;
for (i = 0; i < ar->num_mixer_ctls; i++) {
ctl = mixer_get_ctl(ar->mixer, i);
num_values = mixer_ctl_get_num_values(ctl);
ar->mixer_state[i].old_value = malloc(num_values * sizeof(int));
ar->mixer_state[i].new_value = malloc(num_values * sizeof(int));
ar->mixer_state[i].reset_value = malloc(num_values * sizeof(int));
/*
* Get all mixer values for controls with multiple values. If all
* values are the same, set the linked flag.
*/
linked = true;
for (j = 0; j < num_values; j++) {
ar->mixer_state[i].old_value[j] = mixer_ctl_get_value(ctl, j);
ar->mixer_state[i].new_value[j] = ar->mixer_state[i].old_value[j];
/*
* If the next value is different from the last, set linked to
* false.
*/
if ((j > 0) && (ar->mixer_state[i].old_value[j - 1] !=
ar->mixer_state[i].old_value[j])) {
linked = false;
}
}
ar->mixer_state[i].ctl = ctl;
ar->mixer_state[i].old_linked = linked;
ar->mixer_state[i].new_linked = linked;
ar->mixer_state[i].num_values = num_values;
}
return 0;
}
static void free_mixer_state(struct audio_route *ar)
{
unsigned int i;
for (i = 0; i < ar->num_mixer_ctls; i++) {
free(ar->mixer_state[i].old_value);
free(ar->mixer_state[i].new_value);
free(ar->mixer_state[i].reset_value);
}
free(ar->mixer_state);
ar->mixer_state = NULL;
}
/* Update the mixer with any changed values */
int audio_route_update_mixer(struct audio_route *ar)
{
unsigned int i;
unsigned int j;
for (i = 0; i < ar->num_mixer_ctls; i++) {
unsigned int num_values = ar->mixer_state[i].num_values;
/* if the value has changed, update the mixer */
if (ar->mixer_state[i].new_linked) {
if (ar->mixer_state[i].old_value[0] != ar->mixer_state[i].new_value[0]) {
/* linked ctl, so set all ctl values the same */
for (j = 0; j < num_values; j++)
mixer_ctl_set_value(ar->mixer_state[i].ctl, j,
ar->mixer_state[i].new_value[0]);
ar->mixer_state[i].old_value[0] = ar->mixer_state[i].new_value[0];
}
} else {
for (j = 0; j < num_values; j++) {
/*
* unlinked ctl, so set each value if necessary.
* Note that if the new value is unlinked but the old is
* linked, only value 0 is valid, so we always have to
* update the mixer for the other values.
*/
if (ar->mixer_state[i].old_linked ||
(ar->mixer_state[i].old_value[j] !=
ar->mixer_state[i].new_value[j])) {
mixer_ctl_set_value(ar->mixer_state[i].ctl, j,
ar->mixer_state[i].new_value[j]);
ar->mixer_state[i].old_value[j] = ar->mixer_state[i].new_value[j];
}
}
}
ar->mixer_state[i].old_linked = ar->mixer_state[i].new_linked;
}
return 0;
}
/* saves the current state of the mixer, for resetting all controls */
static void save_mixer_state(struct audio_route *ar)
{
unsigned int i;
unsigned int j;
for (i = 0; i < ar->num_mixer_ctls; i++) {
for (j = 0; j < ar->mixer_state[i].num_values; j++) {
ar->mixer_state[i].reset_value[j] = ar->mixer_state[i].new_value[j];
/* if the values are linked, only need to save value 0 */
if (ar->mixer_state[i].new_linked)
break;
}
ar->mixer_state[i].reset_linked = ar->mixer_state[i].new_linked;
}
}
/* Reset the audio routes back to the initial state */
void audio_route_reset(struct audio_route *ar)
{
unsigned int i;
unsigned int j;
/* load all of the saved values */
for (i = 0; i < ar->num_mixer_ctls; i++) {
for (j = 0; j < ar->mixer_state[i].num_values; j++) {
ar->mixer_state[i].new_value[j] = ar->mixer_state[i].reset_value[j];
/* if the values are linked, only need to save value 0 */
if (ar->mixer_state[i].reset_linked)
break;
}
ar->mixer_state[i].new_linked = ar->mixer_state[i].reset_linked;
}
}
/* Apply an audio route path by name */
int audio_route_apply_path(struct audio_route *ar, const char *name)
{
struct mixer_path *path;
if (!ar) {
ALOGE("invalid audio_route");
return -1;
}
path = path_get_by_name(ar, name);
if (!path) {
ALOGE("unable to find path '%s'", name);
return -1;
}
path_apply(ar, path);
return 0;
}
struct audio_route *audio_route_init(unsigned int card, const char *xml_path)
{
struct config_parse_state state;
XML_Parser parser;
FILE *file;
int bytes_read;
void *buf;
int i;
struct audio_route *ar;
ar = calloc(1, sizeof(struct audio_route));
if (!ar)
goto err_calloc;
ar->mixer = mixer_open(card);
if (!ar->mixer) {
ALOGE("Unable to open the mixer, aborting.");
goto err_mixer_open;
}
ar->mixer_path = NULL;
ar->mixer_path_size = 0;
ar->num_mixer_paths = 0;
/* allocate space for and read current mixer settings */
if (alloc_mixer_state(ar) < 0)
goto err_mixer_state;
/* use the default XML path if none is provided */
if (xml_path == NULL)
xml_path = MIXER_XML_PATH;
file = fopen(xml_path, "r");
if (!file) {
ALOGE("Failed to open %s", xml_path);
goto err_fopen;
}
parser = XML_ParserCreate(NULL);
if (!parser) {
ALOGE("Failed to create XML parser");
goto err_parser_create;
}
memset(&state, 0, sizeof(state));
state.ar = ar;
XML_SetUserData(parser, &state);
XML_SetElementHandler(parser, start_tag, end_tag);
for (;;) {
buf = XML_GetBuffer(parser, BUF_SIZE);
if (buf == NULL)
goto err_parse;
bytes_read = fread(buf, 1, BUF_SIZE, file);
if (bytes_read < 0)
goto err_parse;
if (XML_ParseBuffer(parser, bytes_read,
bytes_read == 0) == XML_STATUS_ERROR) {
ALOGE("Error in mixer xml (%s)", MIXER_XML_PATH);
goto err_parse;
}
if (bytes_read == 0)
break;
}
/* apply the initial mixer values, and save them so we can reset the
mixer to the original values */
audio_route_update_mixer(ar);
save_mixer_state(ar);
XML_ParserFree(parser);
fclose(file);
return ar;
err_parse:
XML_ParserFree(parser);
err_parser_create:
fclose(file);
err_fopen:
free_mixer_state(ar);
err_mixer_state:
mixer_close(ar->mixer);
err_mixer_open:
free(ar);
ar = NULL;
err_calloc:
return NULL;
}
void audio_route_free(struct audio_route *ar)
{
free_mixer_state(ar);
mixer_close(ar->mixer);
free(ar);
}