Skip to content

Commit d74f4a6

Browse files
borinearkq
authored andcommitted
aplay: Refactor code related to ALSA mixer
1 parent 9a55882 commit d74f4a6

File tree

4 files changed

+245
-185
lines changed

4 files changed

+245
-185
lines changed

test/test-utils-aplay.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,12 @@ CK_START_TEST(test_play_mixer_setup) {
245245
NULL), -1);
246246
spawn_terminate(&sp_ba_aplay, 500);
247247

248-
char output[8192] = "";
248+
char output[16384] = "";
249249
ck_assert_int_gt(spawn_read(&sp_ba_aplay, NULL, 0, output, sizeof(output)), 0);
250250

251251
#if DEBUG
252252
ck_assert_ptr_ne(strstr(output,
253253
"Opening ALSA mixer: name=bluealsa:DEV=23:45:67:89:AB:CD elem=SCO index=0"), NULL);
254-
ck_assert_ptr_ne(strstr(output,
255-
"Setting up ALSA mixer volume synchronization"), NULL);
256254
#endif
257255

258256
spawn_close(&sp_ba_aplay, NULL);

utils/aplay/alsa-mixer.c

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* BlueALSA - alsa-mixer.c
3-
* Copyright (c) 2016-2021 Arkadiusz Bokowy
3+
* Copyright (c) 2016-2025 Arkadiusz Bokowy
44
*
55
* This file is a part of bluez-alsa.
66
*
@@ -10,53 +10,174 @@
1010

1111
#include "alsa-mixer.h"
1212

13+
#include <assert.h>
14+
#include <math.h>
1315
#include <stdio.h>
1416
#include <string.h>
1517

