blob: 3379d959099800a2b7b7c925831f965e76021454 [file] [log] [blame]
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <alsa/asoundlib.h>
#include <limits.h>
#include <stdio.h>
#include <syslog.h>
#include "cras_alsa_mixer.h"
#include "cras_alsa_mixer_name.h"
#include "cras_alsa_ucm.h"
#include "cras_util.h"
#include "utlist.h"
#define MIXER_CONTROL_VOLUME_DB_INVALID LONG_MAX
/* Represents an ALSA control element. Each device can have several of these,
* each potentially having independent volume and mute controls.
* elem - ALSA mixer element.
* has_volume - non-zero indicates there is a volume control.
* has_mute - non-zero indicates there is a mute switch.
* max_volume_dB - the maximum volume for this control, or
* MIXER_CONTROL_VOLUME_DB_INVALID.
* min_volume_dB - the minimum volume for this control, or
* MIXER_CONTROL_VOLUME_DB_INVALID.
*/
struct mixer_control_element {
snd_mixer_elem_t *elem;
int has_volume;
int has_mute;
long max_volume_dB;
long min_volume_dB;
struct mixer_control_element *prev, *next;
};
/* Represents an ALSA control element related to a specific input/output
* node such as speakers or headphones. A device can have several of these,
* each potentially having independent volume and mute controls.
*
* Each will have at least one mixer_control_element. For cases where there
* are separate control elements for left/right channels (for example),
* additional mixer_control_elements are added.
*
* For controls with volume it is assumed that all elements have the same
* range.
*
* name - Name of the control (typicially this is the same as the name of the
* mixer_control_element when there is one, or the name of the UCM
* parent when there are multiple).
* dir - Control direction, OUTPUT or INPUT only.
* elements - The mixer_control_elements that are driven by this control.
* has_volume - non-zero indicates there is a volume control.
* has_mute - non-zero indicates there is a mute switch.
* max_volume_dB - Maximum volume available in the volume control.
* min_volume_dB - Minimum volume available in the volume control.
*/
struct mixer_control {
const char *name;
enum CRAS_STREAM_DIRECTION dir;
struct mixer_control_element *elements;
int has_volume;
int has_mute;
long max_volume_dB;
long min_volume_dB;
struct mixer_control *prev, *next;
};
/* Holds a reference to the opened mixer and the volume controls.
* mixer - Pointer to the opened alsa mixer.
* main_volume_controls - List of volume controls (normally 'Master' and 'PCM').
* playback_switch - Switch used to mute the device.
* main_capture_controls - List of capture gain controls (normally 'Capture').
* capture_switch - Switch used to mute the capture stream.
* max_volume_dB - Maximum volume available in main volume controls. The dBFS
* value setting will be applied relative to this.
* min_volume_dB - Minimum volume available in main volume controls.
*/
struct cras_alsa_mixer {
snd_mixer_t *mixer;
struct mixer_control *main_volume_controls;
struct mixer_control *output_controls;
snd_mixer_elem_t *playback_switch;
struct mixer_control *main_capture_controls;
struct mixer_control *input_controls;
snd_mixer_elem_t *capture_switch;
long max_volume_dB;
long min_volume_dB;
};
/* Wrapper for snd_mixer_open and helpers.
* Args:
* mixdev - Name of the device to open the mixer for.
* mixer - Pointer filled with the opened mixer on success, NULL on failure.
*/
static void alsa_mixer_open(const char *mixdev, snd_mixer_t **mixer)
{
int rc;
*mixer = NULL;
rc = snd_mixer_open(mixer, 0);
if (rc < 0) {
syslog(LOG_ERR, "snd_mixer_open: %d: %s", rc, strerror(-rc));
return;
}
rc = snd_mixer_attach(*mixer, mixdev);
if (rc < 0) {
syslog(LOG_ERR, "snd_mixer_attach: %d: %s", rc, strerror(-rc));
goto fail_after_open;
}
rc = snd_mixer_selem_register(*mixer, NULL, NULL);
if (rc < 0) {
syslog(LOG_ERR, "snd_mixer_selem_register: %d: %s", rc,
strerror(-rc));
goto fail_after_open;
}
rc = snd_mixer_load(*mixer);
if (rc < 0) {
syslog(LOG_ERR, "snd_mixer_load: %d: %s", rc, strerror(-rc));
goto fail_after_open;
}
return;
fail_after_open:
snd_mixer_close(*mixer);
*mixer = NULL;
}
static struct mixer_control_element *
mixer_control_element_create(snd_mixer_elem_t *elem,
enum CRAS_STREAM_DIRECTION dir)
{
struct mixer_control_element *c;
long min, max;
if (!elem)
return NULL;
c = (struct mixer_control_element *)calloc(1, sizeof(*c));
if (!c) {
syslog(LOG_ERR, "No memory for mixer_control_elem.");
return NULL;
}
c->elem = elem;
c->max_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
c->min_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
if (dir == CRAS_STREAM_OUTPUT) {
c->has_mute = snd_mixer_selem_has_playback_switch(elem);
if (snd_mixer_selem_has_playback_volume(elem) &&
snd_mixer_selem_get_playback_dB_range(elem, &min, &max) ==
0) {
c->max_volume_dB = max;
c->min_volume_dB = min;
c->has_volume = 1;
}
} else if (dir == CRAS_STREAM_INPUT) {
c->has_mute = snd_mixer_selem_has_capture_switch(elem);
if (snd_mixer_selem_has_capture_volume(elem) &&
snd_mixer_selem_get_capture_dB_range(elem, &min, &max) ==
0) {
c->max_volume_dB = max;
c->min_volume_dB = min;
c->has_volume = 1;
}
}
return c;
}
static void mixer_control_destroy(struct mixer_control *control)
{
struct mixer_control_element *elem;
if (!control)
return;
DL_FOREACH (control->elements, elem) {
DL_DELETE(control->elements, elem);
free(elem);
}
if (control->name)
free((void *)control->name);
free(control);
}
static void mixer_control_destroy_list(struct mixer_control *control_list)
{
struct mixer_control *control;
if (!control_list)
return;
DL_FOREACH (control_list, control) {
DL_DELETE(control_list, control);
mixer_control_destroy(control);
}
}
static int mixer_control_add_element(struct mixer_control *control,
snd_mixer_elem_t *snd_elem)
{
struct mixer_control_element *elem;
if (!control)
return -EINVAL;
elem = mixer_control_element_create(snd_elem, control->dir);
if (!elem)
return -ENOMEM;
DL_APPEND(control->elements, elem);
if (elem->has_volume) {
if (!control->has_volume)
control->has_volume = 1;
/* Assume that all elements have a common volume range, and
* that both min and max values are valid if one of the two
* is valid. */
if (control->min_volume_dB == MIXER_CONTROL_VOLUME_DB_INVALID) {
control->min_volume_dB = elem->min_volume_dB;
control->max_volume_dB = elem->max_volume_dB;
} else if (control->min_volume_dB != elem->min_volume_dB ||
control->max_volume_dB != elem->max_volume_dB) {
syslog(LOG_WARNING,
"Element '%s' of control '%s' has different"
"volume range: [%ld:%ld] ctrl: [%ld:%ld]",
snd_mixer_selem_get_name(elem->elem),
control->name, elem->min_volume_dB,
elem->max_volume_dB, control->min_volume_dB,
control->max_volume_dB);
}
}
if (elem->has_mute && !control->has_mute)
control->has_mute = 1;
return 0;
}
static int mixer_control_create(struct mixer_control **control,
const char *name, snd_mixer_elem_t *elem,
enum CRAS_STREAM_DIRECTION dir)
{
struct mixer_control *c;
int rc = 0;
if (!control)
return -EINVAL;
c = (struct mixer_control *)calloc(1, sizeof(*c));
if (!c) {
syslog(LOG_ERR, "No memory for mixer_control: %s", name);
rc = -ENOMEM;
goto error;
}
c->dir = dir;
c->min_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
c->max_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
if (!name && elem)
name = snd_mixer_selem_get_name(elem);
if (!name) {
syslog(LOG_ERR, "Control does not have a name.");
rc = -EINVAL;
goto error;
}
c->name = strdup(name);
if (!c->name) {
syslog(LOG_ERR, "No memory for control's name: %s", name);
rc = -ENOMEM;
goto error;
}
if (elem && (rc = mixer_control_add_element(c, elem)))
goto error;
*control = c;
return 0;
error:
mixer_control_destroy(c);
*control = NULL;
return rc;
}
/* Creates a mixer_control by finding mixer element names in simple mixer
* interface.
* Args:
* control[out] - Storage for resulting pointer to mixer_control.
* cmix[in] - Parent alsa mixer.
* name[in] - Optional name of the control. Input NULL to take the name of
* the first element from mixer_names.
* mixer_names[in] - Names of the ASLA mixer control elements. Must not
* be empty.
* dir[in] - Control direction: CRAS_STREAM_OUTPUT or CRAS_STREAM_INPUT.
* Returns:
* Returns 0 for success, negative error code otherwise. *control is
* initialized to NULL on error, or has a valid pointer for success.
*/
static int mixer_control_create_by_name(struct mixer_control **control,
struct cras_alsa_mixer *cmix,
const char *name,
struct mixer_name *mixer_names,
enum CRAS_STREAM_DIRECTION dir)
{
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *elem;
struct mixer_control *c;
struct mixer_name *m_name;
int rc;
if (!control)
return -EINVAL;
*control = NULL;
if (!mixer_names)
return -EINVAL;
if (!name) {
/* Assume that we're using the first name in the list of mixer
* names. */
name = mixer_names->name;
}
rc = mixer_control_create(&c, name, NULL, dir);
if (rc)
return rc;
snd_mixer_selem_id_malloc(&sid);
DL_FOREACH (mixer_names, m_name) {
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, m_name->name);
elem = snd_mixer_find_selem(cmix->mixer, sid);
if (!elem) {
mixer_control_destroy(c);
snd_mixer_selem_id_free(sid);
syslog(LOG_ERR, "Unable to find simple control %s, 0",
m_name->name);
return -ENOENT;
}
rc = mixer_control_add_element(c, elem);
if (rc) {
mixer_control_destroy(c);
snd_mixer_selem_id_free(sid);
return rc;
}
}
snd_mixer_selem_id_free(sid);
*control = c;
return 0;
}
static int mixer_control_set_dBFS(const struct mixer_control *control,
long to_set)
{
const struct mixer_control_element *elem = NULL;
int rc = -EINVAL;
if (!control)
return rc;
DL_FOREACH (control->elements, elem) {
if (elem->has_volume) {
if (control->dir == CRAS_STREAM_OUTPUT)
rc = snd_mixer_selem_set_playback_dB_all(
elem->elem, to_set, 1);
else if (control->dir == CRAS_STREAM_INPUT)
rc = snd_mixer_selem_set_capture_dB_all(
elem->elem, to_set, 1);
if (rc)
break;
syslog(LOG_DEBUG, "%s:%s volume set to %ld",
control->name,
snd_mixer_selem_get_name(elem->elem), to_set);
}
}
if (rc && elem) {
syslog(LOG_ERR, "Failed to set volume of '%s:%s': %d",
control->name, snd_mixer_selem_get_name(elem->elem), rc);
}
return rc;
}
static int mixer_control_get_dBFS(const struct mixer_control *control,
long *to_get)
{
const struct mixer_control_element *elem = NULL;
int rc = -EINVAL;
if (!control || !to_get)
return -EINVAL;
DL_FOREACH (control->elements, elem) {
if (elem->has_volume) {
if (control->dir == CRAS_STREAM_OUTPUT)
rc = snd_mixer_selem_get_playback_dB(
elem->elem, SND_MIXER_SCHN_FRONT_LEFT,
to_get);
else if (control->dir == CRAS_STREAM_INPUT)
rc = snd_mixer_selem_get_capture_dB(
elem->elem, SND_MIXER_SCHN_FRONT_LEFT,
to_get);
/* Assume all of the elements of this control have
* the same value. */
break;
}
}
if (rc && elem) {
syslog(LOG_ERR, "Failed to get volume of '%s:%s': %d",
control->name, snd_mixer_selem_get_name(elem->elem), rc);
}
return rc;
}
static int mixer_control_set_mute(const struct mixer_control *control,
int muted)
{
const struct mixer_control_element *elem = NULL;
int rc = -EINVAL;
if (!control)
return -EINVAL;
DL_FOREACH (control->elements, elem) {
if (elem->has_mute) {
if (control->dir == CRAS_STREAM_OUTPUT)
rc = snd_mixer_selem_set_playback_switch_all(
elem->elem, !muted);
else if (control->dir == CRAS_STREAM_INPUT)
rc = snd_mixer_selem_set_capture_switch_all(
elem->elem, !muted);
if (rc)
break;
}
}
if (rc && elem) {
syslog(LOG_ERR, "Failed to mute '%s:%s': %d", control->name,
snd_mixer_selem_get_name(elem->elem), rc);
}
return rc;
}
/* Adds the main volume control to the list and grabs the first seen playback
* switch to use for mute. */
static int add_main_volume_control(struct cras_alsa_mixer *cmix,
snd_mixer_elem_t *elem)
{
if (snd_mixer_selem_has_playback_volume(elem)) {
long range;
struct mixer_control *c, *next;
int rc = mixer_control_create(&c, NULL, elem,
CRAS_STREAM_OUTPUT);
if (rc)
return rc;
if (c->has_volume) {
cmix->max_volume_dB += c->max_volume_dB;
cmix->min_volume_dB += c->min_volume_dB;
}
range = c->max_volume_dB - c->min_volume_dB;
DL_FOREACH (cmix->main_volume_controls, next) {
if (range > next->max_volume_dB - next->min_volume_dB)
break;
}
syslog(LOG_DEBUG, "Add main volume control %s\n", c->name);
DL_INSERT(cmix->main_volume_controls, next, c);
}
/* If cmix doesn't yet have a playback switch and this is a playback
* switch, use it. */
if (cmix->playback_switch == NULL &&
snd_mixer_selem_has_playback_switch(elem)) {
syslog(LOG_DEBUG, "Using '%s' as playback switch.",
snd_mixer_selem_get_name(elem));
cmix->playback_switch = elem;
}
return 0;
}
/* Adds the main capture control to the list and grabs the first seen capture
* switch to mute input. */
static int add_main_capture_control(struct cras_alsa_mixer *cmix,
snd_mixer_elem_t *elem)
{
/* TODO(dgreid) handle index != 0, map to correct input. */
if (snd_mixer_selem_get_index(elem) > 0)
return 0;
if (snd_mixer_selem_has_capture_volume(elem)) {
struct mixer_control *c;
int rc =
mixer_control_create(&c, NULL, elem, CRAS_STREAM_INPUT);
if (rc)
return rc;
syslog(LOG_DEBUG, "Add main capture control %s\n", c->name);
DL_APPEND(cmix->main_capture_controls, c);
}
/* If cmix doesn't yet have a capture switch and this is a capture
* switch, use it. */
if (cmix->capture_switch == NULL &&
snd_mixer_selem_has_capture_switch(elem)) {
syslog(LOG_DEBUG, "Using '%s' as capture switch.",
snd_mixer_selem_get_name(elem));
cmix->capture_switch = elem;
}
return 0;
}
/* Adds a control to the list. */
static int add_control_with_name(struct cras_alsa_mixer *cmix,
enum CRAS_STREAM_DIRECTION dir,
snd_mixer_elem_t *elem, const char *name)
{
int index; /* Index part of mixer simple element */
struct mixer_control *c;
int rc;
index = snd_mixer_selem_get_index(elem);
syslog(LOG_DEBUG, "Add %s control: %s,%d\n",
dir == CRAS_STREAM_OUTPUT ? "output" : "input", name, index);
rc = mixer_control_create(&c, name, elem, dir);
if (rc)
return rc;
if (c->has_volume)
syslog(LOG_DEBUG, "Control '%s' volume range: [%ld:%ld]",
c->name, c->min_volume_dB, c->max_volume_dB);
if (dir == CRAS_STREAM_OUTPUT)
DL_APPEND(cmix->output_controls, c);
else if (dir == CRAS_STREAM_INPUT)
DL_APPEND(cmix->input_controls, c);
return 0;
}
static int add_control(struct cras_alsa_mixer *cmix,
enum CRAS_STREAM_DIRECTION dir, snd_mixer_elem_t *elem)
{
return add_control_with_name(cmix, dir, elem,
snd_mixer_selem_get_name(elem));
}
static void list_controls(struct mixer_control *control_list,
cras_alsa_mixer_control_callback cb, void *cb_arg)
{
struct mixer_control *control;
DL_FOREACH (control_list, control)
cb(control, cb_arg);
}
static struct mixer_control *
get_control_matching_name(struct mixer_control *control_list, const char *name)
{
struct mixer_control *c;
DL_FOREACH (control_list, c) {
if (strstr(name, c->name))
return c;
}
return NULL;
}
/* Creates a mixer_control with multiple control elements. */
static int add_control_with_coupled_mixers(struct cras_alsa_mixer *cmix,
enum CRAS_STREAM_DIRECTION dir,
const char *name,
struct mixer_name *coupled_controls)
{
struct mixer_control *c;
int rc;
rc = mixer_control_create_by_name(&c, cmix, name, coupled_controls,
dir);
if (rc)
return rc;
syslog(LOG_DEBUG, "Add %s control: %s\n",
dir == CRAS_STREAM_OUTPUT ? "output" : "input", c->name);
mixer_name_dump(coupled_controls, " elements");
if (c->has_volume)
syslog(LOG_DEBUG, "Control '%s' volume range: [%ld:%ld]",
c->name, c->min_volume_dB, c->max_volume_dB);
if (dir == CRAS_STREAM_OUTPUT)
DL_APPEND(cmix->output_controls, c);
else if (dir == CRAS_STREAM_INPUT)
DL_APPEND(cmix->input_controls, c);
return 0;
}
static int add_control_by_name(struct cras_alsa_mixer *cmix,
enum CRAS_STREAM_DIRECTION dir, const char *name)
{
struct mixer_control *c;
struct mixer_name *m_name;
int rc;
m_name = mixer_name_add(NULL, name, dir, MIXER_NAME_VOLUME);
if (!m_name)
return -ENOMEM;
rc = mixer_control_create_by_name(&c, cmix, name, m_name, dir);
mixer_name_free(m_name);
if (rc)
return rc;
syslog(LOG_DEBUG, "Add %s control: %s\n",
dir == CRAS_STREAM_OUTPUT ? "output" : "input", c->name);
if (c->has_volume)
syslog(LOG_DEBUG, "Control '%s' volume range: [%ld:%ld]",
c->name, c->min_volume_dB, c->max_volume_dB);
if (dir == CRAS_STREAM_OUTPUT)
DL_APPEND(cmix->output_controls, c);
else if (dir == CRAS_STREAM_INPUT)
DL_APPEND(cmix->input_controls, c);
return 0;
}
/*
* Exported interface.
*/
struct cras_alsa_mixer *cras_alsa_mixer_create(const char *card_name)
{
struct cras_alsa_mixer *cmix;
cmix = (struct cras_alsa_mixer *)calloc(1, sizeof(*cmix));
if (cmix == NULL)
return NULL;
syslog(LOG_DEBUG, "Add mixer for device %s", card_name);
alsa_mixer_open(card_name, &cmix->mixer);
return cmix;
}
int cras_alsa_mixer_add_controls_by_name_matching(
struct cras_alsa_mixer *cmix, struct mixer_name *extra_controls,
struct mixer_name *coupled_controls)
{
/* Names of controls for main system volume. */
static const char *const main_volume_names[] = {
"Master",
"Digital",
"PCM",
};
/* Names of controls for individual outputs. */
static const char *const output_names[] = {
"Headphone",
"Headset",
"HDMI",
"Speaker",
};
/* Names of controls for capture gain/attenuation and mute. */
static const char *const main_capture_names[] = {
"Capture",
"Digital Capture",
};
/* Names of controls for individual inputs. */
static const char *const input_names[] = {
"Mic",
"Microphone",
};
struct mixer_name *default_controls = NULL;
snd_mixer_elem_t *elem;
int extra_main_volume = 0;
snd_mixer_elem_t *other_elem = NULL;
long other_dB_range = 0;
int rc = 0;
/* Note that there is no mixer on some cards. This is acceptable. */
if (cmix->mixer == NULL) {
syslog(LOG_DEBUG, "Couldn't open mixer.");
return 0;
}
default_controls =
mixer_name_add_array(default_controls, output_names,
ARRAY_SIZE(output_names),
CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME);
default_controls =
mixer_name_add_array(default_controls, input_names,
ARRAY_SIZE(input_names), CRAS_STREAM_INPUT,
MIXER_NAME_VOLUME);
default_controls =
mixer_name_add_array(default_controls, main_volume_names,
ARRAY_SIZE(main_volume_names),
CRAS_STREAM_OUTPUT,
MIXER_NAME_MAIN_VOLUME);
default_controls =
mixer_name_add_array(default_controls, main_capture_names,
ARRAY_SIZE(main_capture_names),
CRAS_STREAM_INPUT, MIXER_NAME_MAIN_VOLUME);
extra_main_volume =
mixer_name_find(extra_controls, NULL, CRAS_STREAM_OUTPUT,
MIXER_NAME_MAIN_VOLUME) != NULL;
/* Find volume and mute controls. */
for (elem = snd_mixer_first_elem(cmix->mixer); elem != NULL;
elem = snd_mixer_elem_next(elem)) {
const char *name;
struct mixer_name *control;
int found = 0;
name = snd_mixer_selem_get_name(elem);
if (name == NULL)
continue;
/* Find a matching control. */
control = mixer_name_find(default_controls, name,
CRAS_STREAM_OUTPUT,
MIXER_NAME_UNDEFINED);
/* If our extra controls contain a main volume
* entry, and we found a main volume entry, then
* skip it. */
if (extra_main_volume && control &&
control->type == MIXER_NAME_MAIN_VOLUME)
control = NULL;
/* If we didn't match any of the defaults, match
* the extras list. */
if (!control)
control = mixer_name_find(extra_controls, name,
CRAS_STREAM_OUTPUT,
MIXER_NAME_UNDEFINED);
if (control) {
int rc = -1;
switch (control->type) {
case MIXER_NAME_MAIN_VOLUME:
rc = add_main_volume_control(cmix, elem);
break;
case MIXER_NAME_VOLUME:
/* TODO(dgreid) - determine device index. */
rc = add_control(cmix, CRAS_STREAM_OUTPUT,
elem);
break;
case MIXER_NAME_UNDEFINED:
rc = -EINVAL;
break;
}
if (rc) {
syslog(LOG_ERR,
"Failed to add mixer control '%s'"
" with type '%d'",
control->name, control->type);
goto out;
}
found = 1;
}
/* Find a matching input control. */
control = mixer_name_find(default_controls, name,
CRAS_STREAM_INPUT,
MIXER_NAME_UNDEFINED);
/* If we didn't match any of the defaults, match
the extras list */
if (!control)
control = mixer_name_find(extra_controls, name,
CRAS_STREAM_INPUT,
MIXER_NAME_UNDEFINED);
if (control) {
int rc = -1;
switch (control->type) {
case MIXER_NAME_MAIN_VOLUME:
rc = add_main_capture_control(cmix, elem);
break;
case MIXER_NAME_VOLUME:
rc = add_control(cmix, CRAS_STREAM_INPUT, elem);
break;
case MIXER_NAME_UNDEFINED:
rc = -EINVAL;
break;
}
if (rc) {
syslog(LOG_ERR,
"Failed to add mixer control '%s'"
" with type '%d'",
control->name, control->type);
goto out;
}
found = 1;
}
if (!found && snd_mixer_selem_has_playback_volume(elem)) {
/* Temporarily cache one elem whose name is not
* in the list above, but has a playback volume
* control and the largest volume range. */
long min, max, range;
if (snd_mixer_selem_get_playback_dB_range(elem, &min,
&max) != 0)
continue;
range = max - min;
if (other_dB_range < range) {
other_dB_range = range;
other_elem = elem;
}
}
}
/* Handle coupled output names for speaker */
if (coupled_controls) {
rc = add_control_with_coupled_mixers(
cmix, CRAS_STREAM_OUTPUT, "Speaker", coupled_controls);
if (rc) {
syslog(LOG_ERR, "Could not add coupled output");
goto out;
}
}
/* If there is no volume control and output control found,
* use the volume control which has the largest volume range
* in the mixer as a main volume control. */
if (!cmix->main_volume_controls && !cmix->output_controls &&
other_elem) {
rc = add_main_volume_control(cmix, other_elem);
if (rc) {
syslog(LOG_ERR, "Could not add other volume control");
goto out;
}
}
out:
mixer_name_free(default_controls);
return rc;
}
int cras_alsa_mixer_add_main_volume_control_by_name(
struct cras_alsa_mixer *cmix, struct mixer_name *mixer_names)
{
snd_mixer_elem_t *elem;
struct mixer_name *m_name;
int rc = 0;
snd_mixer_selem_id_t *sid;
if (!mixer_names)
return -EINVAL;
snd_mixer_selem_id_malloc(&sid);
DL_FOREACH (mixer_names, m_name) {
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, m_name->name);
elem = snd_mixer_find_selem(cmix->mixer, sid);
if (!elem) {
rc = -ENOENT;
syslog(LOG_ERR, "Unable to find simple control %s, 0",
m_name->name);
break;
}
rc = add_main_volume_control(cmix, elem);
if (rc)
break;
}
snd_mixer_selem_id_free(sid);
return rc;
}
int cras_alsa_mixer_add_controls_in_section(struct cras_alsa_mixer *cmix,
struct ucm_section *section)
{
int rc;
/* Note that there is no mixer on some cards. This is acceptable. */
if (cmix->mixer == NULL) {
syslog(LOG_DEBUG, "Couldn't open mixer.");
return 0;
}
if (!section) {
syslog(LOG_ERR, "No UCM SectionDevice specified.");
return -EINVAL;
}
/* TODO(muirj) - Extra main volume controls when fully-specified. */
if (section->mixer_name) {
rc = add_control_by_name(cmix, section->dir,
section->mixer_name);
if (rc) {
syslog(LOG_ERR, "Could not add mixer control '%s': %s",
section->mixer_name, strerror(-rc));
return rc;
}
}
if (section->coupled) {
rc = add_control_with_coupled_mixers(
cmix, section->dir, section->name, section->coupled);
if (rc) {
syslog(LOG_ERR, "Could not add coupled control: %s",
strerror(-rc));
return rc;
}
}
return 0;
}
void cras_alsa_mixer_destroy(struct cras_alsa_mixer *cras_mixer)
{
assert(cras_mixer);
mixer_control_destroy_list(cras_mixer->main_volume_controls);
mixer_control_destroy_list(cras_mixer->main_capture_controls);
mixer_control_destroy_list(cras_mixer->output_controls);
mixer_control_destroy_list(cras_mixer->input_controls);
if (cras_mixer->mixer)
snd_mixer_close(cras_mixer->mixer);
free(cras_mixer);
}
int cras_alsa_mixer_has_main_volume(const struct cras_alsa_mixer *cras_mixer)
{
return !!cras_mixer->main_volume_controls;
}
int cras_alsa_mixer_has_volume(const struct mixer_control *mixer_control)
{
return mixer_control && mixer_control->has_volume;
}
void cras_alsa_mixer_set_dBFS(struct cras_alsa_mixer *cras_mixer, long dBFS,
struct mixer_control *mixer_output)
{
struct mixer_control *c;
long to_set;
assert(cras_mixer);
/* dBFS is normally < 0 to specify the attenuation from max. max is the
* combined max of the main controls and the current output.
*/
to_set = dBFS + cras_mixer->max_volume_dB;
if (cras_alsa_mixer_has_volume(mixer_output))
to_set += mixer_output->max_volume_dB;
/* Go through all the controls, set the volume level for each,
* taking the value closest but greater than the desired volume. If the
* entire volume can't be set on the current control, move on to the
* next one until we have the exact volume, or gotten as close as we
* can. Once all of the volume is set the rest of the controls should be
* set to 0dB. */
DL_FOREACH (cras_mixer->main_volume_controls, c) {
long actual_dB;
if (!c->has_volume)
continue;
if (mixer_control_set_dBFS(c, to_set) == 0 &&
mixer_control_get_dBFS(c, &actual_dB) == 0)
to_set -= actual_dB;
}
/* Apply the rest to the output-specific control. */
if (cras_alsa_mixer_has_volume(mixer_output))
mixer_control_set_dBFS(mixer_output, to_set);
}
long cras_alsa_mixer_get_dB_range(struct cras_alsa_mixer *cras_mixer)
{
if (!cras_mixer)
return 0;
return cras_mixer->max_volume_dB - cras_mixer->min_volume_dB;
}
long cras_alsa_mixer_get_output_dB_range(struct mixer_control *mixer_output)
{
if (!cras_alsa_mixer_has_volume(mixer_output))
return 0;
return mixer_output->max_volume_dB - mixer_output->min_volume_dB;
}
void cras_alsa_mixer_set_capture_dBFS(struct cras_alsa_mixer *cras_mixer,
long dBFS,
struct mixer_control *mixer_input)
{
struct mixer_control *c;
long to_set;
assert(cras_mixer);
to_set = dBFS;
/* Go through all the controls, set the gain for each, taking the value
* closest but greater than the desired gain. If the entire gain can't
* be set on the current control, move on to the next one until we have
* the exact gain, or gotten as close as we can. Once all of the gain is
* set the rest of the controls should be set to 0dB. */
DL_FOREACH (cras_mixer->main_capture_controls, c) {
long actual_dB;
if (!c->has_volume)
continue;
if (mixer_control_set_dBFS(c, to_set) == 0 &&
mixer_control_get_dBFS(c, &actual_dB) == 0)
to_set -= actual_dB;
}
/* Apply the reset to input specific control */
if (cras_alsa_mixer_has_volume(mixer_input))
mixer_control_set_dBFS(mixer_input, to_set);
}
long cras_alsa_mixer_get_minimum_capture_gain(struct cras_alsa_mixer *cmix,
struct mixer_control *mixer_input)
{
struct mixer_control *c;
long total_min = 0;
assert(cmix);
DL_FOREACH (cmix->main_capture_controls, c)
if (c->has_volume)
total_min += c->min_volume_dB;
if (mixer_input && mixer_input->has_volume)
total_min += mixer_input->min_volume_dB;
return total_min;
}
long cras_alsa_mixer_get_maximum_capture_gain(struct cras_alsa_mixer *cmix,
struct mixer_control *mixer_input)
{
struct mixer_control *c;
long total_max = 0;
assert(cmix);
DL_FOREACH (cmix->main_capture_controls, c)
if (c->has_volume)
total_max += c->max_volume_dB;
if (mixer_input && mixer_input->has_volume)
total_max += mixer_input->max_volume_dB;
return total_max;
}
void cras_alsa_mixer_set_mute(struct cras_alsa_mixer *cras_mixer, int muted,
struct mixer_control *mixer_output)
{
assert(cras_mixer);
if (cras_mixer->playback_switch) {
snd_mixer_selem_set_playback_switch_all(
cras_mixer->playback_switch, !muted);
}
if (mixer_output && mixer_output->has_mute) {
mixer_control_set_mute(mixer_output, muted);
}
}
void cras_alsa_mixer_set_capture_mute(struct cras_alsa_mixer *cras_mixer,
int muted,
struct mixer_control *mixer_input)
{
assert(cras_mixer);
if (cras_mixer->capture_switch) {
snd_mixer_selem_set_capture_switch_all(
cras_mixer->capture_switch, !muted);
return;
}
if (mixer_input && mixer_input->has_mute)
mixer_control_set_mute(mixer_input, muted);
}
void cras_alsa_mixer_list_outputs(struct cras_alsa_mixer *cras_mixer,
cras_alsa_mixer_control_callback cb,
void *cb_arg)
{
assert(cras_mixer);
list_controls(cras_mixer->output_controls, cb, cb_arg);
}
void cras_alsa_mixer_list_inputs(struct cras_alsa_mixer *cras_mixer,
cras_alsa_mixer_control_callback cb,
void *cb_arg)
{
assert(cras_mixer);
list_controls(cras_mixer->input_controls, cb, cb_arg);
}
const char *
cras_alsa_mixer_get_control_name(const struct mixer_control *control)
{
if (!control)
return NULL;
return control->name;
}
struct mixer_control *
cras_alsa_mixer_get_control_matching_name(struct cras_alsa_mixer *cras_mixer,
enum CRAS_STREAM_DIRECTION dir,
const char *name, int create_missing)
{
struct mixer_control *c;
assert(cras_mixer);
if (!name)
return NULL;
if (dir == CRAS_STREAM_OUTPUT) {
c = get_control_matching_name(cras_mixer->output_controls,
name);
} else if (dir == CRAS_STREAM_INPUT) {
c = get_control_matching_name(cras_mixer->input_controls, name);
} else {
return NULL;
}
/* TODO: Allowing creation of a new control is a workaround: we
* should pass the input names in ucm config to
* cras_alsa_mixer_create. */
if (!c && cras_mixer->mixer && create_missing) {
int rc = add_control_by_name(cras_mixer, dir, name);
if (rc)
return NULL;
c = cras_alsa_mixer_get_control_matching_name(cras_mixer, dir,
name, 0);
}
return c;
}
struct mixer_control *
cras_alsa_mixer_get_control_for_section(struct cras_alsa_mixer *cras_mixer,
const struct ucm_section *section)
{
assert(cras_mixer && section);
if (section->mixer_name) {
return cras_alsa_mixer_get_control_matching_name(
cras_mixer, section->dir, section->mixer_name, 0);
} else if (section->coupled) {
return cras_alsa_mixer_get_control_matching_name(
cras_mixer, section->dir, section->name, 0);
}
return NULL;
}
struct mixer_control *
cras_alsa_mixer_get_output_matching_name(struct cras_alsa_mixer *cras_mixer,
const char *const name)
{
return cras_alsa_mixer_get_control_matching_name(
cras_mixer, CRAS_STREAM_OUTPUT, name, 0);
}
struct mixer_control *
cras_alsa_mixer_get_input_matching_name(struct cras_alsa_mixer *cras_mixer,
const char *name)
{
/* TODO: Allowing creation of a new control is a workaround: we
* should pass the input names in ucm config to
* cras_alsa_mixer_create. */
return cras_alsa_mixer_get_control_matching_name(
cras_mixer, CRAS_STREAM_INPUT, name, 1);
}
int cras_alsa_mixer_set_output_active_state(struct mixer_control *output,
int active)
{
assert(output);
if (!output->has_mute)
return -1;
return mixer_control_set_mute(output, !active);
}