blob: 2c562792ee4fc1641b9d4082196de15d5e000a5b [file] [edit]
/*
* Copyright 2023 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "alsa_conformance_mixer.h"
#include <string.h>
#include "include/utlist.h"
#include "include/util.h"
#include <alsa/mixer.h>
static int alsa_mixer_open(const char *card_name, snd_mixer_t **mixer)
{
int rc;
*mixer = NULL;
rc = snd_mixer_open(mixer, 0);
if (rc < 0) {
return rc;
}
rc = snd_mixer_attach(*mixer, card_name);
if (rc < 0) {
goto fail_after_open;
}
rc = snd_mixer_selem_register(*mixer, NULL, NULL);
if (rc < 0) {
goto fail_after_open;
}
rc = snd_mixer_load(*mixer);
if (rc < 0) {
goto fail_after_open;
}
return rc;
fail_after_open:
snd_mixer_close(*mixer);
*mixer = NULL;
return rc;
}
static const char *output_mixer_name_filter(const char *mixer_name)
{
const char *output_mixer_names[] = {
"Headphone", "Headset", "Headset Earphone", "Speaker",
"PCM", "Master", "Digital", "Speaker Volume"
};
for (int i = 0; i < ARRAY_SIZE(output_mixer_names); i++) {
if (!strcmp(mixer_name, output_mixer_names[i])) {
return mixer_name;
}
}
return NULL;
}
static const char *input_mixer_name_filter(const char *mixer_name)
{
const char *input_mixer_names[] = { "Capture", "Digital Capture",
"Mic", "Microphone",
"Headset", "Mic Volume" };
for (int i = 0; i < ARRAY_SIZE(input_mixer_names); i++) {
if (!strcmp(mixer_name, input_mixer_names[i])) {
return mixer_name;
}
}
return NULL;
}
static struct alsa_mixer_control *
alsa_mixer_output_control_create(snd_mixer_elem_t *elem)
{
struct alsa_mixer_control *control;
control = (struct alsa_mixer_control *)calloc(
1, sizeof(struct alsa_mixer_control));
if (!control) {
perror("alsa_mixer_control_create calloc fail");
exit(EXIT_FAILURE);
}
control->elem = elem;
control->name = snd_mixer_selem_get_name(elem);
control->index = snd_mixer_selem_get_index(elem);
control->has_volume = snd_mixer_selem_has_playback_volume(elem);
snd_mixer_selem_get_playback_dB_range(elem, &control->min_volume_dB,
&control->max_volume_dB);
snd_mixer_selem_get_playback_volume_range(
elem, &control->volume_range_min, &control->volume_range_max);
return control;
}
static struct alsa_mixer_control *
alsa_mixer_input_control_create(snd_mixer_elem_t *elem)
{
struct alsa_mixer_control *control;
control = (struct alsa_mixer_control *)calloc(
1, sizeof(struct alsa_mixer_control));
if (!control) {
perror("alsa_mixer_control_create calloc fail");
exit(EXIT_FAILURE);
}
control->elem = elem;
control->name = snd_mixer_selem_get_name(elem);
control->has_volume = snd_mixer_selem_has_capture_volume(elem);
snd_mixer_selem_get_capture_dB_range(elem, &control->min_volume_dB,
&control->max_volume_dB);
snd_mixer_selem_get_capture_volume_range(
elem, &control->volume_range_min, &control->volume_range_max);
return control;
}
struct alsa_mixer *alsa_usb_mixer_create(const char *card_name)
{
int rc;
struct alsa_mixer *amixer;
struct alsa_mixer_control *control;
snd_mixer_elem_t *elem;
if (!card_name) {
fprintf(stderr, "card name is null\n");
return NULL;
}
amixer = (struct alsa_mixer *)calloc(1, sizeof(struct alsa_mixer));
if (!amixer) {
perror("alsa_usb_mixer_create calloc fail");
exit(EXIT_FAILURE);
}
rc = alsa_mixer_open(card_name, &amixer->mixer);
if (rc < 0) {
fprintf(stderr, "alsa_mixer_open fail\n");
return NULL;
}
for (elem = snd_mixer_first_elem(amixer->mixer); elem != NULL;
elem = snd_mixer_elem_next(elem)) {
const char *output_mixer_name = output_mixer_name_filter(
snd_mixer_selem_get_name(elem));
if (output_mixer_name &&
snd_mixer_selem_has_playback_volume(elem)) {
control = alsa_mixer_output_control_create(elem);
DL_APPEND(amixer->output_controls, control);
}
const char *input_mixer_name =
input_mixer_name_filter(snd_mixer_selem_get_name(elem));
if (input_mixer_name &&
snd_mixer_selem_has_capture_volume(elem)) {
control = alsa_mixer_input_control_create(elem);
DL_APPEND(amixer->input_controls, control);
}
}
return amixer;
}
void alsa_usb_mixer_control_destroy(struct alsa_mixer *amixer)
{
struct alsa_mixer_control *elem;
DL_FOREACH (amixer->output_controls, elem) {
DL_DELETE(amixer->output_controls, elem);
free(elem);
}
DL_FOREACH (amixer->input_controls, elem) {
DL_DELETE(amixer->input_controls, elem);
free(elem);
}
if (amixer->mixer) {
snd_mixer_close(amixer->mixer);
}
free(amixer);
}