16-
int alsa_mixer_open(snd_mixer_t **mixer, snd_mixer_elem_t **elem,
17-
const char *dev_name, const char *elem_name, unsigned int elem_idx,
18+
#include "shared/log.h"
19+
20+
static int alsa_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned int mask) {
21+
struct alsa_mixer *mixer = snd_mixer_elem_get_callback_private(elem);
22+
if (mask & SND_CTL_EVENT_MASK_REMOVE)
23+
/* The element has been removed and cannot
24+
* now be used - we must close the mixer. */
25+
return -1;
26+
if (mask & SND_CTL_EVENT_MASK_VALUE)
27+
mixer->event_handler(mixer->event_handler_userdata);
28+
return 0;
29+
}
30+
31+
void alsa_mixer_init(
32+
struct alsa_mixer *mixer,
33+
alsa_mixer_event_handler handler,
34+
void *userdata) {
35+
memset(mixer, 0, sizeof(*mixer));
36+
mixer->event_handler = handler;
37+
mixer->event_handler_userdata = userdata;
38+
}
39+
40+
int alsa_mixer_open(
41+
struct alsa_mixer *mixer,
42+
const char *dev_name,
43+
const char *elem_name,
44+
unsigned int elem_idx,
1845
char **msg) {
1946

20-
snd_mixer_t *_mixer = NULL;
21-
snd_mixer_elem_t *_elem;
2247
char buf[256];
48+
long vmin_db;
2349
int err;
2450

2551
snd_mixer_selem_id_t *id;
2652
snd_mixer_selem_id_alloca(&id);
2753
snd_mixer_selem_id_set_name(id, elem_name);
2854
snd_mixer_selem_id_set_index(id, elem_idx);
2955

30-
if ((err = snd_mixer_open(&_mixer, 0)) != 0) {
56+
if ((err = snd_mixer_open(&mixer->mixer, 0)) != 0) {
3157
snprintf(buf, sizeof(buf), "Open mixer: %s", snd_strerror(err));
3258
goto fail;
3359
}
34-
if ((err = snd_mixer_attach(_mixer, dev_name)) != 0) {
60+
if ((err = snd_mixer_attach(mixer->mixer, dev_name)) != 0) {
3561
snprintf(buf, sizeof(buf), "Attach mixer: %s", snd_strerror(err));
3662
goto fail;
3763
}
38-
if ((err = snd_mixer_selem_register(_mixer, NULL, NULL)) != 0) {
64+
if ((err = snd_mixer_selem_register(mixer->mixer, NULL, NULL)) != 0) {
3965
snprintf(buf, sizeof(buf), "Register mixer class: %s", snd_strerror(err));
4066
goto fail;
4167
}
42-
if ((err = snd_mixer_load(_mixer)) != 0) {
68+
if ((err = snd_mixer_load(mixer->mixer)) != 0) {
4369
snprintf(buf, sizeof(buf), "Load mixer elements: %s", snd_strerror(err));
4470
goto fail;
4571
}
46-
if ((_elem = snd_mixer_find_selem(_mixer, id)) == NULL) {
72+
if ((mixer->elem = snd_mixer_find_selem(mixer->mixer, id)) == NULL) {
4773
snprintf(buf, sizeof(buf), "Mixer element not found");
4874
err = -1;
4975
goto fail;
5076
}
5177

52-
*mixer = _mixer;
53-
*elem = _elem;
78+
mixer->has_mute_switch = snd_mixer_selem_has_playback_switch(mixer->elem);
79+
80+
if ((err = snd_mixer_selem_get_playback_dB_range(mixer->elem,
81+
&vmin_db, &mixer->volume_db_max_value)) != 0)
82+
warn("Couldn't get ALSA mixer playback dB range: %s", snd_strerror(err));
83+
84+
snd_mixer_elem_set_callback(mixer->elem, alsa_mixer_elem_callback);
85+
snd_mixer_elem_set_callback_private(mixer->elem, mixer);
86+
5487
return 0;
5588

5689
fail:
57-
if (_mixer != NULL)
58-
snd_mixer_close(_mixer);
90+
alsa_mixer_close(mixer);
5991
if (msg != NULL)
6092
*msg = strdup(buf);
6193
return err;
6294
}
95+
96+
void alsa_mixer_close(
97+
struct alsa_mixer *mixer) {
98+
if (mixer->mixer != NULL)
99+
snd_mixer_close(mixer->mixer);
100+
mixer->mixer = NULL;
101+
mixer->elem = NULL;
102+
}
103+
104+
int alsa_mixer_get_volume(
105+
const struct alsa_mixer *mixer,
106+
unsigned int vmax,
107+
unsigned int *volume,
108+
bool *muted) {
109+
110+
snd_mixer_elem_t *elem = mixer->elem;
111+
long long volume_db_sum = 0;
112+
bool alsa_muted = true;
113+
114+
snd_mixer_selem_channel_id_t ch;
115+
for (ch = 0; snd_mixer_selem_has_playback_channel(elem, ch) == 1; ch++) {
116+
117+
long ch_volume_db;
118+
int ch_switch = 1;
119+
int err;
120+
121+
if ((err = snd_mixer_selem_get_playback_dB(elem, ch, &ch_volume_db)) != 0) {
122+
error("Couldn't get ALSA mixer playback dB level: %s", snd_strerror(err));
123+
return -1;
124+
}
125+
126+
/* Mute switch is an optional feature for a mixer element. */
127+
if (mixer->has_mute_switch &&
128+
(err = snd_mixer_selem_get_playback_switch(elem, ch, &ch_switch)) != 0) {
129+
error("Couldn't get ALSA mixer playback switch: %s", snd_strerror(err));
130+
return -1;
131+
}
132+
133+
volume_db_sum += ch_volume_db;
134+
/* Normalize volume level so it will not exceed 0.00 dB. */
135+
volume_db_sum -= mixer->volume_db_max_value;
136+
137+
if (ch_switch == 1)
138+
alsa_muted = false;
139+
140+
}
141+
142+
/* Safety check for undefined behavior from
143+
* out-of-bounds dB conversion. */
144+
assert(volume_db_sum <= 0LL);
145+
146+
/* Convert dB to loudness using decibel formula and
147+
* round to the nearest integer. */
148+
*volume = lround(pow(2, (0.01 * volume_db_sum / ch) / 10) * vmax);
149+
150+
/* If mixer element supports playback switch,
151+
* return the actual mute state to the caller. */
152+
if (mixer->has_mute_switch)
153+
*muted = alsa_muted;
154+
155+
return 0;
156+
}
157+
158+
int alsa_mixer_set_volume(
159+
struct alsa_mixer *mixer,
160+
unsigned int vmax,
161+
unsigned int volume,
162+
bool muted) {
163+
164+
/* Convert loudness to dB using decibel formula. */
165+
long db = 10 * log2(1.0 * volume / vmax) * 100;
166+
/* Shift dB level so it will match hardware range. */
167+
db += mixer->volume_db_max_value;
168+
169+
int err;
170+
if ((err = snd_mixer_selem_set_playback_dB_all(mixer->elem, db, 0)) != 0) {
171+
error("Couldn't set ALSA mixer playback dB level: %s", snd_strerror(err));
172+
return -1;
173+
}
174+
175+
/* Mute switch is an optional feature for a mixer element. */
176+
if (mixer->has_mute_switch &&
177+
(err = snd_mixer_selem_set_playback_switch_all(mixer->elem, !muted)) != 0) {
178+
error("Couldn't set ALSA mixer playback mute switch: %s", snd_strerror(err));
179+
return -1;
180+
}
181+
182+
return 0;
183+
}

utils/aplay/alsa-mixer.h

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,84 @@
11
/*
22
* BlueALSA - alsa-mixer.h
3-
* Copyright (c) 2016-2021 Arkadiusz Bokowy
3+
* Copyright (c) 2016-2025 Arkadiusz Bokowy
44
*
55
* This file is a part of bluez-alsa.
66
*
77
* This project is licensed under the terms of the MIT license.
88
*
99
*/
1010

11+
#pragma once
1112
#ifndef BLUEALSA_APLAY_ALSAMIXER_H_
1213
#define BLUEALSA_APLAY_ALSAMIXER_H_
1314

15+
#include <stdbool.h>
16+
1417
#include <alsa/asoundlib.h>
1518

16-
int alsa_mixer_open(snd_mixer_t **mixer, snd_mixer_elem_t **elem,
17-
const char *dev_name, const char *elem_name, unsigned int elem_idx,
19+
typedef void (*alsa_mixer_event_handler)(void *userdata);
20+
21+
struct alsa_mixer {
22+
23+
/* The ALSA mixer handle. */
24+
snd_mixer_t *mixer;
25+
snd_mixer_elem_t *elem;
26+
27+
long volume_db_max_value;
28+
bool has_mute_switch;
29+
30+
alsa_mixer_event_handler event_handler;
31+
void *event_handler_userdata;
32+
33+
};
34+
35+
void alsa_mixer_init(
36+
struct alsa_mixer *mixer,
37+
alsa_mixer_event_handler handler,
38+
void *userdata);
39+
40+
int alsa_mixer_open(
41+
struct alsa_mixer *mixer,
42+
const char *dev_name,
43+
const char *elem_name,
44+
unsigned int elem_idx,
1845
char **msg);
1946

47+
void alsa_mixer_close(
48+
struct alsa_mixer *mixer);
49+
50+
inline static bool alsa_mixer_is_open(
51+
const struct alsa_mixer *mixer) {
52+
return mixer->mixer != NULL && mixer->elem != NULL;
53+
}
54+
55+
inline static int alsa_mixer_poll_descriptors_count(
56+
struct alsa_mixer *mixer) {
57+
return snd_mixer_poll_descriptors_count(mixer->mixer);
58+
}
59+
60+
inline static int alsa_mixer_poll_descriptors(
61+
struct alsa_mixer *mixer,
62+
struct pollfd* pfds,
63+
unsigned int space) {
64+
return snd_mixer_poll_descriptors(mixer->mixer, pfds, space);
65+
}
66+
67+
inline static int alsa_mixer_handle_events(
68+
struct alsa_mixer *mixer) {
69+
return snd_mixer_handle_events(mixer->mixer);
70+
}
71+
72+
int alsa_mixer_get_volume(
73+
const struct alsa_mixer *mixer,
74+
unsigned int vmax,
75+
unsigned int *volume,
76+
bool *muted);
77+
78+
int alsa_mixer_set_volume(
79+
struct alsa_mixer *mixer,
80+
unsigned int vmax,
81+
unsigned int volume,
82+
bool muted);
83+
2084
#endif

0 commit comments

Comments
 (0)