This commit is contained in:
2026-03-29 14:01:52 +03:00
commit 0611279128
210 changed files with 60454 additions and 0 deletions

292
op/mixer_alsa.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* 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 2 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 <http://www.gnu.org/licenses/>.
*/
#include "../mixer.h"
#include "../op.h"
#include "../xmalloc.h"
#include "../debug.h"
#include <strings.h>
#include <math.h>
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>
static snd_mixer_t *mixer;
static snd_mixer_elem_t *mixer_elem = NULL;
/* configuration */
static char *alsa_mixer_device = NULL;
static char *alsa_mixer_element = NULL;
static int alsa_mixer_mapped_volume = 1;
static int alsa_mixer_init(void)
{
if (alsa_mixer_device == NULL)
alsa_mixer_device = xstrdup("default");
if (alsa_mixer_element == NULL)
alsa_mixer_element = xstrdup("PCM");
return 0;
}
static int alsa_mixer_exit(void)
{
free(alsa_mixer_device);
alsa_mixer_device = NULL;
free(alsa_mixer_element);
alsa_mixer_element = NULL;
return 0;
}
static snd_mixer_elem_t *find_mixer_elem_by_name(const char *name)
{
snd_mixer_elem_t *elem;
snd_mixer_selem_id_t *sid = NULL;
snd_mixer_selem_id_alloca(&sid);
if (snd_mixer_selem_id_parse(sid, name))
return NULL;
elem = snd_mixer_find_selem(mixer, sid);
if (!elem) {
d_print("unable to find simple control '%s',%i\n",
snd_mixer_selem_id_get_name(sid),
snd_mixer_selem_id_get_index(sid));
return NULL;
}
if (!snd_mixer_selem_has_playback_volume(elem)) {
d_print("simple control '%s',%i does not have playback volume\n",
snd_mixer_selem_id_get_name(sid),
snd_mixer_selem_id_get_index(sid));
return NULL;
}
return elem;
}
static int alsa_mixer_open(int *volume_max)
{
snd_mixer_elem_t *elem;
int rc;
rc = snd_mixer_open(&mixer, 0);
if (rc < 0)
goto error;
rc = snd_mixer_attach(mixer, alsa_mixer_device);
if (rc < 0)
goto error;
rc = snd_mixer_selem_register(mixer, NULL, NULL);
if (rc < 0)
goto error;
rc = snd_mixer_load(mixer);
if (rc < 0)
goto error;
elem = find_mixer_elem_by_name(alsa_mixer_element);
if (!elem) {
d_print("mixer element '%s' not found, trying 'Master'\n",
alsa_mixer_element);
elem = find_mixer_elem_by_name("Master");
if (!elem) {
d_print("error: cannot find suitable mixer element\n");
return -2;
}
}
mixer_elem = elem;
*volume_max = 100;
return 0;
error:
d_print("error: %s\n", snd_strerror(rc));
return -1;
}
static int alsa_mixer_close(void)
{
snd_mixer_close(mixer);
return 0;
}
static int alsa_mixer_get_fds(int what, int *fds)
{
struct pollfd pfd[NR_MIXER_FDS];
int count, i;
switch (what) {
case MIXER_FDS_VOLUME:
count = snd_mixer_poll_descriptors(mixer, pfd, NR_MIXER_FDS);
for (i = 0; i < count; i++)
fds[i] = pfd[i].fd;
return count;
default:
return 0;
}
}
static void alsa_mixer_get_range(long *min, long *max, int *linear_mapping)
{
if (!*linear_mapping) {
int err = snd_mixer_selem_get_playback_dB_range(
mixer_elem, min, max);
*linear_mapping = err != 0 || *min >= *max;
/* Force linear mapping for small ranges of 24 dB */
enum { max_linear_db_scale = 24 };
*linear_mapping |= *max - *min <= max_linear_db_scale * 100;
}
if (*linear_mapping)
snd_mixer_selem_get_playback_volume_range(mixer_elem, min, max);
}
static void alsa_mixer_set_vol(snd_mixer_selem_channel_id_t channel,
long min, long max, int vol, int linear_mapping)
{
if (linear_mapping) {
long v = (long)(vol * (max - min) / 100.0 + 0.5) + min;
snd_mixer_selem_set_playback_volume(mixer_elem, channel, v);
} else {
/* alsamixer's volume mapping */
double norm = vol / 100.0;
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
double min_norm = pow(10, (min - max) / 6000.0);
norm = norm * (1 - min_norm) + min_norm;
}
if (norm <= 0)
norm = 1e-36;
long v = (long)(6000.0 * log10(norm) + 0.5) + max;
snd_mixer_selem_set_playback_dB(mixer_elem, channel, v, 0);
}
}
static int alsa_mixer_set_volume(int l, int r)
{
int linear_mapping = !alsa_mixer_mapped_volume;
long min, max;
if (!mixer_elem)
return -1;
snd_mixer_handle_events(mixer);
alsa_mixer_get_range(&min, &max, &linear_mapping);
alsa_mixer_set_vol(SND_MIXER_SCHN_FRONT_LEFT,
min, max, l, linear_mapping);
alsa_mixer_set_vol(SND_MIXER_SCHN_FRONT_RIGHT,
min, max, r, linear_mapping);
return 0;
}
static int alsa_mixer_get_vol(snd_mixer_selem_channel_id_t channel,
long min, long max, int linear_mapping)
{
long val;
if (linear_mapping) {
snd_mixer_selem_get_playback_volume(mixer_elem, channel, &val);
return ((val - min) / (double)(max - min) * 100) + 0.5;
}
/* alsamixer's volume mapping */
snd_mixer_selem_get_playback_dB(mixer_elem, channel, &val);
double normalized = pow(10, (val - max) / 6000.0);
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
double min_norm = pow(10, (min - max) / 6000.0);
normalized = (normalized - min_norm) / (1 - min_norm);
}
return normalized * 100.0 + 0.5;
}
static int alsa_mixer_get_volume(int *l, int *r)
{
int linear_mapping = !alsa_mixer_mapped_volume;
long min, max;
if (!mixer_elem)
return -1;
snd_mixer_handle_events(mixer);
alsa_mixer_get_range(&min, &max, &linear_mapping);
*l = alsa_mixer_get_vol(SND_MIXER_SCHN_FRONT_LEFT,
min, max, linear_mapping);
*r = alsa_mixer_get_vol(SND_MIXER_SCHN_FRONT_RIGHT,
min, max, linear_mapping);
return 0;
}
static int alsa_mixer_set_channel(const char *val)
{
free(alsa_mixer_element);
alsa_mixer_element = xstrdup(val);
return 0;
}
static int alsa_mixer_get_channel(char **val)
{
if (alsa_mixer_element)
*val = xstrdup(alsa_mixer_element);
return 0;
}
static int alsa_mixer_set_device(const char *val)
{
free(alsa_mixer_device);
alsa_mixer_device = xstrdup(val);
return 0;
}
static int alsa_mixer_get_device(char **val)
{
if (alsa_mixer_device)
*val = xstrdup(alsa_mixer_device);
return 0;
}
static int alsa_mixer_set_mapped_volume(const char *val)
{
alsa_mixer_mapped_volume = atoi(val);
return 0;
}
static int alsa_mixer_get_mapped_volume(char **val)
{
*val = alsa_mixer_mapped_volume ? xstrdup("1") : xstrdup("0");
return 0;
}
const struct mixer_plugin_ops op_mixer_ops = {
.init = alsa_mixer_init,
.exit = alsa_mixer_exit,
.open = alsa_mixer_open,
.close = alsa_mixer_close,
.get_fds.abi_2 = alsa_mixer_get_fds,
.set_volume = alsa_mixer_set_volume,
.get_volume = alsa_mixer_get_volume,
};
const struct mixer_plugin_opt op_mixer_options[] = {
OPT(alsa_mixer, channel),
OPT(alsa_mixer, device),
OPT(alsa_mixer, mapped_volume),
{ NULL },
};