summaryrefslogtreecommitdiff
path: root/src/modules/alsa
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/alsa')
-rw-r--r--src/modules/alsa/90-pulseaudio.rules22
-rw-r--r--src/modules/alsa/alsa-mixer.c370
-rw-r--r--src/modules/alsa/alsa-mixer.h33
-rw-r--r--src/modules/alsa/alsa-sink.c103
-rw-r--r--src/modules/alsa/alsa-source.c90
-rw-r--r--src/modules/alsa/alsa-ucm.c618
-rw-r--r--src/modules/alsa/alsa-ucm.h87
-rw-r--r--src/modules/alsa/alsa-util.c184
-rw-r--r--src/modules/alsa/alsa-util.h11
-rw-r--r--src/modules/alsa/meson.build2
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-speaker.conf3
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf.common3
-rw-r--r--src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf (renamed from src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf)0
-rw-r--r--src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf (renamed from src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf)0
-rw-r--r--src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf (renamed from src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf)12
-rw-r--r--src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf (renamed from src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf)15
-rw-r--r--src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf (renamed from src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf)15
-rw-r--r--src/modules/alsa/mixer/profile-sets/default.conf6
-rw-r--r--src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf (renamed from src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf)5
-rw-r--r--src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf (renamed from src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf)20
-rw-r--r--src/modules/alsa/module-alsa-card.c132
-rw-r--r--src/modules/alsa/module-alsa-sink.c2
-rw-r--r--src/modules/alsa/module-alsa-source.c2
23 files changed, 1302 insertions, 433 deletions
diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules
index d857639..fa43cb8 100644
--- a/src/modules/alsa/90-pulseaudio.rules
+++ b/src/modules/alsa/90-pulseaudio.rules
@@ -109,15 +109,23 @@ ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudi
ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf"
ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf"
-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-5-usb-audio.conf"
+
# ID 1038:12ad is for the 2018 refresh of the Arctis 7.
-# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7
-# configuration).
-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf"
-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf"
-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf"
-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf"
+# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration).
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
+# Lucidsound LS31
+ATTRS{idVendor}=="2f12", ATTRS{idProduct}=="0109", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
+
+# ID 1038:1250 is for the Arctis 5
+# ID 1037:12aa is for the Arctis 5 2019
+# ID 1038:1252 is for the Arctis Pro 2019 edition
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf"
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12aa", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf"
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1252", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf"
ATTRS{idVendor}=="147a", ATTRS{idProduct}=="e055", ENV{PULSE_PROFILE_SET}="cmedia-high-speed-true-hdaudio.conf"
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index cd99a75..d184aec 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -107,13 +107,54 @@ struct description_map {
const char *description;
};
-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) {
+static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) {
+ if (id->index > 0) {
+ snprintf(dst, dst_len, "'%s',%d", id->name, id->index);
+ } else {
+ snprintf(dst, dst_len, "'%s'", id->name);
+ }
+ return dst;
+}
+
+static int alsa_id_decode(const char *src, char *name, int *index) {
+ char *idx, c;
+ int i;
+
+ *index = 0;
+ c = src[0];
+ /* Strip quotes in entries such as 'Speaker',1 or "Speaker",1 */
+ if (c == '\'' || c == '"') {
+ strcpy(name, src + 1);
+ for (i = 0; name[i] != '\0' && name[i] != c; i++);
+ idx = NULL;
+ if (name[i]) {
+ name[i] = '\0';
+ idx = strchr(name + i + 1, ',');
+ }
+ } else {
+ strcpy(name, src);
+ idx = strchr(name, ',');
+ }
+ if (idx == NULL)
+ return 0;
+ *idx = '\0';
+ idx++;
+ if (*idx < '0' || *idx > '9') {
+ pa_log("Element %s: index value is invalid", src);
+ return 1;
+ }
+ *index = atoi(idx);
+ return 0;
+}
+
+pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) {
pa_alsa_jack *jack;
pa_assert(name);
jack = pa_xnew0(pa_alsa_jack, 1);
jack->path = path;
+ jack->mixer_device_name = pa_xstrdup(mixer_device_name);
jack->name = pa_xstrdup(name);
jack->alsa_name = pa_sprintf_malloc("%s Jack", name);
jack->state_unplugged = PA_AVAILABLE_NO;
@@ -132,6 +173,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) {
pa_xfree(jack->alsa_name);
pa_xfree(jack->name);
+ pa_xfree(jack->mixer_device_name);
pa_xfree(jack);
}
@@ -467,7 +509,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {
unsigned short revents = 0;
int err, ret = 0;
- pd = pa_rtpoll_item_get_userdata(i);
+ pd = pa_rtpoll_item_get_work_userdata(i);
pa_assert_fp(pd);
pa_assert_fp(i == pd->poll_item);
@@ -547,8 +589,7 @@ int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer,
pd->poll_item = i;
pd->mixer = mixer;
- pa_rtpoll_item_set_userdata(i, pd);
- pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb);
+ pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb, pd);
return 0;
}
@@ -642,6 +683,7 @@ static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
pa_xfree(db_fix->name);
pa_xfree(db_fix->db_values);
+ pa_xfree(db_fix->key);
pa_xfree(db_fix);
}
@@ -657,7 +699,7 @@ static void element_free(pa_alsa_element *e) {
if (e->db_fix)
decibel_fix_free(e->db_fix);
- pa_xfree(e->alsa_name);
+ pa_xfree(e->alsa_id.name);
pa_xfree(e);
}
@@ -699,6 +741,12 @@ void pa_alsa_path_set_free(pa_alsa_path_set *ps) {
pa_xfree(ps);
}
+int pa_alsa_path_set_is_empty(pa_alsa_path_set *ps) {
+ if (ps && !pa_hashmap_isempty(ps->paths))
+ return 0;
+ return 1;
+}
+
static long to_alsa_dB(pa_volume_t v) {
return lround(pa_sw_volume_to_dB(v) * 100.0);
}
@@ -718,11 +766,11 @@ static pa_volume_t from_alsa_volume(long v, long min, long max) {
return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min));
}
-#define SELEM_INIT(sid, name) \
- do { \
- snd_mixer_selem_id_alloca(&(sid)); \
- snd_mixer_selem_id_set_name((sid), (name)); \
- snd_mixer_selem_id_set_index((sid), 0); \
+#define SELEM_INIT(sid, aid) \
+ do { \
+ snd_mixer_selem_id_alloca(&(sid)); \
+ snd_mixer_selem_id_set_name((sid), (aid)->name); \
+ snd_mixer_selem_id_set_index((sid), (aid)->index); \
} while(false)
static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
@@ -730,6 +778,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
snd_mixer_elem_t *me;
snd_mixer_selem_channel_id_t c;
pa_channel_position_mask_t mask = 0;
+ char buf[64];
unsigned k;
pa_assert(m);
@@ -737,9 +786,10 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
pa_assert(cm);
pa_assert(v);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -764,14 +814,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
if (value < e->db_fix->min_step) {
value = e->db_fix->min_step;
snd_mixer_selem_set_playback_volume(me, c, value);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
- "Volume reset to %0.2f dB.", e->alsa_name, c,
+ "Volume reset to %0.2f dB.", buf, c,
e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
} else if (value > e->db_fix->max_step) {
value = e->db_fix->max_step;
snd_mixer_selem_set_playback_volume(me, c, value);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
- "Volume reset to %0.2f dB.", e->alsa_name, c,
+ "Volume reset to %0.2f dB.", buf, c,
e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
}
@@ -792,14 +844,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
if (value < e->db_fix->min_step) {
value = e->db_fix->min_step;
snd_mixer_selem_set_capture_volume(me, c, value);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
- "Volume reset to %0.2f dB.", e->alsa_name, c,
+ "Volume reset to %0.2f dB.", buf, c,
e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
} else if (value > e->db_fix->max_step) {
value = e->db_fix->max_step;
snd_mixer_selem_set_capture_volume(me, c, value);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
- "Volume reset to %0.2f dB.", e->alsa_name, c,
+ "Volume reset to %0.2f dB.", buf, c,
e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
}
@@ -897,14 +951,16 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) {
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *me;
snd_mixer_selem_channel_id_t c;
+ char buf[64];
pa_assert(m);
pa_assert(e);
pa_assert(b);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1058,6 +1114,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
snd_mixer_elem_t *me;
snd_mixer_selem_channel_id_t c;
pa_channel_position_mask_t mask = 0;
+ char buf[64];
unsigned k;
pa_assert(m);
@@ -1066,9 +1123,10 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
pa_assert(v);
pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1251,14 +1309,16 @@ int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_ma
static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
snd_mixer_elem_t *me;
snd_mixer_selem_id_t *sid;
+ char buf[64];
int r;
pa_assert(m);
pa_assert(e);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1267,8 +1327,10 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
else
r = snd_mixer_selem_set_capture_switch_all(me, b);
- if (r < 0)
- pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+ if (r < 0) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
+ }
return r;
}
@@ -1303,13 +1365,15 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
int r = 0;
long volume = -1;
bool volume_set = false;
+ char buf[64];
pa_assert(m);
pa_assert(e);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1352,8 +1416,10 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
r = snd_mixer_selem_set_capture_dB_all(me, 0, -1);
}
- if (r < 0)
- pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+ if (r < 0) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno));
+ }
return r;
}
@@ -1531,6 +1597,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
int r;
bool is_mono;
pa_channel_position_t p;
+ char buf[64];
if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
if (!snd_mixer_selem_has_playback_volume(me)) {
@@ -1556,29 +1623,33 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
if (r < 0) {
- pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r));
return false;
}
if (e->min_volume >= e->max_volume) {
- pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.",
- e->min_volume, e->max_volume);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.",
+ buf, e->min_volume, e->max_volume);
return false;
}
if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
- e->constant_volume, e->alsa_name, e->min_volume, e->max_volume);
+ e->constant_volume, buf, e->min_volume, e->max_volume);
return false;
}
if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) {
- pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
- "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
- e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
+ "real hardware range (%li-%li). Disabling the decibel fix.", buf,
+ e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume);
- decibel_fix_free(e->db_fix);
- e->db_fix = NULL;
+ decibel_fix_free(e->db_fix);
+ e->db_fix = NULL;
}
if (e->db_fix) {
@@ -1599,19 +1670,22 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
long max_dB_checked = 0;
if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) {
- pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume);
return false;
}
if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) {
- pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume);
return false;
}
if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
"doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
- "%0.2f dB at level %li.", e->alsa_name, min_dB / 100.0, max_dB / 100.0,
+ "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0,
min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume);
return false;
}
@@ -1630,11 +1704,12 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
}
if (e->volume_limit >= 0) {
- if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
+ if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
"%li-%li. The volume limit is ignored.",
- e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
- else {
+ buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
+ } else {
e->max_volume = e->volume_limit;
if (e->has_dB) {
@@ -1642,7 +1717,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
e->db_fix->max_step = e->max_volume;
e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
} else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) {
- pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r));
e->has_dB = false;
} else
e->max_dB = ((double) max_dB) / 100.0;
@@ -1682,7 +1758,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
}
if (e->n_channels <= 0) {
- pa_log_warn("Volume element %s with no channels?", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Volume element %s with no channels?", buf);
return false;
} else if (e->n_channels > 2) {
/* FIXME: In some places code like this is used:
@@ -1696,7 +1773,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
* Since the array size is fixed at 2, we obviously
* don't support elements with more than two
* channels... */
- pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels);
return false;
}
@@ -1734,7 +1812,7 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
pa_assert(e);
pa_assert(e->path);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
@@ -1837,7 +1915,7 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m)
j->append_pcm_to_name = false;
}
- has_control = pa_alsa_mixer_find(m, j->alsa_name, 0) != NULL;
+ has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL;
pa_alsa_jack_set_has_control(j, has_control);
if (j->has_control) {
@@ -1853,8 +1931,10 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m)
return 0;
}
-static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool prefixed) {
+pa_alsa_element * pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed) {
pa_alsa_element *e;
+ char *name;
+ int index;
pa_assert(p);
pa_assert(section);
@@ -1870,16 +1950,22 @@ static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool p
if (strchr(section, ':'))
return NULL;
- if (p->last_element && pa_streq(p->last_element->alsa_name, section))
+ name = alloca(strlen(section) + 1);
+ if (alsa_id_decode(section, name, &index))
+ return NULL;
+
+ if (p->last_element && pa_streq(p->last_element->alsa_id.name, name) &&
+ p->last_element->alsa_id.index == index)
return p->last_element;
PA_LLIST_FOREACH(e, p->elements)
- if (pa_streq(e->alsa_name, section))
+ if (pa_streq(e->alsa_id.name, name) && e->alsa_id.index == index)
goto finish;
e = pa_xnew0(pa_alsa_element, 1);
e->path = p;
- e->alsa_name = pa_xstrdup(section);
+ e->alsa_id.name = pa_xstrdup(name);
+ e->alsa_id.index = index;
e->direction = p->direction;
e->volume_limit = -1;
@@ -1904,7 +1990,7 @@ static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
if (pa_streq(j->name, section))
goto finish;
- j = pa_alsa_jack_new(p, section);
+ j = pa_alsa_jack_new(p, NULL, section);
PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j);
finish:
@@ -1913,10 +1999,12 @@ finish:
}
static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
- char *en;
+ char *en, *name;
const char *on;
pa_alsa_option *o;
pa_alsa_element *e;
+ size_t len;
+ int index;
if (!pa_startswith(section, "Option "))
return NULL;
@@ -1927,18 +2015,25 @@ static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
if (!(on = strchr(section, ':')))
return NULL;
- en = pa_xstrndup(section, on - section);
+ len = on - section;
+ en = alloca(len + 1);
+ strncpy(en, section, len);
+ en[len] = '\0';
+
+ name = alloca(strlen(en) + 1);
+ if (alsa_id_decode(en, name, &index))
+ return NULL;
+
on++;
if (p->last_option &&
- pa_streq(p->last_option->element->alsa_name, en) &&
+ pa_streq(p->last_option->element->alsa_id.name, name) &&
+ p->last_option->element->alsa_id.index == index &&
pa_streq(p->last_option->alsa_name, on)) {
- pa_xfree(en);
return p->last_option;
}
- pa_assert_se(e = element_get(p, en, false));
- pa_xfree(en);
+ pa_assert_se(e = pa_alsa_element_get(p, en, false));
PA_LLIST_FOREACH(o, e->options)
if (pa_streq(o->alsa_name, on))
@@ -1967,7 +2062,7 @@ static int element_parse_switch(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] Switch makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -1998,7 +2093,7 @@ static int element_parse_volume(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] Volume makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -2034,7 +2129,7 @@ static int element_parse_enumeration(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] Enumeration makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -2126,7 +2221,7 @@ static int element_parse_required(pa_config_parser_state *state) {
p = state->userdata;
- e = element_get(p, state->section, true);
+ e = pa_alsa_element_get(p, state->section, true);
o = option_get(p, state->section);
j = jack_get(p, state->section);
if (!e && !o && !j) {
@@ -2192,7 +2287,7 @@ static int element_parse_direction(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -2218,7 +2313,7 @@ static int element_parse_direction_try_other(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -2241,7 +2336,7 @@ static int element_parse_volume_limit(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] volume-limit makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -2299,7 +2394,7 @@ static int element_parse_override_map(pa_config_parser_state *state) {
p = state->userdata;
- if (!(e = element_get(p, state->section, true))) {
+ if (!(e = pa_alsa_element_get(p, state->section, true))) {
pa_log("[%s:%u] Override map makes no sense in '%s'", state->filename, state->lineno, state->section);
return -1;
}
@@ -2394,14 +2489,16 @@ static int jack_parse_append_pcm_to_name(pa_config_parser_state *state) {
static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *me;
+ char buf[64];
int r;
pa_assert(e);
pa_assert(m);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -2412,14 +2509,18 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
else
r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
- if (r < 0)
- pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+ if (r < 0) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
+ }
} else {
pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
- if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0)
- pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+ if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno));
+ }
}
return r;
@@ -2463,6 +2564,7 @@ static int option_verify(pa_alsa_option *o) {
{ "output-speaker", N_("Speaker") },
{ "output-headphones", N_("Headphones") }
};
+ char buf[64];
pa_assert(o);
@@ -2473,14 +2575,16 @@ static int option_verify(pa_alsa_option *o) {
if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
- pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name);
+ alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
+ pa_log("Element %s of option %s not set for select.", buf, o->name);
return -1;
}
if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
!pa_streq(o->alsa_name, "on") &&
!pa_streq(o->alsa_name, "off")) {
- pa_log("Switch %s options need be named off or on ", o->element->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
+ pa_log("Switch %s options need be named off or on ", buf);
return -1;
}
@@ -2496,6 +2600,7 @@ static int option_verify(pa_alsa_option *o) {
static int element_verify(pa_alsa_element *e) {
pa_alsa_option *o;
+ char buf[64];
pa_assert(e);
@@ -2504,12 +2609,14 @@ static int element_verify(pa_alsa_element *e) {
(e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
(e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
(e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
- pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log("Element %s cannot be required and absent at the same time.", buf);
return -1;
}
if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
- pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log("Element %s cannot set select for both switch and enumeration.", buf);
return -1;
}
@@ -2661,9 +2768,15 @@ fail:
pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) {
pa_alsa_path *p;
pa_alsa_element *e;
+ char *name;
+ int index;
pa_assert(element);
+ name = alloca(strlen(element) + 1);
+ if (alsa_id_decode(element, name, &index))
+ return NULL;
+
p = pa_xnew0(pa_alsa_path, 1);
p->name = pa_xstrdup(element);
p->direction = direction;
@@ -2671,7 +2784,8 @@ pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t d
e = pa_xnew0(pa_alsa_element, 1);
e->path = p;
- e->alsa_name = pa_xstrdup(element);
+ e->alsa_id.name = pa_xstrdup(name);
+ e->alsa_id.index = index;
e->direction = direction;
e->volume_limit = -1;
@@ -2821,6 +2935,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
pa_channel_position_t t;
pa_channel_position_mask_t path_volume_channels = 0;
+ char buf[64];
pa_assert(p);
pa_assert(m);
@@ -2844,12 +2959,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
}
PA_LLIST_FOREACH(e, p->elements) {
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
if (element_probe(e, m) < 0) {
p->supported = false;
- pa_log_debug("Probe of element '%s' failed.", e->alsa_name);
+ pa_log_debug("Probe of element %s failed.", buf);
return -1;
}
- pa_log_debug("Probe of element '%s' succeeded (volume=%d, switch=%d, enumeration=%d).", e->alsa_name, e->volume_use, e->switch_use, e->enumeration_use);
+ pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use);
if (ignore_dB)
e->has_dB = false;
@@ -2885,13 +3001,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
* which cannot do dB volumes, so we we need
* to 'neutralize' this slider */
e->volume_use = PA_ALSA_VOLUME_ZERO;
- pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name);
+ pa_log_info("Zeroing volume of %s on path '%s'", buf, p->name);
}
}
} else if (p->has_volume) {
/* We can't use this volume, so let's ignore it */
e->volume_use = PA_ALSA_VOLUME_IGNORE;
- pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name);
+ pa_log_info("Ignoring volume of %s on path '%s' (missing dB info)", buf, p->name);
}
p->has_volume = true;
}
@@ -2955,11 +3071,14 @@ void pa_alsa_option_dump(pa_alsa_option *o) {
}
void pa_alsa_element_dump(pa_alsa_element *e) {
+ char buf[64];
+
pa_alsa_option *o;
pa_assert(e);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s",
- e->alsa_name,
+ buf,
e->direction,
e->switch_use,
e->volume_use,
@@ -3009,14 +3128,16 @@ void pa_alsa_path_dump(pa_alsa_path *p) {
static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *me;
+ char buf[64];
pa_assert(e);
pa_assert(m);
pa_assert(cb);
- SELEM_INIT(sid, e->alsa_name);
+ SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return;
}
@@ -3082,6 +3203,8 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
char **pn = NULL, **en = NULL, **ie;
pa_alsa_decibel_fix *db_fix;
void *state, *state2;
+ char name[64];
+ int index;
pa_assert(m);
pa_assert(m->profile_set);
@@ -3161,9 +3284,18 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
if (je == ie)
continue;
+ if (strlen(*je) + 1 >= sizeof(name)) {
+ pa_log("Element identifier %s is too long!", *je);
+ continue;
+ }
+
+ if (alsa_id_decode(*je, name, &index))
+ continue;
+
e = pa_xnew0(pa_alsa_element, 1);
e->path = p;
- e->alsa_name = pa_xstrdup(*je);
+ e->alsa_id.name = pa_xstrdup(name);
+ e->alsa_id.index = index;
e->direction = direction;
e->required_absent = PA_ALSA_REQUIRED_ANY;
e->volume_limit = -1;
@@ -3184,7 +3316,8 @@ finish:
pa_alsa_element *e;
PA_LLIST_FOREACH(e, p->elements) {
- if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
+ if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_id.name) &&
+ db_fix->index == e->alsa_id.index) {
/* The profile set that contains the dB fix may be freed
* before the element, so we have to copy the dB fix
* object. */
@@ -3257,6 +3390,8 @@ static bool enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_o
* Compares two elements to see if a is a subset of b
*/
static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) {
+ char buf[64];
+
pa_assert(a);
pa_assert(b);
pa_assert(m);
@@ -3294,9 +3429,10 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *me;
- SELEM_INIT(sid, a->alsa_name);
+ SELEM_INIT(sid, &a->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- pa_log_warn("Element %s seems to have disappeared.", a->alsa_name);
+ alsa_id_str(buf, sizeof(buf), &a->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
return false;
}
@@ -3326,8 +3462,9 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
return false;
for (s = 0; s <= SND_MIXER_SCHN_LAST; s++)
if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) {
+ alsa_id_str(buf, sizeof(buf), &a->alsa_id);
pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d",
- a->alsa_name, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
+ buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
return false;
}
}
@@ -3424,7 +3561,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
break;
PA_LLIST_FOREACH(eb, p2->elements) {
- if (pa_streq(ea->alsa_name, eb->alsa_name)) {
+ if (pa_streq(ea->alsa_id.name, eb->alsa_id.name) &&
+ ea->alsa_id.index == eb->alsa_id.index) {
found_matching_element = true;
is_subset = element_is_subset(ea, eb, m);
break;
@@ -3601,22 +3739,30 @@ static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
return p;
}
-static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
+static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *alsa_id) {
pa_alsa_decibel_fix *db_fix;
+ char *name;
+ int index;
- if (!pa_startswith(name, "DecibelFix "))
+ if (!pa_startswith(alsa_id, "DecibelFix "))
return NULL;
- name += 11;
+ alsa_id += 11;
- if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
+ if ((db_fix = pa_hashmap_get(ps->decibel_fixes, alsa_id)))
return db_fix;
+ name = alloca(strlen(alsa_id) + 1);
+ if (alsa_id_decode(alsa_id, name, &index))
+ return NULL;
+
db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
db_fix->profile_set = ps;
db_fix->name = pa_xstrdup(name);
+ db_fix->index = index;
+ db_fix->key = pa_xstrdup(alsa_id);
- pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
+ pa_hashmap_put(ps->decibel_fixes, db_fix->key, db_fix);
return db_fix;
}
@@ -3840,6 +3986,24 @@ static int mapping_parse_fallback(pa_config_parser_state *state) {
return 0;
}
+static int mapping_parse_intended_roles(pa_config_parser_state *state) {
+ pa_alsa_profile_set *ps;
+ pa_alsa_mapping *m;
+
+ pa_assert(state);
+
+ ps = state->userdata;
+
+ if (!(m = pa_alsa_mapping_get(ps, state->section))) {
+ pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section);
+ return -1;
+ }
+
+ pa_proplist_sets(m->proplist, PA_PROP_DEVICE_INTENDED_ROLES, state->rvalue);
+
+ return 0;
+}
+
static int profile_parse_mappings(pa_config_parser_state *state) {
pa_alsa_profile_set *ps;
@@ -4004,7 +4168,8 @@ fail:
}
static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
- pa_alsa_direction_t direction, pa_hashmap *used_paths) {
+ pa_alsa_direction_t direction, pa_hashmap *used_paths,
+ pa_hashmap *mixers) {
pa_alsa_path *p;
void *state;
@@ -4029,7 +4194,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
pa_assert(pcm_handle);
- mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL);
+ mixer_handle = pa_alsa_open_mixer_for_pcm(mixers, pcm_handle, true);
if (!mixer_handle) {
/* Cannot open mixer, remove all entries */
pa_hashmap_remove_all(ps->paths);
@@ -4047,9 +4212,6 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
path_set_condense(ps, mixer_handle);
path_set_make_path_descriptions_unique(ps);
- if (mixer_handle)
- snd_mixer_close(mixer_handle);
-
PA_HASHMAP_FOREACH(p, ps->paths, state)
pa_hashmap_put(used_paths, p, p);
@@ -4431,6 +4593,7 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
{ "element-output", mapping_parse_element, NULL, NULL },
{ "direction", mapping_parse_direction, NULL, NULL },
{ "exact-channels", mapping_parse_exact_channels, NULL, NULL },
+ { "intended-roles", mapping_parse_intended_roles, NULL, NULL },
/* Shared by [Mapping ...] and [Profile ...] */
{ "description", mapping_parse_description, NULL, NULL },
@@ -4628,6 +4791,7 @@ static void mapping_query_hw_device(pa_alsa_mapping *mapping, snd_pcm_t *pcm) {
void pa_alsa_profile_set_probe(
pa_alsa_profile_set *ps,
+ pa_hashmap *mixers,
const char *dev_id,
const pa_sample_spec *ss,
unsigned default_n_fragments,
@@ -4757,14 +4921,14 @@ void pa_alsa_profile_set_probe(
PA_IDXSET_FOREACH(m, p->output_mappings, idx)
if (m->output_pcm) {
found_output |= !p->fallback_output;
- mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths);
+ mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths, mixers);
}
if (p->input_mappings)
PA_IDXSET_FOREACH(m, p->input_mappings, idx)
if (m->input_pcm) {
found_input |= !p->fallback_input;
- mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths);
+ mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths, mixers);
}
}
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 65b0711..df739cc 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -32,8 +32,10 @@
#include <pulsecore/rtpoll.h>
typedef struct pa_alsa_fdlist pa_alsa_fdlist;
+typedef struct pa_alsa_mixer pa_alsa_mixer;
typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
typedef struct pa_alsa_setting pa_alsa_setting;
+typedef struct pa_alsa_mixer_id pa_alsa_mixer_id;
typedef struct pa_alsa_option pa_alsa_option;
typedef struct pa_alsa_element pa_alsa_element;
typedef struct pa_alsa_jack pa_alsa_jack;
@@ -97,6 +99,20 @@ struct pa_alsa_setting {
unsigned priority;
};
+/* An entry for one ALSA mixer */
+struct pa_alsa_mixer {
+ snd_mixer_t *mixer_handle;
+ int card_index;
+ pa_alsa_fdlist *fdl;
+ bool used_for_probe_only:1;
+};
+
+/* ALSA mixer element identifier */
+struct pa_alsa_mixer_id {
+ char *name;
+ int index;
+};
+
/* An option belongs to an element and refers to one enumeration item
* of the element is an enumeration item, or a switch status if the
* element is a switch item. */
@@ -123,7 +139,7 @@ struct pa_alsa_element {
pa_alsa_path *path;
PA_LLIST_FIELDS(pa_alsa_element);
- char *alsa_name;
+ struct pa_alsa_mixer_id alsa_id;
pa_alsa_direction_t direction;
pa_alsa_switch_use_t switch_use;
@@ -158,6 +174,9 @@ struct pa_alsa_jack {
pa_alsa_path *path;
PA_LLIST_FIELDS(pa_alsa_jack);
+ snd_mixer_t *mixer_handle;
+ char *mixer_device_name;
+
char *name; /* E g "Headphone" */
char *alsa_name; /* E g "Headphone Jack" */
bool has_control; /* is the jack itself present? */
@@ -175,7 +194,7 @@ struct pa_alsa_jack {
bool append_pcm_to_name;
};
-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name);
+pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name);
void pa_alsa_jack_free(pa_alsa_jack *jack);
void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control);
void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
@@ -194,6 +213,7 @@ struct pa_alsa_path {
char *description;
unsigned priority;
bool autodetect_eld_device;
+ pa_alsa_mixer *eld_mixer_handle;
int eld_device;
pa_proplist *proplist;
@@ -237,6 +257,7 @@ void pa_alsa_element_dump(pa_alsa_element *e);
pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction);
pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction);
+pa_alsa_element *pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed);
int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB);
void pa_alsa_path_dump(pa_alsa_path *p);
int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
@@ -251,6 +272,7 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
void pa_alsa_path_set_dump(pa_alsa_path_set *s);
void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata);
void pa_alsa_path_set_free(pa_alsa_path_set *s);
+int pa_alsa_path_set_is_empty(pa_alsa_path_set *s);
struct pa_alsa_mapping {
pa_alsa_profile_set *profile_set;
@@ -315,9 +337,12 @@ struct pa_alsa_profile {
};
struct pa_alsa_decibel_fix {
+ char *key;
+
pa_alsa_profile_set *profile_set;
char *name; /* Alsa volume element name. */
+ int index; /* Alsa volume element index. */
long min_step;
long max_step;
@@ -348,13 +373,11 @@ void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix);
pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name);
pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus);
-void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
+void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);
void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s);
-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device);
-
pa_alsa_fdlist *pa_alsa_fdlist_new(void);
void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl);
int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m);
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 4b46708..042d4df 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -105,6 +105,7 @@ struct userdata {
char *paths_dir;
pa_alsa_fdlist *mixer_fdl;
pa_alsa_mixer_pdata *mixer_pd;
+ pa_hashmap *mixers;
snd_mixer_t *mixer_handle;
pa_alsa_path_set *mixer_path_set;
pa_alsa_path *mixer_path;
@@ -1266,7 +1267,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) {
/* port may be NULL, because if we use a synthesized mixer path, then the
* sink has no ports. */
- if (port) {
+ if (port && !u->ucm_context) {
pa_alsa_port_data *data;
data = PA_DEVICE_PORT_DATA(port);
@@ -1598,7 +1599,7 @@ static void sink_set_mute_cb(pa_sink *s) {
static void mixer_volume_init(struct userdata *u) {
pa_assert(u);
- if (!u->mixer_path->has_volume) {
+ if (!u->mixer_path || !u->mixer_path->has_volume) {
pa_sink_set_write_volume_callback(u->sink, NULL);
pa_sink_set_get_volume_callback(u->sink, NULL);
pa_sink_set_set_volume_callback(u->sink, NULL);
@@ -1633,7 +1634,7 @@ static void mixer_volume_init(struct userdata *u) {
pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
}
- if (!u->mixer_path->has_mute) {
+ if (!u->mixer_path || !u->mixer_path->has_mute) {
pa_sink_set_get_mute_callback(u->sink, NULL);
pa_sink_set_set_mute_callback(u->sink, NULL);
pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@@ -1646,11 +1647,21 @@ static void mixer_volume_init(struct userdata *u) {
static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) {
struct userdata *u = s->userdata;
+ pa_alsa_ucm_port_data *data;
pa_assert(u);
pa_assert(p);
pa_assert(u->ucm_context);
+ data = PA_DEVICE_PORT_DATA(p);
+ pa_assert_se(u->mixer_path = data->path);
+ mixer_volume_init(u);
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME)
+ pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_SYNC_MIXER, p, 0, NULL);
+ else
+ sync_mixer(u, p);
+
return pa_alsa_ucm_set_port(u->ucm_context, p, true);
}
@@ -1661,6 +1672,7 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
pa_assert(u);
pa_assert(p);
pa_assert(u->mixer_handle);
+ pa_assert(!u->ucm_context);
data = PA_DEVICE_PORT_DATA(p);
pa_assert_se(u->mixer_path = data->path);
@@ -2071,10 +2083,24 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de
}
static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) {
+ const char *mdev;
+
if (!mapping && !element)
return;
- if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ if (!element && mapping && pa_alsa_path_set_is_empty(mapping->output_path_set))
+ return;
+
+ u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
+ NULL, (pa_free_cb_t) pa_alsa_mixer_free);
+
+ mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device");
+ if (mdev) {
+ u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, true);
+ } else {
+ u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->mixers, u->pcm_handle, true);
+ }
+ if (!u->mixer_handle) {
pa_log_info("Failed to find a working mixer device.");
return;
}
@@ -2089,8 +2115,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
pa_alsa_path_dump(u->mixer_path);
- } else if (!(u->mixer_path_set = mapping->output_path_set))
- goto fail;
+ } else {
+ u->mixer_path_set = mapping->output_path_set;
+ }
return;
@@ -2101,10 +2128,9 @@ fail:
u->mixer_path = NULL;
}
- if (u->mixer_handle) {
- snd_mixer_close(u->mixer_handle);
- u->mixer_handle = NULL;
- }
+ u->mixer_handle = NULL;
+ pa_hashmap_free(u->mixers);
+ u->mixers = NULL;
}
static int setup_mixer(struct userdata *u, bool ignore_dB) {
@@ -2116,16 +2142,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
return 0;
if (u->sink->active_port) {
- pa_alsa_port_data *data;
+ if (!u->ucm_context) {
+ pa_alsa_port_data *data;
+
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
- /* We have a list of supported paths, so let's activate the
- * one that has been chosen as active */
+ data = PA_DEVICE_PORT_DATA(u->sink->active_port);
+ u->mixer_path = data->path;
- data = PA_DEVICE_PORT_DATA(u->sink->active_port);
- u->mixer_path = data->path;
+ pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
+ } else {
+ pa_alsa_ucm_port_data *data;
+
+ /* First activate the port on the UCM side */
+ if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
+ return -1;
- pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
+ data = PA_DEVICE_PORT_DATA(u->sink->active_port);
+ /* Now activate volume controls, if any */
+ if (data->path) {
+ u->mixer_path = data->path;
+ pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->sink->muted);
+ }
+ }
} else {
if (!u->mixer_path && u->mixer_path_set)
@@ -2135,7 +2176,6 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
/* Hmm, we have only a single path, then let's activate it */
pa_alsa_path_select(u->mixer_path, u->mixer_path->settings, u->mixer_handle, u->sink->muted);
-
} else
return 0;
}
@@ -2466,8 +2506,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
- if (!u->ucm_context)
- find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_sink_new_data_init(&data);
data.driver = driver;
@@ -2523,10 +2562,14 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
goto fail;
}
- if (u->ucm_context)
- pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card);
- else if (u->mixer_path_set)
- pa_alsa_add_ports(&data, u->mixer_path_set, card);
+ if (u->ucm_context) {
+ pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card, u->pcm_handle, ignore_dB);
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+ } else {
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+ if (u->mixer_path_set)
+ pa_alsa_add_ports(&data, u->mixer_path_set, card);
+ }
u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE | PA_SINK_LATENCY | (u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0) |
(set_formats ? PA_SINK_SET_FORMATS : 0));
@@ -2598,10 +2641,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
if (update_sw_params(u, false) < 0)
goto fail;
- if (u->ucm_context) {
- if (u->sink->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
- goto fail;
- } else if (setup_mixer(u, ignore_dB) < 0)
+ if (setup_mixer(u, ignore_dB) < 0)
goto fail;
pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2666,7 +2706,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
* pa_sink_suspend() between pa_sink_new() and pa_sink_put() would
* otherwise work, but currently pa_sink_suspend() will crash if
* pa_sink_put() hasn't been called. */
- if (u->sink->active_port) {
+ if (u->sink->active_port && !u->ucm_context) {
pa_alsa_port_data *port_data;
port_data = PA_DEVICE_PORT_DATA(u->sink->active_port);
@@ -2725,11 +2765,12 @@ static void userdata_free(struct userdata *u) {
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
- if (u->mixer_path && !u->mixer_path_set)
+ /* Only free the mixer_path if the sink owns it */
+ if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
pa_alsa_path_free(u->mixer_path);
- if (u->mixer_handle)
- snd_mixer_close(u->mixer_handle);
+ if (u->mixers)
+ pa_hashmap_free(u->mixers);
if (u->smoother)
pa_smoother_free(u->smoother);
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index c8bf649..104de4e 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -93,6 +93,7 @@ struct userdata {
char *paths_dir;
pa_alsa_fdlist *mixer_fdl;
pa_alsa_mixer_pdata *mixer_pd;
+ pa_hashmap *mixers;
snd_mixer_t *mixer_handle;
pa_alsa_path_set *mixer_path_set;
pa_alsa_path *mixer_path;
@@ -1137,7 +1138,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) {
/* port may be NULL, because if we use a synthesized mixer path, then the
* source has no ports. */
- if (port) {
+ if (port && !u->ucm_context) {
pa_alsa_port_data *data;
data = PA_DEVICE_PORT_DATA(port);
@@ -1469,7 +1470,7 @@ static void source_set_mute_cb(pa_source *s) {
static void mixer_volume_init(struct userdata *u) {
pa_assert(u);
- if (!u->mixer_path->has_volume) {
+ if (!u->mixer_path || !u->mixer_path->has_volume) {
pa_source_set_write_volume_callback(u->source, NULL);
pa_source_set_get_volume_callback(u->source, NULL);
pa_source_set_set_volume_callback(u->source, NULL);
@@ -1504,7 +1505,7 @@ static void mixer_volume_init(struct userdata *u) {
pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
}
- if (!u->mixer_path->has_mute) {
+ if (!u->mixer_path || !u->mixer_path->has_mute) {
pa_source_set_get_mute_callback(u->source, NULL);
pa_source_set_set_mute_callback(u->source, NULL);
pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@@ -1517,11 +1518,21 @@ static void mixer_volume_init(struct userdata *u) {
static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) {
struct userdata *u = s->userdata;
+ pa_alsa_ucm_port_data *data;
pa_assert(u);
pa_assert(p);
pa_assert(u->ucm_context);
+ data = PA_DEVICE_PORT_DATA(p);
+ pa_assert_se(u->mixer_path = data->path);
+ mixer_volume_init(u);
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME)
+ pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_SYNC_MIXER, p, 0, NULL);
+ else
+ sync_mixer(u, p);
+
return pa_alsa_ucm_set_port(u->ucm_context, p, false);
}
@@ -1532,6 +1543,7 @@ static int source_set_port_cb(pa_source *s, pa_device_port *p) {
pa_assert(u);
pa_assert(p);
pa_assert(u->mixer_handle);
+ pa_assert(!u->ucm_context);
data = PA_DEVICE_PORT_DATA(p);
pa_assert_se(u->mixer_path = data->path);
@@ -1777,10 +1789,24 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char
}
static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) {
+ const char *mdev;
+
if (!mapping && !element)
return;
- if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ if (!element && mapping && pa_alsa_path_set_is_empty(mapping->input_path_set))
+ return;
+
+ u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
+ NULL, (pa_free_cb_t) pa_alsa_mixer_free);
+
+ mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device");
+ if (mdev) {
+ u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, false);
+ } else {
+ u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->mixers, u->pcm_handle, false);
+ }
+ if (!u->mixer_handle) {
pa_log_info("Failed to find a working mixer device.");
return;
}
@@ -1795,8 +1821,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
pa_alsa_path_dump(u->mixer_path);
- } else if (!(u->mixer_path_set = mapping->input_path_set))
- goto fail;
+ } else {
+ u->mixer_path_set = mapping->input_path_set;
+ }
return;
@@ -1807,10 +1834,9 @@ fail:
u->mixer_path = NULL;
}
- if (u->mixer_handle) {
- snd_mixer_close(u->mixer_handle);
- u->mixer_handle = NULL;
- }
+ u->mixer_handle = NULL;
+ pa_hashmap_free(u->mixers);
+ u->mixers = NULL;
}
static int setup_mixer(struct userdata *u, bool ignore_dB) {
@@ -1822,16 +1848,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
return 0;
if (u->source->active_port) {
- pa_alsa_port_data *data;
+ if (!u->ucm_context) {
+ pa_alsa_port_data *data;
- /* We have a list of supported paths, so let's activate the
- * one that has been chosen as active */
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
- data = PA_DEVICE_PORT_DATA(u->source->active_port);
- u->mixer_path = data->path;
+ data = PA_DEVICE_PORT_DATA(u->source->active_port);
+ u->mixer_path = data->path;
- pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
+ pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
+ } else {
+ pa_alsa_ucm_port_data *data;
+ /* First activate the port on the UCM side */
+ if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
+ return -1;
+
+ data = PA_DEVICE_PORT_DATA(u->source->active_port);
+
+ /* Now activate volume controls, if any */
+ if (data->path) {
+ u->mixer_path = data->path;
+ pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->source->muted);
+ }
+ }
} else {
if (!u->mixer_path && u->mixer_path_set)
@@ -2152,8 +2193,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
- if (!u->ucm_context)
- find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_source_new_data_init(&data);
data.driver = driver;
@@ -2210,7 +2250,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
}
if (u->ucm_context)
- pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card);
+ pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card, u->pcm_handle, ignore_dB);
else if (u->mixer_path_set)
pa_alsa_add_ports(&data, u->mixer_path_set, card);
@@ -2276,10 +2316,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
if (update_sw_params(u) < 0)
goto fail;
- if (u->ucm_context) {
- if (u->source->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
- goto fail;
- } else if (setup_mixer(u, ignore_dB) < 0)
+ if (setup_mixer(u, ignore_dB) < 0)
goto fail;
pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2368,11 +2405,12 @@ static void userdata_free(struct userdata *u) {
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
- if (u->mixer_path && !u->mixer_path_set)
+ /* Only free the mixer_path if the sink owns it */
+ if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
pa_alsa_path_free(u->mixer_path);
- if (u->mixer_handle)
- snd_mixer_close(u->mixer_handle);
+ if (u->mixers)
+ pa_hashmap_free(u->mixers);
if (u->smoother)
pa_smoother_free(u->smoother);
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index 0a40ca8..a57be6d 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -81,34 +81,37 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name);
-struct ucm_port {
- pa_alsa_ucm_config *ucm;
- pa_device_port *core_port;
-
- /* A single port will be associated with multiple devices if it represents
- * a combination of devices. */
- pa_dynarray *devices; /* pa_alsa_ucm_device */
-};
-static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
- pa_alsa_ucm_device **devices, unsigned n_devices);
-static void ucm_port_free(pa_device_port *port);
-static void ucm_port_update_available(struct ucm_port *port);
+static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
+ pa_alsa_ucm_device **devices, unsigned n_devices);
+static void ucm_port_data_free(pa_device_port *port);
+static void ucm_port_update_available(pa_alsa_ucm_port_data *port);
static struct ucm_items item[] = {
{"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
{"CapturePCM", PA_ALSA_PROP_UCM_SOURCE},
+ {"PlaybackCTL", PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE},
{"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME},
{"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH},
+ {"PlaybackMixer", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE},
+ {"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM},
+ {"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM},
+ {"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE},
{"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY},
{"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE},
{"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS},
+ {"CaptureCTL", PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE},
{"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME},
{"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH},
+ {"CaptureMixer", PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE},
+ {"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM},
+ {"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM},
+ {"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE},
{"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY},
{"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE},
{"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
{"TQ", PA_ALSA_PROP_UCM_QOS},
+ {"JackCTL", PA_ALSA_PROP_UCM_JACK_DEVICE},
{"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL},
{"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE},
{NULL, NULL},
@@ -143,29 +146,25 @@ static struct ucm_info dev_info[] = {
{NULL, 0}
};
-/* UCM profile properties - The verb data is store so it can be used to fill
- * the new profiles properties */
-static int ucm_get_property(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) {
- const char *value;
- char *id;
- int i;
-
- for (i = 0; item[i].id; i++) {
- int err;
-
- id = pa_sprintf_malloc("=%s//%s", item[i].id, verb_name);
- err = snd_use_case_get(uc_mgr, id, &value);
- pa_xfree(id);
- if (err < 0)
- continue;
- pa_log_debug("Got %s for verb %s: %s", item[i].id, verb_name, value);
- pa_proplist_sets(verb->proplist, item[i].property, value);
- free((void*)value);
- }
+static char *ucm_verb_value(
+ snd_use_case_mgr_t *uc_mgr,
+ const char *verb_name,
+ const char *id) {
- return 0;
-};
+ const char *value;
+ char *_id = pa_sprintf_malloc("=%s//%s", id, verb_name);
+ int err = snd_use_case_get(uc_mgr, _id, &value);
+ pa_xfree(_id);
+ if (err < 0)
+ return NULL;
+ pa_log_debug("Got %s for verb %s: %s", id, verb_name, value);
+ /* Use the cast here to allow free() call without casting for callers.
+ * The snd_use_case_get() returns mallocated string.
+ * See the Note: in use-case.h for snd_use_case_get().
+ */
+ return (char *)value;
+}
static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) {
pa_alsa_ucm_device *d;
@@ -202,6 +201,139 @@ static void ucm_add_devices_to_idxset(
}
}
+/* Split a string into words. Like pa_split_spaces() but handle '' and "". */
+static char *ucm_split_devnames(const char *c, const char **state) {
+ const char *current = *state ? *state : c;
+ char h;
+ size_t l;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, "\n\r \t");
+ h = *current;
+ if (h == '\'' || h =='"') {
+ c = ++current;
+ for (l = 0; *c && *c != h; l++) c++;
+ if (*c != h)
+ return NULL;
+ *state = c + 1;
+ } else {
+ l = strcspn(current, "\n\r \t");
+ *state = current+l;
+ }
+
+ return pa_xstrndup(current, l);
+}
+
+
+static void ucm_volume_free(pa_alsa_ucm_volume *vol) {
+ pa_assert(vol);
+ pa_xfree(vol->mixer_elem);
+ pa_xfree(vol->master_elem);
+ pa_xfree(vol->master_type);
+ pa_xfree(vol);
+}
+
+/* Get the volume identifier */
+static char *ucm_get_mixer_id(
+ pa_alsa_ucm_device *device,
+ const char *mprop,
+ const char *cprop,
+ const char *cid)
+{
+#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */
+ snd_ctl_elem_id_t *ctl;
+ int err;
+#endif
+ const char *value;
+ char *value2;
+ int index;
+
+ /* mixer element as first, if it's found, return it without modifications */
+ value = pa_proplist_gets(device->proplist, mprop);
+ if (value)
+ return pa_xstrdup(value);
+ /* fallback, get the control element identifier */
+ /* and try to do some heuristic to determine the mixer element name */
+ value = pa_proplist_gets(device->proplist, cprop);
+ if (value == NULL)
+ return NULL;
+#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */
+ /* The new parser may return also element index. */
+ snd_ctl_elem_id_alloca(&ctl);
+ err = snd_use_case_parse_ctl_elem_id(ctl, cid, value);
+ if (err < 0)
+ return NULL;
+ value = snd_ctl_elem_id_get_name(ctl);
+ index = snd_ctl_elem_id_get_index(ctl);
+#else
+#warning "Upgrade to alsa-lib 1.2.1!"
+ index = 0;
+#endif
+ if (!(value2 = pa_str_strip_suffix(value, " Playback Volume")))
+ if (!(value2 = pa_str_strip_suffix(value, " Capture Volume")))
+ if (!(value2 = pa_str_strip_suffix(value, " Volume")))
+ value2 = pa_xstrdup(value);
+ if (index > 0) {
+ char *mix = pa_sprintf_malloc("'%s',%d", value2, index);
+ pa_xfree(value2);
+ return mix;
+ }
+ return value2;
+}
+
+/* Get the volume identifier */
+static pa_alsa_ucm_volume *ucm_get_mixer_volume(
+ pa_alsa_ucm_device *device,
+ const char *mprop,
+ const char *cprop,
+ const char *cid,
+ const char *masterid,
+ const char *mastertype)
+{
+ pa_alsa_ucm_volume *vol;
+ char *mixer_elem;
+
+ mixer_elem = ucm_get_mixer_id(device, mprop, cprop, cid);
+ if (mixer_elem == NULL)
+ return NULL;
+ vol = pa_xnew0(pa_alsa_ucm_volume, 1);
+ if (vol == NULL) {
+ pa_xfree(mixer_elem);
+ return NULL;
+ }
+ vol->mixer_elem = mixer_elem;
+ vol->master_elem = pa_xstrdup(pa_proplist_gets(device->proplist, masterid));
+ vol->master_type = pa_xstrdup(pa_proplist_gets(device->proplist, mastertype));
+ return vol;
+}
+
+/* Get the ALSA mixer device for the UCM device */
+static const char *get_mixer_device(pa_alsa_ucm_device *dev, bool is_sink)
+{
+ const char *dev_name;
+
+ if (is_sink) {
+ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE);
+ if (!dev_name)
+ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE);
+ } else {
+ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE);
+ if (!dev_name)
+ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE);
+ }
+ return dev_name;
+}
+
+/* Get the ALSA mixer device for the UCM jack */
+static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) {
+ const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_DEVICE);
+ if (!dev_name)
+ return get_mixer_device(dev, is_sink);
+ return dev_name;
+}
+
/* Create a property list for this ucm device */
static int ucm_get_device_property(
pa_alsa_ucm_device *device,
@@ -216,9 +348,10 @@ static int ucm_get_device_property(
int err;
uint32_t ui;
int n_confdev, n_suppdev;
+ pa_alsa_ucm_volume *vol;
for (i = 0; item[i].id; i++) {
- id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name);
+ id = pa_sprintf_malloc("%s/%s", item[i].id, device_name);
err = snd_use_case_get(uc_mgr, id, &value);
pa_xfree(id);
if (err < 0)
@@ -240,14 +373,8 @@ static int ucm_get_device_property(
/* get pcm */
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK);
- if (!value) { /* take pcm from verb playback default */
- value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SINK);
- if (value) {
- pa_log_debug("UCM playback device %s fetch pcm from verb default %s", device_name, value);
- pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SINK, value);
- } else
- pa_log("UCM playback device %s fetch pcm failed", device_name);
- }
+ if (!value) /* take pcm from verb playback default */
+ pa_log("UCM playback device %s fetch pcm failed", device_name);
}
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
@@ -260,14 +387,8 @@ static int ucm_get_device_property(
/* get pcm */
value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE);
- if (!value) { /* take pcm from verb capture default */
- value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SOURCE);
- if (value) {
- pa_log_debug("UCM capture device %s fetch pcm from verb default %s", device_name, value);
- pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SOURCE, value);
- } else
- pa_log("UCM capture device %s fetch pcm failed", device_name);
- }
+ if (!value) /* take pcm from verb capture default */
+ pa_log("UCM capture device %s fetch pcm failed", device_name);
}
if (device->playback_channels == 0 && device->capture_channels == 0) {
@@ -280,8 +401,7 @@ static int ucm_get_device_property(
/* get rate and priority of device */
if (device->playback_channels) { /* sink device */
/* get rate */
- if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE)) ||
- (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) {
+ if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) {
if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) {
pa_log_debug("UCM playback device %s rate %d", device_name, ui);
device->playback_rate = ui;
@@ -297,12 +417,20 @@ static int ucm_get_device_property(
else
pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
}
+
+ vol = ucm_get_mixer_volume(device,
+ PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM,
+ PA_ALSA_PROP_UCM_PLAYBACK_VOLUME,
+ "PlaybackVolume",
+ PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM,
+ PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE);
+ if (vol)
+ pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
}
if (device->capture_channels) { /* source device */
/* get rate */
- if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE)) ||
- (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) {
+ if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) {
if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) {
pa_log_debug("UCM capture device %s rate %d", device_name, ui);
device->capture_rate = ui;
@@ -318,6 +446,15 @@ static int ucm_get_device_property(
else
pa_log_debug("UCM capture priority %s for device %s error", value, device_name);
}
+
+ vol = ucm_get_mixer_volume(device,
+ PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM,
+ PA_ALSA_PROP_UCM_CAPTURE_VOLUME,
+ "CaptureVolume",
+ PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM,
+ PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE);
+ if (vol)
+ pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
}
if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
@@ -421,6 +558,11 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
d->hw_mute_jacks = pa_dynarray_new(NULL);
d->available = PA_AVAILABLE_UNKNOWN;
+ d->playback_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+ (pa_free_cb_t) ucm_volume_free);
+ d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+ (pa_free_cb_t) ucm_volume_free);
+
PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
}
@@ -576,17 +718,25 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
const char **verb_list;
int num_verbs, i, err = 0;
- /* is UCM available for this card ? */
- err = snd_card_get_name(card_index, &card_name);
- if (err < 0) {
- pa_log("Card can't get card_name from card_index %d", card_index);
- goto name_fail;
- }
-
+ /* support multiple card instances, address card directly by index */
+ card_name = pa_sprintf_malloc("hw:%i", card_index);
+ if (card_name == NULL)
+ return -ENOMEM;
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
- pa_log_info("UCM not available for card %s", card_name);
- goto ucm_mgr_fail;
+ /* fallback longname: is UCM available for this card ? */
+ pa_xfree(card_name);
+ err = snd_card_get_name(card_index, &card_name);
+ if (err < 0) {
+ pa_log("Card can't get card_name from card_index %d", card_index);
+ goto name_fail;
+ }
+
+ err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
+ if (err < 0) {
+ pa_log_info("UCM not available for card %s", card_name);
+ goto ucm_mgr_fail;
+ }
}
pa_log_info("UCM available for card %s", card_name);
@@ -626,7 +776,7 @@ ucm_verb_fail:
}
ucm_mgr_fail:
- free(card_name);
+ pa_xfree(card_name);
name_fail:
return err;
@@ -636,6 +786,8 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons
pa_alsa_ucm_device *d;
pa_alsa_ucm_modifier *mod;
pa_alsa_ucm_verb *verb;
+ char *value;
+ unsigned ui;
int err = 0;
*p_verb = NULL;
@@ -650,6 +802,11 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons
pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name));
pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc));
+ value = ucm_verb_value(uc_mgr, verb_name, "Priority");
+ if (value && !pa_atou(value, &ui))
+ verb->priority = ui > 10000 ? 10000 : ui;
+ free(value);
+
err = ucm_get_devices(verb, uc_mgr);
if (err < 0)
pa_log("No UCM devices for verb %s", verb_name);
@@ -658,9 +815,6 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons
if (err < 0)
pa_log("No UCM modifiers for verb %s", verb_name);
- /* Verb properties */
- ucm_get_property(verb, uc_mgr, verb_name);
-
PA_LLIST_FOREACH(d, verb->devices) {
const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
@@ -693,6 +847,86 @@ static int pa_alsa_ucm_device_cmp(const void *a, const void *b) {
return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME));
}
+static void set_eld_devices(pa_hashmap *hash)
+{
+ pa_device_port *port;
+ pa_alsa_ucm_port_data *data;
+ pa_alsa_ucm_device *dev;
+ const char *eld_mixer_device_name;
+ void *state;
+ int idx, eld_device;
+
+ PA_HASHMAP_FOREACH(port, hash, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+ eld_mixer_device_name = NULL;
+ eld_device = -1;
+ PA_DYNARRAY_FOREACH(dev, data->devices, idx) {
+ if (dev->eld_device >= 0 && dev->eld_mixer_device_name) {
+ if (eld_device >= 0 && eld_device != dev->eld_device) {
+ pa_log_error("The ELD device is already set!");
+ } else if (eld_mixer_device_name && pa_streq(dev->eld_mixer_device_name, eld_mixer_device_name)) {
+ pa_log_error("The ELD mixer device is already set (%s, %s)!", dev->eld_mixer_device_name, dev->eld_mixer_device_name);
+ } else {
+ eld_mixer_device_name = dev->eld_mixer_device_name;
+ eld_device = dev->eld_device;
+ }
+ }
+ }
+ data->eld_device = eld_device;
+ data->eld_mixer_device_name = pa_xstrdup(eld_mixer_device_name);
+ }
+}
+
+static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_hashmap *mixers, bool ignore_dB) {
+ pa_device_port *port;
+ pa_alsa_path *path;
+ pa_alsa_ucm_port_data *data;
+ pa_alsa_ucm_device *dev;
+ snd_mixer_t *mixer_handle;
+ const char *profile, *mdev, *mdev2;
+ void *state, *state2;
+ int idx;
+
+ PA_HASHMAP_FOREACH(port, hash, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+
+ mdev = NULL;
+ PA_DYNARRAY_FOREACH(dev, data->devices, idx) {
+ mdev2 = get_mixer_device(dev, is_sink);
+ if (mdev && !pa_streq(mdev, mdev2)) {
+ pa_log_error("Two mixer device names found ('%s', '%s'), using s/w volume", mdev, mdev2);
+ goto fail;
+ }
+ mdev = mdev2;
+ }
+
+ if (!(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) {
+ pa_log_error("Failed to find a working mixer device (%s).", mdev);
+ goto fail;
+ }
+
+ PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) {
+ if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) {
+ pa_log_warn("Could not probe path: %s, using s/w volume", data->path->name);
+ pa_hashmap_remove(data->paths, profile);
+ } else if (!path->has_volume) {
+ pa_log_warn("Path %s is not a volume control", data->path->name);
+ pa_hashmap_remove(data->paths, profile);
+ } else
+ pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name);
+ }
+ }
+
+ return;
+
+fail:
+ /* We could not probe the paths we created. Free them and revert to software volumes. */
+ PA_HASHMAP_FOREACH(port, hash, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+ pa_hashmap_remove_all(data->paths);
+ }
+}
+
static void ucm_add_port_combination(
pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context,
@@ -710,7 +944,11 @@ static void ucm_add_port_combination(
char *name, *desc;
const char *dev_name;
const char *direction;
+ const char *profile;
pa_alsa_ucm_device *sorted[num], *dev;
+ pa_alsa_ucm_port_data *data;
+ pa_alsa_ucm_volume *vol;
+ void *state;
for (i = 0; i < num; i++)
sorted[i] = pdevices[i];
@@ -758,8 +996,6 @@ static void ucm_add_port_combination(
port = pa_hashmap_get(ports, name);
if (!port) {
- struct ucm_port *ucm_port;
-
pa_device_port_new_data port_data;
pa_device_port_new_data_init(&port_data);
@@ -767,15 +1003,45 @@ static void ucm_add_port_combination(
pa_device_port_new_data_set_description(&port_data, desc);
pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
- port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port));
- port->impl_free = ucm_port_free;
+ port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data));
pa_device_port_new_data_done(&port_data);
- ucm_port = PA_DEVICE_PORT_DATA(port);
- ucm_port_init(ucm_port, context->ucm, port, pdevices, num);
+ data = PA_DEVICE_PORT_DATA(port);
+ ucm_port_data_init(data, context->ucm, port, pdevices, num);
+ port->impl_free = ucm_port_data_free;
pa_hashmap_put(ports, port->name, port);
pa_log_debug("Add port %s: %s", port->name, port->description);
+
+ if (num == 1) {
+ /* To keep things simple and not worry about stacking controls, we only support hardware volumes on non-combination
+ * ports. */
+ data = PA_DEVICE_PORT_DATA(port);
+
+ PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) {
+ pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem,
+ is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT);
+
+ if (!path)
+ pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem);
+ else {
+ if (vol->master_elem) {
+ pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false);
+ e->switch_use = PA_ALSA_SWITCH_MUTE;
+ e->volume_use = PA_ALSA_VOLUME_MERGE;
+ }
+
+ pa_hashmap_put(data->paths, pa_xstrdup(profile), path);
+
+ /* Add path also to already created empty path set */
+ dev = sorted[0];
+ if (is_sink)
+ pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path);
+ else
+ pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path);
+ }
+ }
+ }
}
port->priority = priority;
@@ -950,6 +1216,9 @@ void pa_alsa_ucm_add_ports_combination(
ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core);
pa_xfree(pdevices);
}
+
+ /* ELD devices */
+ set_eld_devices(ports);
}
void pa_alsa_ucm_add_ports(
@@ -957,7 +1226,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
- pa_card *card) {
+ pa_card *card,
+ snd_pcm_t *pcm_handle,
+ bool ignore_dB) {
uint32_t idx;
char *merged_roles;
@@ -972,6 +1243,9 @@ void pa_alsa_ucm_add_ports(
/* add ports first */
pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
+ /* now set up volume paths if any */
+ probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB);
+
/* then set property PA_PROP_DEVICE_INTENDED_ROLES */
merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
@@ -996,10 +1270,13 @@ void pa_alsa_ucm_add_ports(
}
/* Change UCM verb and device to match selected card profile */
-int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
int ret = 0;
const char *profile;
pa_alsa_ucm_verb *verb;
+ pa_device_port *port;
+ pa_alsa_ucm_port_data *data;
+ void *state;
if (new_profile == old_profile)
return ret;
@@ -1028,6 +1305,12 @@ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, co
}
}
+ /* select volume controls on ports */
+ PA_HASHMAP_FOREACH(port, card->ports, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+ data->path = pa_hashmap_get(data->paths, new_profile);
+ }
+
return ret;
}
@@ -1079,23 +1362,35 @@ int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *p
static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
+ pa_alsa_path_set *ps;
+
+ /* create empty path set for the future path additions */
+ ps = pa_xnew0(pa_alsa_path_set, 1);
+ ps->direction = m->direction;
+ ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
switch (m->direction) {
case PA_ALSA_DIRECTION_ANY:
pa_idxset_put(p->output_mappings, m, NULL);
pa_idxset_put(p->input_mappings, m, NULL);
+ m->output_path_set = ps;
+ m->input_path_set = ps;
break;
case PA_ALSA_DIRECTION_OUTPUT:
pa_idxset_put(p->output_mappings, m, NULL);
+ m->output_path_set = ps;
break;
case PA_ALSA_DIRECTION_INPUT:
pa_idxset_put(p->input_mappings, m, NULL);
+ m->input_path_set = ps;
break;
}
}
static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) {
char *cur_desc;
- const char *new_desc;
+ const char *new_desc, *mdev;
+ bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT;
pa_idxset_put(m->ucm_context.ucm_devices, device, NULL);
@@ -1111,10 +1406,14 @@ static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *
m->description = m->description ? m->description : pa_xstrdup("");
/* save mapping to ucm device */
- if (m->direction == PA_ALSA_DIRECTION_OUTPUT)
+ if (is_sink)
device->playback_mapping = m;
else
device->capture_mapping = m;
+
+ mdev = get_mixer_device(device, is_sink);
+ if (mdev)
+ pa_proplist_sets(m->proplist, "alsa.mixer_device", mdev);
}
static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) {
@@ -1294,6 +1593,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
pa_alsa_jack *j;
const char *device_name;
const char *jack_control;
+ const char *mixer_device_name;
char *name;
pa_assert(ucm);
@@ -1303,6 +1603,22 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL);
if (jack_control) {
+#if SND_LIB_VERSION >= 0x10201
+ snd_ctl_elem_id_t *ctl;
+ int err, index;
+ snd_ctl_elem_id_alloca(&ctl);
+ err = snd_use_case_parse_ctl_elem_id(ctl, "JackControl", jack_control);
+ if (err < 0)
+ return NULL;
+ jack_control = snd_ctl_elem_id_get_name(ctl);
+ index = snd_ctl_elem_id_get_index(ctl);
+ if (index > 0) {
+ pa_log("[%s] Invalid JackControl index value: \"%s\",%d", device_name, jack_control, index);
+ return NULL;
+ }
+#else
+#warning "Upgrade to alsa-lib 1.2.1!"
+#endif
if (!pa_endswith(jack_control, " Jack")) {
pa_log("[%s] Invalid JackControl value: \"%s\"", device_name, jack_control);
return NULL;
@@ -1312,16 +1628,22 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
* end, so drop the trailing " Jack". */
name = pa_xstrndup(jack_control, strlen(jack_control) - 5);
} else {
- /* The jack control hasn't been explicitly configured - try a jack name
- * that is the same as the device name. */
- name = pa_xstrdup(device_name);
+ /* The jack control hasn't been explicitly configured, fail. */
+ return NULL;
}
PA_LLIST_FOREACH(j, ucm->jacks)
if (pa_streq(j->name, name))
goto finish;
- j = pa_alsa_jack_new(NULL, name);
+ mixer_device_name = get_jack_mixer_device(device, true);
+ if (!mixer_device_name)
+ mixer_device_name = get_jack_mixer_device(device, false);
+ if (!mixer_device_name) {
+ pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control);
+ return NULL;
+ }
+ j = pa_alsa_jack_new(NULL, mixer_device_name, name);
PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
finish:
@@ -1342,7 +1664,7 @@ static int ucm_create_profile(
pa_alsa_ucm_modifier *mod;
int i = 0;
const char *name, *sink, *source;
- char *verb_cmp, *c;
+ unsigned int priority;
pa_assert(ps);
@@ -1362,24 +1684,26 @@ static int ucm_create_profile(
p->supported = true;
pa_hashmap_put(ps->profiles, p->name, p);
- /* TODO: get profile priority from ucm info or policy management */
- c = verb_cmp = pa_xstrdup(verb_name);
- while (*c) {
- if (*c == '_') *c = ' ';
- c++;
- }
+ /* TODO: get profile priority from policy management */
+ priority = verb->priority;
- for (i = 0; verb_info[i].id; i++) {
- if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
- p->priority = verb_info[i].priority;
- break;
+ if (priority == 0) {
+ char *verb_cmp, *c;
+ c = verb_cmp = pa_xstrdup(verb_name);
+ while (*c) {
+ if (*c == '_') *c = ' ';
+ c++;
+ }
+ for (i = 0; verb_info[i].id; i++) {
+ if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
+ priority = verb_info[i].priority;
+ break;
+ }
}
+ pa_xfree(verb_cmp);
}
- pa_xfree(verb_cmp);
-
- if (verb_info[i].id == NULL)
- p->priority = 1000;
+ p->priority = priority;
PA_LLIST_FOREACH(dev, verb->devices) {
pa_alsa_jack *jack;
@@ -1393,7 +1717,8 @@ static int ucm_create_profile(
ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
jack = ucm_get_jack(ucm, dev);
- device_set_jack(dev, jack);
+ if (jack)
+ device_set_jack(dev, jack);
/* JackHWMute contains a list of device names. Each listed device must
* be associated with the jack object that we just created. */
@@ -1402,7 +1727,7 @@ static int ucm_create_profile(
char *hw_mute_device_name;
const char *state = NULL;
- while ((hw_mute_device_name = pa_split_spaces(jack_hw_mute, &state))) {
+ while ((hw_mute_device_name = ucm_split_devnames(jack_hw_mute, &state))) {
pa_alsa_ucm_verb *verb2;
bool device_found = false;
@@ -1446,6 +1771,33 @@ static int ucm_create_profile(
return 0;
}
+static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm)
+{
+ pa_alsa_ucm_mapping_context *context = &m->ucm_context;
+ pa_alsa_ucm_device *dev;
+ uint32_t idx;
+ char *mdev;
+ snd_pcm_info_t *info;
+ int pcm_card, pcm_device;
+
+ snd_pcm_info_alloca(&info);
+ if (snd_pcm_info(pcm, info) < 0)
+ return;
+
+ if ((pcm_card = snd_pcm_info_get_card(info)) < 0)
+ return;
+ if ((pcm_device = snd_pcm_info_get_device(info)) < 0)
+ return;
+
+ PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
+ mdev = pa_sprintf_malloc("hw:%i", pcm_card);
+ if (mdev == NULL)
+ continue;
+ dev->eld_mixer_device_name = mdev;
+ dev->eld_device = pcm_device;
+ }
+}
+
static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
snd_pcm_t* pcm;
pa_sample_spec try_ss = ucm->core->default_sample_spec;
@@ -1467,8 +1819,11 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m,
pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss,
&try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels);
- if (pcm && !exact_channels)
- m->channel_map = try_map;
+ if (pcm) {
+ if (!exact_channels)
+ m->channel_map = try_map;
+ mapping_init_eld(m, pcm);
+ }
return pcm;
}
@@ -1500,27 +1855,28 @@ static void profile_finalize_probing(pa_alsa_profile *p) {
}
}
-static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
- snd_pcm_t *pcm_handle;
+static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) {
snd_mixer_t *mixer_handle;
pa_alsa_ucm_mapping_context *context = &m->ucm_context;
pa_alsa_ucm_device *dev;
uint32_t idx;
- pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm;
- mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL);
- if (!mixer_handle)
- return;
-
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
bool has_control;
- has_control = pa_alsa_mixer_find(mixer_handle, dev->jack->alsa_name, 0) != NULL;
+ if (!dev->jack || !dev->jack->mixer_device_name)
+ continue;
+
+ mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true);
+ if (!mixer_handle) {
+ pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name);
+ continue;
+ }
+
+ has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL;
pa_alsa_jack_set_has_control(dev->jack, has_control);
pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control);
}
-
- snd_mixer_close(mixer_handle);
}
static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
@@ -1578,11 +1934,11 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
PA_IDXSET_FOREACH(m, p->output_mappings, idx)
if (!PA_UCM_IS_MODIFIER_MAPPING(m))
- ucm_mapping_jack_probe(m);
+ ucm_mapping_jack_probe(m, ucm->mixers);
PA_IDXSET_FOREACH(m, p->input_mappings, idx)
if (!PA_UCM_IS_MODIFIER_MAPPING(m))
- ucm_mapping_jack_probe(m);
+ ucm_mapping_jack_probe(m, ucm->mixers);
profile_finalize_probing(p);
}
@@ -1636,11 +1992,20 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
if (di->ucm_ports)
pa_dynarray_free(di->ucm_ports);
+ if (di->playback_volumes)
+ pa_hashmap_free(di->playback_volumes);
+ if (di->capture_volumes)
+ pa_hashmap_free(di->capture_volumes);
+
pa_proplist_free(di->proplist);
+
if (di->conflicting_devices)
pa_idxset_free(di->conflicting_devices, NULL);
if (di->supported_devices)
pa_idxset_free(di->supported_devices, NULL);
+
+ pa_xfree(di->eld_mixer_device_name);
+
pa_xfree(di);
}
@@ -1771,7 +2136,7 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_
}
}
-static void device_add_ucm_port(pa_alsa_ucm_device *device, struct ucm_port *port) {
+static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) {
pa_assert(device);
pa_assert(port);
@@ -1799,7 +2164,7 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
}
static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
- struct ucm_port *port;
+ pa_alsa_ucm_port_data *port;
unsigned idx;
pa_assert(device);
@@ -1833,8 +2198,8 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
device_set_available(device, available);
}
-static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
- pa_alsa_ucm_device **devices, unsigned n_devices) {
+static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
+ pa_alsa_ucm_device **devices, unsigned n_devices) {
unsigned i;
pa_assert(ucm);
@@ -1844,17 +2209,21 @@ static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_dev
port->ucm = ucm;
port->core_port = core_port;
port->devices = pa_dynarray_new(NULL);
+ port->eld_device = -1;
for (i = 0; i < n_devices; i++) {
pa_dynarray_append(port->devices, devices[i]);
device_add_ucm_port(devices[i], port);
}
+ port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+ (pa_free_cb_t) pa_alsa_path_free);
+
ucm_port_update_available(port);
}
-static void ucm_port_free(pa_device_port *port) {
- struct ucm_port *ucm_port;
+static void ucm_port_data_free(pa_device_port *port) {
+ pa_alsa_ucm_port_data *ucm_port;
pa_assert(port);
@@ -1862,9 +2231,14 @@ static void ucm_port_free(pa_device_port *port) {
if (ucm_port->devices)
pa_dynarray_free(ucm_port->devices);
+
+ if (ucm_port->paths)
+ pa_hashmap_free(ucm_port->paths);
+
+ pa_xfree(ucm_port->eld_mixer_device_name);
}
-static void ucm_port_update_available(struct ucm_port *port) {
+static void ucm_port_update_available(pa_alsa_ucm_port_data *port) {
pa_alsa_ucm_device *device;
unsigned idx;
pa_available_t available = PA_AVAILABLE_YES;
@@ -1896,7 +2270,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha
return NULL;
}
-int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
return -1;
}
@@ -1909,7 +2283,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
- pa_card *card) {
+ pa_card *card,
+ snd_pcm_t *pcm_handle,
+ bool ignore_dB) {
}
void pa_alsa_ucm_add_ports_combination(
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index c926f3c..e7a795c 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -45,12 +45,33 @@ typedef void snd_use_case_mgr_t;
/** For devices: Playback roles */
#define PA_ALSA_PROP_UCM_PLAYBACK_ROLES "alsa.ucm.playback.roles"
+/** For devices: Playback control device name */
+#define PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE "alsa.ucm.playback.ctldev"
+
/** For devices: Playback control volume ID string. e.g PlaybackVolume */
#define PA_ALSA_PROP_UCM_PLAYBACK_VOLUME "alsa.ucm.playback.volume"
/** For devices: Playback switch e.g PlaybackSwitch */
#define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH "alsa.ucm.playback.switch"
+/** For devices: Playback mixer device name */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE "alsa.ucm.playback.mixer.device"
+
+/** For devices: Playback mixer identifier */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM "alsa.ucm.playback.mixer.element"
+
+/** For devices: Playback mixer master identifier */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM "alsa.ucm.playback.master.element"
+
+/** For devices: Playback mixer master type */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type"
+
+/** For devices: Playback mixer master identifier */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ID "alsa.ucm.playback.master.id"
+
+/** For devices: Playback mixer master type */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type"
+
/** For devices: Playback priority */
#define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY "alsa.ucm.playback.priority"
@@ -63,12 +84,33 @@ typedef void snd_use_case_mgr_t;
/** For devices: Capture roles */
#define PA_ALSA_PROP_UCM_CAPTURE_ROLES "alsa.ucm.capture.roles"
+/** For devices: Capture control device name */
+#define PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE "alsa.ucm.capture.ctldev"
+
/** For devices: Capture controls volume ID string. e.g CaptureVolume */
#define PA_ALSA_PROP_UCM_CAPTURE_VOLUME "alsa.ucm.capture.volume"
/** For devices: Capture switch e.g CaptureSwitch */
#define PA_ALSA_PROP_UCM_CAPTURE_SWITCH "alsa.ucm.capture.switch"
+/** For devices: Capture mixer device name */
+#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE "alsa.ucm.capture.mixer.device"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM "alsa.ucm.capture.mixer.element"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM "alsa.ucm.capture.master.element"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ID "alsa.ucm.capture.master.id"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type"
+
/** For devices: Capture priority */
#define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY "alsa.ucm.capture.priority"
@@ -84,6 +126,9 @@ typedef void snd_use_case_mgr_t;
/** For devices: The modifier (if any) that this device corresponds to */
#define PA_ALSA_PROP_UCM_MODIFIER "alsa.ucm.modifier"
+/* Corresponds to the "JackCTL" UCM value. */
+#define PA_ALSA_PROP_UCM_JACK_DEVICE "alsa.ucm.jack_device"
+
/* Corresponds to the "JackControl" UCM value. */
#define PA_ALSA_PROP_UCM_JACK_CONTROL "alsa.ucm.jack_control"
@@ -95,10 +140,12 @@ typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
typedef struct pa_alsa_ucm_config pa_alsa_ucm_config;
typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context;
+typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data;
+typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume;
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index);
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
-int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile);
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile);
int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb);
@@ -107,7 +154,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
- pa_card *card);
+ pa_card *card,
+ snd_pcm_t *pcm_handle,
+ bool ignore_dB);
void pa_alsa_ucm_add_ports_combination(
pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context,
@@ -139,6 +188,11 @@ struct pa_alsa_ucm_device {
unsigned playback_channels;
unsigned capture_channels;
+ /* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to
+ * make this a hashmap of verb -> per-verb-device-properties-struct. */
+ pa_hashmap *playback_volumes;
+ pa_hashmap *capture_volumes;
+
pa_alsa_mapping *playback_mapping;
pa_alsa_mapping *capture_mapping;
@@ -153,6 +207,9 @@ struct pa_alsa_ucm_device {
pa_alsa_jack *jack;
pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */
pa_available_t available;
+
+ char *eld_mixer_device_name;
+ int eld_device;
};
void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device);
@@ -184,6 +241,7 @@ struct pa_alsa_ucm_verb {
PA_LLIST_FIELDS(pa_alsa_ucm_verb);
pa_proplist *proplist;
+ unsigned priority;
PA_LLIST_HEAD(pa_alsa_ucm_device, devices);
PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers);
@@ -194,6 +252,7 @@ struct pa_alsa_ucm_config {
snd_use_case_mgr_t *ucm_mgr;
pa_alsa_ucm_verb *active_verb;
+ pa_hashmap *mixers;
PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs);
PA_LLIST_HEAD(pa_alsa_jack, jacks);
};
@@ -206,4 +265,28 @@ struct pa_alsa_ucm_mapping_context {
pa_idxset *ucm_modifiers;
};
+struct pa_alsa_ucm_port_data {
+ pa_alsa_ucm_config *ucm;
+ pa_device_port *core_port;
+
+ /* A single port will be associated with multiple devices if it represents
+ * a combination of devices. */
+ pa_dynarray *devices; /* pa_alsa_ucm_device */
+
+ /* profile name -> pa_alsa_path for volume control */
+ pa_hashmap *paths;
+ /* Current path, set when activating profile */
+ pa_alsa_path *path;
+
+ /* ELD info */
+ char *eld_mixer_device_name;
+ int eld_device; /* PCM device number */
+};
+
+struct pa_alsa_ucm_volume {
+ char *mixer_elem; /* mixer element identifier */
+ char *master_elem; /* master mixer element identifier */
+ char *master_type;
+};
+
#endif
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index bd0a47e..d86f43c 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1066,6 +1066,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) {
int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
snd_pcm_state_t state;
+ snd_pcm_hw_params_t *hwparams;
int err;
pa_assert(pcm);
@@ -1103,16 +1104,25 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
break;
case SND_PCM_STATE_SUSPENDED:
- /* Retry resume 3 times before giving up, then fallback to restarting the stream. */
- for (int i = 0; i < 3; i++) {
- if ((err = snd_pcm_resume(pcm)) == 0)
- return 0;
- if (err != -EAGAIN)
- break;
- pa_msleep(25);
+ snd_pcm_hw_params_alloca(&hwparams);
+
+ if ((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(err));
+ return -1;
}
- pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM");
- /* Fall through */
+
+ if (snd_pcm_hw_params_can_resume(hwparams)) {
+ /* Retry resume 3 times before giving up, then fallback to restarting the stream. */
+ for (int i = 0; i < 3; i++) {
+ if ((err = snd_pcm_resume(pcm)) == 0)
+ return 0;
+ if (err != -EAGAIN)
+ break;
+ pa_msleep(25);
+ }
+ pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM");
+ }
+ /* Fall through */
default:
@@ -1600,7 +1610,11 @@ bool pa_alsa_may_tsched(bool want) {
#define SND_MIXER_ELEM_PULSEAUDIO (SND_MIXER_ELEM_LAST + 10)
-snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsigned int device) {
+static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer,
+ snd_ctl_elem_iface_t iface,
+ const char *name,
+ unsigned int index,
+ unsigned int device) {
snd_mixer_elem_t *elem;
for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) {
@@ -1608,8 +1622,12 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_PULSEAUDIO)
continue;
helem = snd_mixer_elem_get_private(elem);
+ if (snd_hctl_elem_get_interface(helem) != iface)
+ continue;
if (!pa_streq(snd_hctl_elem_get_name(helem), name))
continue;
+ if (snd_hctl_elem_get_index(helem) != index)
+ continue;
if (snd_hctl_elem_get_device(helem) != device)
continue;
return elem;
@@ -1617,6 +1635,14 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig
return NULL;
}
+snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device) {
+ return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, name, 0, device);
+}
+
+snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) {
+ return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_PCM, name, 0, device);
+}
+
static int mixer_class_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2)
{
/* Dummy compare function */
@@ -1700,85 +1726,119 @@ static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
return 0;
}
-snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device) {
+snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe) {
+ char *md = pa_sprintf_malloc("hw:%i", alsa_card_index);
+ snd_mixer_t *m = pa_alsa_open_mixer_by_name(mixers, md, probe);
+ pa_xfree(md);
+ return m;
+}
+
+snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe) {
int err;
snd_mixer_t *m;
- char *md;
- snd_pcm_info_t* info;
- snd_pcm_info_alloca(&info);
+ pa_alsa_mixer *pm;
+ char *dev2;
+ void *state;
+
+ pa_assert(mixers);
+ pa_assert(dev);
+
+ pm = pa_hashmap_get(mixers, dev);
+
+ /* The quick card number/index lookup (hw:#)
+ * We already know the card number/index, thus use the mixer
+ * from the cache at first.
+ */
+ if (!pm && pa_strneq(dev, "hw:", 3)) {
+ const char *s = dev + 3;
+ int card_index;
+ while (*s && *s >= 0 && *s <= '9') s++;
+ if (*s == '\0' && pa_atoi(dev + 3, &card_index) >= 0) {
+ PA_HASHMAP_FOREACH_KV(dev2, pm, mixers, state) {
+ if (pm->card_index == card_index) {
+ dev = dev2;
+ pm = pa_hashmap_get(mixers, dev);
+ break;
+ }
+ }
+ }
+ }
+
+ if (pm) {
+ if (!probe)
+ pm->used_for_probe_only = false;
+ return pm->mixer_handle;
+ }
if ((err = snd_mixer_open(&m, 0)) < 0) {
pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
return NULL;
}
- /* Then, try by card index */
- md = pa_sprintf_malloc("hw:%i", alsa_card_index);
- if (prepare_mixer(m, md) >= 0) {
-
- if (ctl_device)
- *ctl_device = md;
- else
- pa_xfree(md);
-
- return m;
+ if (prepare_mixer(m, dev) >= 0) {
+ pm = pa_xnew0(pa_alsa_mixer, 1);
+ if (pm) {
+ snd_hctl_t *hctl;
+ pm->card_index = -1;
+ /* determine the ALSA card number (index) and store it to card_index */
+ err = snd_mixer_get_hctl(m, dev, &hctl);
+ if (err >= 0) {
+ snd_ctl_card_info_t *info;
+ snd_ctl_card_info_alloca(&info);
+ err = snd_ctl_card_info(snd_hctl_ctl(hctl), info);
+ if (err >= 0)
+ pm->card_index = snd_ctl_card_info_get_card(info);
+ }
+ pm->used_for_probe_only = probe;
+ pm->mixer_handle = m;
+ pa_hashmap_put(mixers, pa_xstrdup(dev), pm);
+ return m;
+ }
}
- pa_xfree(md);
-
snd_mixer_close(m);
return NULL;
}
-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) {
- int err;
- snd_mixer_t *m;
- const char *dev;
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe) {
snd_pcm_info_t* info;
snd_pcm_info_alloca(&info);
pa_assert(pcm);
- if ((err = snd_mixer_open(&m, 0)) < 0) {
- pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
- return NULL;
- }
-
- /* First, try by name */
- if ((dev = snd_pcm_name(pcm)))
- if (prepare_mixer(m, dev) >= 0) {
- if (ctl_device)
- *ctl_device = pa_xstrdup(dev);
-
- return m;
- }
-
- /* Then, try by card index */
if (snd_pcm_info(pcm, info) >= 0) {
- char *md;
int card_idx;
- if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
-
- md = pa_sprintf_malloc("hw:%i", card_idx);
-
- if (!dev || !pa_streq(dev, md))
- if (prepare_mixer(m, md) >= 0) {
+ if ((card_idx = snd_pcm_info_get_card(info)) >= 0)
+ return pa_alsa_open_mixer(mixers, card_idx, probe);
+ }
- if (ctl_device)
- *ctl_device = md;
- else
- pa_xfree(md);
+ return NULL;
+}
- return m;
- }
+void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer_handle, pa_mainloop_api *ml)
+{
+ pa_alsa_mixer *pm;
+ void *state;
- pa_xfree(md);
+ PA_HASHMAP_FOREACH(pm, mixers, state)
+ if (pm->mixer_handle == mixer_handle) {
+ pm->used_for_probe_only = false;
+ if (!pm->fdl) {
+ pm->fdl = pa_alsa_fdlist_new();
+ if (pm->fdl)
+ pa_alsa_fdlist_set_handle(pm->fdl, pm->mixer_handle, NULL, ml);
+ }
}
- }
+}
- snd_mixer_close(m);
- return NULL;
+void pa_alsa_mixer_free(pa_alsa_mixer *mixer)
+{
+ if (mixer->fdl)
+ pa_alsa_fdlist_free(mixer->fdl);
+ if (mixer->mixer_handle)
+ snd_mixer_close(mixer->mixer_handle);
+ pa_xfree(mixer);
}
int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) {
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index 4ceaa06..0d3d5af 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -141,9 +141,14 @@ const char* pa_alsa_strerror(int errnum);
bool pa_alsa_may_tsched(bool want);
-snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsigned int device);
-
-snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device);
+snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device);
+snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device);
+
+snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe);
+snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe);
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe);
+void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer, pa_mainloop_api *ml);
+void pa_alsa_mixer_free(pa_alsa_mixer *mixer);
typedef struct pa_hdmi_eld pa_hdmi_eld;
struct pa_hdmi_eld {
diff --git a/src/modules/alsa/meson.build b/src/modules/alsa/meson.build
index 5309dc1..f31eeb5 100644
--- a/src/modules/alsa/meson.build
+++ b/src/modules/alsa/meson.build
@@ -32,7 +32,7 @@ libalsa_util = shared_library('alsa-util',
c_args : [pa_c_args, server_c_args],
link_args : [nodelete_link_args],
include_directories : [configinc, topinc],
- dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, alsa_dep, dbus_dep, libatomic_ops_dep, libm_dep, udev_dep],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, alsa_dep, dbus_dep, libatomic_ops_dep, libm_dep, udev_dep, libintl_dep],
install : true,
install_rpath : privlibdir,
install_dir : modlibexecdir,
diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
index 9f4dac4..6f9968e 100644
--- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
@@ -56,6 +56,9 @@ state.unplugged = unknown
state.plugged = no
state.unplugged = unknown
+[Jack Speaker]
+required-any = any
+
[Jack Speaker Phantom]
required-any = any
state.plugged = unknown
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common
index e52830d..7bf3463 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf.common
+++ b/src/modules/alsa/mixer/paths/analog-output.conf.common
@@ -85,7 +85,8 @@
; required-any = ignore | enumeration | any # In this element, either this or another option must exist (or an element)
; required-absent = ignore | enumeration | any # In this element, this option must not exist or the path will be invalid
;
-; [Element ...] # For each element that we shall control
+; [Element ...] # For each element that we shall control. The "..." here is the element name,
+; # or name and index separated by a comma.
; required = ignore | switch | volume | enumeration | any # If set, require this element to be of this kind and available,
; # otherwise don't consider this path valid for the card
; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements or jacks with required-any in this
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf
index 5842bfe..5842bfe 100644
--- a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf
+++ b/src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf
index b758a6f..b758a6f 100644
--- a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf
+++ b/src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf
index 3fa36e9..9fa7fe9 100644
--- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf
+++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf
@@ -13,8 +13,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
-; Steelseries Arctis 7 USB headset microphone path. Works also with Arctis Pro
-; Wireless.
+; USB gaming headset microphone input path. These headsets usually have two
+; output devices. The first one is mono, meant for voice audio, and the second
+; one is stereo, meant for everything else. The purpose of this unusual design
+; is to provide separate volume controls for voice and other audio, which can
+; be useful in gaming.
+;
+; Works with:
+; Steelseries Arctis 7
+; Steelseries Arctis Pro Wireless.
+; Lucidsound LS31
[General]
description-key = analog-input-microphone-headset
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf
index d8b24a2..6df662f 100644
--- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf
+++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf
@@ -13,11 +13,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
-; Steelseries Arctis 7 USB headset mono output path. Works also with Arctis Pro
-; Wireless. The headset has two output devices. The first one is mono, meant
-; for voice audio, and the second one is stereo, meant for everything else. The
-; purpose of this unusual design is to provide separate volume controls for
-; voice and other audio, which can be useful in gaming.
+; USB gaming headset mono output path. These headsets usually have two
+; output devices. The first one is mono, meant for voice audio, and the second
+; one is stereo, meant for everything else. The purpose of this unusual design
+; is to provide separate volume controls for voice and other audio, which can
+; be useful in gaming.
+;
+; Works with:
+; Steelseries Arctis 7
+; Steelseries Arctis Pro Wireless.
+; Lucidsound LS31
[General]
description-key = analog-output-headphones-mono
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf
index fcc58a0..e3f91cd 100644
--- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf
+++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf
@@ -13,11 +13,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
-; Steelseries Arctis 7 USB headset stereo output path. Works also with Arctis
-; Pro Wireless. The headset has two output devices. The first one is mono,
-; meant for voice audio, and the second one is stereo, meant for everything
-; else. The purpose of this unusual design is to provide separate volume
-; controls for voice and other audio, which can be useful in gaming.
+; USB gaming headset mono output path. These headsets usually have two
+; output devices. The first one is mono, meant for voice audio, and the second
+; one is stereo, meant for everything else. The purpose of this unusual design
+; is to provide separate volume controls for voice and other audio, which can
+; be useful in gaming.
+;
+; Works with:
+; Steelseries Arctis 7
+; Steelseries Arctis Pro Wireless.
+; Lucidsound LS31
;
; This path doesn't provide hardware volume control, because the stereo
; output is controlled by the PCM element with index 1, and currently
diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf
index 34c51bc..c73bf5c 100644
--- a/src/modules/alsa/mixer/profile-sets/default.conf
+++ b/src/modules/alsa/mixer/profile-sets/default.conf
@@ -49,7 +49,8 @@
; # If multiple are found to be working they will be available as device ports
; paths-output = ...
; element-input = ... # Instead of configuring a full mixer path simply configure a single
-; # mixer element for volume/mute handling
+; # mixer element for volume/mute handling. The value can be an element
+; # name, or name and index separated by a comma.
; element-output = ...
; priority = ...
; direction = any | input | output # Only useful for?
@@ -72,7 +73,8 @@
; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB
; # information from alsa. A decibel fix is a table that maps volume steps
; # to decibel values for one volume element. The "element" part in the
-; # section title is the name of the volume element.
+; # section title is the name of the volume element (or name and index
+; # separated by a comma).
; #
; # NOTE: This feature is meant just as a help for figuring out the correct
; # decibel values. PulseAudio is not the correct place to maintain the
diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf
index fe353c3..5f11ed1 100644
--- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf
+++ b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf
@@ -6,13 +6,14 @@ description = Chat
device-strings = hw:%f,0,0
channel-map = left,right
paths-input = analog-input-mic
-paths-output = steelseries-arctis-5-output-chat
+paths-output = steelseries-arctis-output-chat-common
+intended-roles = phone
[Mapping analog-game]
description = Game
device-strings = hw:%f,1,0
channel-map = left,right
-paths-output = steelseries-arctis-5-output-game
+paths-output = steelseries-arctis-output-game-common
direction = output
[Profile output:analog-chat+output:analog-game+input:analog-chat]
diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf
index e1394dc..f48b44f 100644
--- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf
+++ b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf
@@ -13,12 +13,17 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
-; Steelseries Arctis 7 USB and Arctis Pro Wireless USB headset. These headsets
-; have a microphone and two output devices. The first output device is mono,
-; meant for voice audio, and the second one is stereo, meant for everything
-; else. The purpose of this unusual design is to provide separate volume
+; USB gaming headset.
+; These headsets usually have two output devices. The first one is mono,
+; meant for voice audio, and the second one is stereo, meant for everything
+; else. The purpose of this unusual design is to provide separate volume
; controls for voice and other audio, which can be useful in gaming.
;
+; Works with:
+; Steelseries Arctis 7
+; Steelseries Arctis Pro Wireless.
+; Lucidsound LS31
+;
; See default.conf for an explanation on the directives used here.
[General]
@@ -27,13 +32,14 @@ auto-profiles = yes
[Mapping analog-mono]
device-strings = hw:%f,0,0
channel-map = mono
-paths-output = steelseries-arctis-7-output-mono
-paths-input = steelseries-arctis-7-input
+paths-output = usb-gaming-headset-output-mono
+paths-input = usb-gaming-headset-input
+intended-roles = phone
[Mapping analog-stereo]
device-strings = hw:%f,1,0
channel-map = left,right
-paths-output = steelseries-arctis-7-output-stereo
+paths-output = usb-gaming-headset-output-stereo
direction = output
[Profile output:analog-mono+output:analog-stereo+input:analog-mono]
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 1e1090f..c5852b4 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -111,9 +111,8 @@ struct userdata {
char *device_id;
int alsa_card_index;
- snd_mixer_t *mixer_handle;
+ pa_hashmap *mixers;
pa_hashmap *jacks;
- pa_alsa_fdlist *mixer_fdl;
pa_card *card;
@@ -241,7 +240,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
/* if UCM is available for this card then update the verb */
if (u->use_ucm) {
- if (pa_alsa_ucm_set_profile(&u->ucm, nd->profile ? nd->profile->name : NULL,
+ if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL,
od->profile ? od->profile->name : NULL) < 0) {
ret = -1;
goto finish;
@@ -294,7 +293,7 @@ static void init_profile(struct userdata *u) {
if (d->profile && u->use_ucm) {
/* Set initial verb */
- if (pa_alsa_ucm_set_profile(ucm, d->profile->name, NULL) < 0) {
+ if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) {
pa_log("Failed to set ucm profile %s", d->profile->name);
return;
}
@@ -513,15 +512,24 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
return 0;
}
-static pa_device_port* find_port_with_eld_device(pa_hashmap *ports, int device) {
+static pa_device_port* find_port_with_eld_device(struct userdata *u, int device) {
void *state;
pa_device_port *p;
- PA_HASHMAP_FOREACH(p, ports, state) {
- pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p);
- pa_assert(data->path);
- if (device == data->path->eld_device)
- return p;
+ if (u->use_ucm) {
+ PA_HASHMAP_FOREACH(p, u->card->ports, state) {
+ pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(p);
+ pa_assert(data->eld_mixer_device_name);
+ if (device == data->eld_device)
+ return p;
+ }
+ } else {
+ PA_HASHMAP_FOREACH(p, u->card->ports, state) {
+ pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p);
+ pa_assert(data->path);
+ if (device == data->path->eld_device)
+ return p;
+ }
}
return NULL;
}
@@ -538,7 +546,7 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) {
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return 0;
- p = find_port_with_eld_device(u->card->ports, device);
+ p = find_port_with_eld_device(u, device);
if (p == NULL) {
pa_log_error("Invalid device changed in ALSA: %d", device);
return 0;
@@ -566,33 +574,46 @@ static void init_eld_ctls(struct userdata *u) {
void *state;
pa_device_port *port;
- if (!u->mixer_handle)
- return;
-
/* The code in this function expects ports to have a pa_alsa_port_data
* struct as their data, but in UCM mode ports don't have any data. Hence,
* the ELD controls can't currently be used in UCM mode. */
- if (u->use_ucm)
- return;
-
PA_HASHMAP_FOREACH(port, u->card->ports, state) {
- pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port);
+ snd_mixer_t *mixer_handle;
snd_mixer_elem_t* melem;
int device;
- pa_assert(data->path);
- device = data->path->eld_device;
- if (device < 0)
+ if (u->use_ucm) {
+ pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(port);
+ device = data->eld_device;
+ if (device < 0 || !data->eld_mixer_device_name)
+ continue;
+
+ mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, data->eld_mixer_device_name, true);
+ } else {
+ pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port);
+
+ pa_assert(data->path);
+
+ device = data->path->eld_device;
+ if (device < 0)
+ continue;
+
+ mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, true);
+ }
+
+ if (!mixer_handle)
continue;
- melem = pa_alsa_mixer_find(u->mixer_handle, "ELD", device);
+ melem = pa_alsa_mixer_find_pcm(mixer_handle, "ELD", device);
if (melem) {
+ pa_alsa_mixer_set_fdlist(u->mixers, mixer_handle, u->core->mainloop);
snd_mixer_elem_set_callback(melem, hdmi_eld_changed);
snd_mixer_elem_set_callback_private(melem, u);
hdmi_eld_changed(melem, 0);
+ pa_log_info("ELD device found for port %s (%d).", port->name, device);
}
else
- pa_log_debug("No ELD device found for port %s.", port->name);
+ pa_log_debug("No ELD device found for port %s (%d).", port->name, device);
}
}
@@ -627,25 +648,31 @@ static void init_jacks(struct userdata *u) {
if (pa_hashmap_size(u->jacks) == 0)
return;
- u->mixer_fdl = pa_alsa_fdlist_new();
-
- u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL);
- if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) >= 0) {
- PA_HASHMAP_FOREACH(jack, u->jacks, state) {
- jack->melem = pa_alsa_mixer_find(u->mixer_handle, jack->alsa_name, 0);
- if (!jack->melem) {
- pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
- pa_alsa_jack_set_has_control(jack, false);
- continue;
+ PA_HASHMAP_FOREACH(jack, u->jacks, state) {
+ if (!jack->mixer_device_name) {
+ jack->mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, false);
+ if (!jack->mixer_handle) {
+ pa_log("Failed to open mixer for card %d for jack detection", u->alsa_card_index);
+ continue;
+ }
+ } else {
+ jack->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, jack->mixer_device_name, false);
+ if (!jack->mixer_handle) {
+ pa_log("Failed to open mixer '%s' for jack detection", jack->mixer_device_name);
+ continue;
}
- snd_mixer_elem_set_callback(jack->melem, report_jack_state);
- snd_mixer_elem_set_callback_private(jack->melem, u);
- report_jack_state(jack->melem, 0);
}
-
- } else
- pa_log("Failed to open mixer for jack detection");
-
+ pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop);
+ jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0);
+ if (!jack->melem) {
+ pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
+ pa_alsa_jack_set_has_control(jack, false);
+ continue;
+ }
+ snd_mixer_elem_set_callback(jack->melem, report_jack_state);
+ snd_mixer_elem_set_callback_private(jack->melem, u);
+ report_jack_state(jack->melem, 0);
+ }
}
static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) {
@@ -769,6 +796,10 @@ int pa__init(pa_module *m) {
u->use_ucm = true;
u->ucm.core = m->core;
+ u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
+ pa_xfree, (pa_free_cb_t) pa_alsa_mixer_free);
+ u->ucm.mixers = u->mixers; /* alias */
+
if (!(u->modargs = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments.");
goto fail;
@@ -849,7 +880,7 @@ int pa__init(pa_module *m) {
u->profile_set->ignore_dB = ignore_dB;
- pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec);
+ pa_alsa_profile_set_probe(u->profile_set, u->mixers, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec);
pa_alsa_profile_set_dump(u->profile_set);
pa_card_new_data_init(&data);
@@ -900,7 +931,8 @@ int pa__init(pa_module *m) {
* results in an infinite loop of "fill buffer, handle underrun". To work
* around this issue, the suspend_when_unavailable flag is used to stop
* playback when the HDMI cable is unplugged. */
- if (pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) {
+ if (!u->use_ucm &&
+ pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) {
pa_device_port *port;
void *state;
@@ -948,6 +980,16 @@ int pa__init(pa_module *m) {
init_profile(u);
init_eld_ctls(u);
+ /* Remove all probe only mixers */
+ if (u->mixers) {
+ const char *devname;
+ pa_alsa_mixer *pm;
+ void *state;
+ PA_HASHMAP_FOREACH_KV(devname, pm, u->mixers, state)
+ if (pm->used_for_probe_only)
+ pa_hashmap_remove_and_free(u->mixers, devname);
+ }
+
if (reserve)
pa_reserve_wrapper_unref(reserve);
@@ -998,10 +1040,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
goto finish;
- if (u->mixer_fdl)
- pa_alsa_fdlist_free(u->mixer_fdl);
- if (u->mixer_handle)
- snd_mixer_close(u->mixer_handle);
+ if (u->mixers)
+ pa_hashmap_free(u->mixers);
if (u->jacks)
pa_hashmap_free(u->jacks);
diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c
index 4d4beb3..a90c5e4 100644
--- a/src/modules/alsa/module-alsa-sink.c
+++ b/src/modules/alsa/module-alsa-sink.c
@@ -52,7 +52,7 @@ PA_MODULE_USAGE(
"tsched_buffer_size=<buffer size when using timer based scheduling> "
"tsched_buffer_watermark=<lower fill watermark> "
"ignore_dB=<ignore dB information from the device?> "
- "control=<name of mixer control> "
+ "control=<name of mixer control, or name and index separated by a comma> "
"rewind_safeguard=<number of bytes that cannot be rewound> "
"deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> "
"deferred_volume_safety_margin=<usec adjustment depending on volume direction> "
diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c
index 747ba93..d152283 100644
--- a/src/modules/alsa/module-alsa-source.c
+++ b/src/modules/alsa/module-alsa-source.c
@@ -61,7 +61,7 @@ PA_MODULE_USAGE(
"tsched_buffer_size=<buffer size when using timer based scheduling> "
"tsched_buffer_watermark=<upper fill watermark> "
"ignore_dB=<ignore dB information from the device?> "
- "control=<name of mixer control>"
+ "control=<name of mixer control, or name and index separated by a comma>"
"deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> "
"deferred_volume_safety_margin=<usec adjustment depending on volume direction> "
"deferred_volume_extra_delay=<usec adjustment to HW volume changes> "