diff --git a/NEWS b/NEWS
index 782a612e1..72dd76fda 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,154 @@
+PulseAudio 14.2
+
+A bug fix release.
+
+ * Fix port switching when unplugging headphones
+
+Contributors
+
+ Tanu Kaskinen
+
+
+PulseAudio 14.1
+
+A bug fix release.
+
+ * Support upto 8 mixer channels on ALSA devices
+ * Handle ALSA jacks with the same name but different index values
+ * Switch to plugged-in headset when mic availability is unknown
+ * Fix a potential segfault in the Bluetooth oFono HFP backend
+ * Fix a problem with module-ladspa-sink when avoid-resampling=true
+ * Fix database names containing canonical host for meson builds
+
+Contributors
+
+ Arun Raghavan
+ Hui Wang
+ Igor V. Kovalenko
+ Jaroslav Kysela
+ Kai-Heng Feng
+ Patrick Gaskin
+ Tanu Kaskinen
+ morrishoresh
+
+
+PulseAudio 14.0
+
+Changes at a glance:
+
+ * Notes for end users
+ * Significant routing changes to default sinks/sources
+ * Changing the default sink moves streams from the old default sink to the new
+ * Moving a stream to the default sink removes the "manually routed" status of the stream
+ * If a sink changes status to available, streams that prefer that sink move there automatically
+ * The same changes have been applied to the source stream routing
+ * Workaround for GNOME Sound Settings' stream routing behaviour
+ * module-rescue-streams is deprecated, functionality moved to the core
+ * New rescue-streams option in daemon.conf
+ * Automatic switching to HDMI is now disabled by default
+ * Better support for some USB gaming headsets
+ * Flat volumes are now disabled by default
+ * The RAOP sink can be configured to automatically reconnect on connection failures
+ * Separate sink_channels and source_channels for module-jackdbus-detect
+ * Improved support for ALSA UCM
+ * Support for ALSA mixer controls with non-zero index
+ * It's now possible to set intended roles for devices in the ALSA profile configuration
+ * Ports now have a type associated with them
+ * Mappings have a new "description-key" option in the ALSA profile configuration
+ * New xauthority argument for X11 modules
+ * module-null-sink's compressed format support can now be configured at run-time
+ * The enable-lfe-remixing option in daemon.conf was split into remixing-produce-lfe and remixing-consume-lfe
+ * New channel_map argument for module-raop-sink
+ * Notes for application developers
+ * pa_mainloop_prepare interprets the timeout argument as microseconds again
+ * New availability_group and type fields in the port info structs
+ * New macros: PA_LIKELY(), PA_UNLIKELY(), PA_CLAMP() and PA_CLAMP_UNLIKELY()
+ * Notes for packagers
+ * New GStreamer-based RTP implementation
+ * qpaeq switched from Python 2 to Python 3
+ * Compile-time option to forget pre-14.0 stream routing
+ * The install path of the ALSA configuration files is now configurable
+ * GNU gettext minimum version requirement bumped from 0.19.3 to 0.19.8
+ * Heads-up: dropping autotools build system
+ * Heads-up: dropping EsounD support is considered, tell us if you still need it
+ * Heads-up: dropping GConf support
+
+Detailed change log:
+
+ https://www.freedesktop.org/wiki/Software/PulseAudio/Notes/14.0/
+
+Contributors
+
+ Alexander E. Patrakov
+ Arun Raghavan
+ Balázs Meskó
+ Baurzhan Muftakhidinov
+ Ben Buchwald
+ Daniil Kovalev
+ Dave Chiluk
+ David Heidelberg
+ Dusan Kazik
+ Eero Nurkkala
+ Emanuil Novachev
+ Emilio Herrera
+ Felipe Sateler
+ Felix Yan
+ Geert Warrink
+ Georg Chini
+ Göran Uddeborg
+ Hugo Osvaldo Barrera
+ Hui Wang
+ Igor V. Kovalenko
+ Jan Alexander Steffens
+ Jarno Suni
+ Jaroslav Kysela
+ Jaska Uimonen
+ Jean-Baptiste Holcroft
+ Josh
+ Juliano de Souza Camargo
+ Kai-Heng Feng
+ Karl Ove Hufthammer
+ Khem Raj
+ Krzysztof Stasiowski
+ Laurent Bigonville
+ Libin Yang
+ Marc Ranolfi
+ Michael Pivonka
+ Milo Casagrande
+ Milo Ivir
+ Nick Moriarty
+ Oğuz Ersen
+ Pali Rohár
+ Peter Levine
+ Peter Meerwald
+ Philip Withnall
+ Piotr Drąg
+ RODRIGUEZ Christophe
+ Rafael Fontenelle
+ Ralph Seichter
+ Rasmus Thomsen
+ Rickie Schroeder
+ Rosen Penev
+ Ryszard Knop
+ Sanchayan Maity
+ Sebastian Dröge
+ Sebastien
+ StefanBruens
+ Taahir Ahmed
+ Tanu Kaskinen
+ Timo Gurr
+ Tom Yan
+ Tomasz Kontusz
+ Vasilis Tsiligiannis
+ Wim Taymans
+ Yi-Jyun Pan
+ Yuri Chornoivan
+ itsthem
+ muzena
+ roshal
+ zhaochengyi
+
+
PulseAudio 13.0
Changes at a glance:
diff --git a/configure.ac b/configure.ac
index a519e63d7..680989708 100644
--- a/configure.ac
+++ b/configure.ac
@@ -56,9 +56,6 @@ AC_SUBST(LIBPULSE_SIMPLE_VERSION_INFO, [1:1:1])
# info x:y:z always will hold x=z
AC_SUBST(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO, [0:6:0])
-AC_CANONICAL_HOST
-AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.])
-
AC_CHECK_PROG([STOW], [stow], [yes], [no])
AS_IF([test "x$STOW" = "xyes" && test -d /usr/local/stow], [
diff --git a/meson.build b/meson.build
index 658eeee57..a5263215c 100644
--- a/meson.build
+++ b/meson.build
@@ -125,7 +125,6 @@ cdata = configuration_data()
cdata.set_quoted('PACKAGE', 'pulseaudio')
cdata.set_quoted('PACKAGE_NAME', 'pulseaudio')
cdata.set_quoted('PACKAGE_VERSION', pa_version_str)
-cdata.set_quoted('CANONICAL_HOST', host_machine.cpu())
cdata.set('PA_MAJOR', pa_version_major)
cdata.set('PA_MINOR', pa_version_minor)
cdata.set('PA_API_VERSION', pa_api_version)
diff --git a/src/Makefile.am b/src/Makefile.am
index bd764037b..454b644bb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1037,7 +1037,7 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \
pulsecore/source.c pulsecore/source.h \
pulsecore/start-child.c pulsecore/start-child.h \
pulsecore/thread-mq.c pulsecore/thread-mq.h \
- pulsecore/database.h
+ pulsecore/database.c pulsecore/database.h
libpulsecore_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(LIBSNDFILE_CFLAGS) $(WINSOCK_CFLAGS)
libpulsecore_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
diff --git a/src/daemon/main.c b/src/daemon/main.c
index 59f931219..30ef49964 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -916,7 +916,6 @@ int main(int argc, char *argv[]) {
pa_set_env_and_record("PULSE_SYSTEM", conf->system_instance ? "1" : "0");
pa_log_info("This is PulseAudio %s", PACKAGE_VERSION);
- pa_log_debug("Compilation host: %s", CANONICAL_HOST);
pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
#ifdef HAVE_LIBSAMPLERATE
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 063179052..e494a1282 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -113,7 +113,7 @@ struct description2_map {
pa_device_port_type_t type;
};
-static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) {
+char *pa_alsa_mixer_id_to_string(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 {
@@ -153,7 +153,7 @@ static int alsa_id_decode(const char *src, char *name, int *index) {
return 0;
}
-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) {
+pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index) {
pa_alsa_jack *jack;
pa_assert(name);
@@ -162,7 +162,8 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name
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->alsa_id.name = pa_sprintf_malloc("%s Jack", name);
+ jack->alsa_id.index = index;
jack->state_unplugged = PA_AVAILABLE_NO;
jack->state_plugged = PA_AVAILABLE_YES;
jack->ucm_devices = pa_dynarray_new(NULL);
@@ -177,7 +178,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) {
pa_dynarray_free(jack->ucm_hw_mute_devices);
pa_dynarray_free(jack->ucm_devices);
- pa_xfree(jack->alsa_name);
+ pa_xfree(jack->alsa_id.name);
pa_xfree(jack->name);
pa_xfree(jack->mixer_device_name);
pa_xfree(jack);
@@ -689,6 +690,20 @@ static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_M
[PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
};
+static snd_mixer_selem_channel_id_t alsa_channel_positions[POSITION_MASK_CHANNELS] = {
+ SND_MIXER_SCHN_FRONT_LEFT,
+ SND_MIXER_SCHN_FRONT_RIGHT,
+ SND_MIXER_SCHN_REAR_LEFT,
+ SND_MIXER_SCHN_REAR_RIGHT,
+ SND_MIXER_SCHN_FRONT_CENTER,
+ SND_MIXER_SCHN_WOOFER,
+ SND_MIXER_SCHN_SIDE_LEFT,
+ SND_MIXER_SCHN_SIDE_RIGHT,
+#if POSITION_MASK_CHANNELS > 8
+#error "Extend alsa_channel_positions[] array (9+)"
+#endif
+};
+
static void setting_free(pa_alsa_setting *s) {
pa_assert(s);
@@ -821,7 +836,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -847,14 +862,14 @@ 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_alsa_mixer_id_to_string(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.", 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_alsa_mixer_id_to_string(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.", buf, c,
e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
@@ -877,14 +892,14 @@ 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_alsa_mixer_id_to_string(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.", 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_alsa_mixer_id_to_string(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.", buf, c,
e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
@@ -992,7 +1007,7 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) {
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1158,7 +1173,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1350,7 +1365,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1361,7 +1376,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
r = snd_mixer_selem_set_capture_switch_all(me, b);
if (r < 0) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
}
@@ -1405,7 +1420,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -1450,7 +1465,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
}
if (r < 0) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno));
}
@@ -1656,19 +1671,19 @@ 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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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_alsa_mixer_id_to_string(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, buf, e->min_volume, e->max_volume);
return false;
@@ -1676,7 +1691,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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);
@@ -1703,19 +1718,19 @@ 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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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_alsa_mixer_id_to_string(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.", buf, min_dB / 100.0, max_dB / 100.0,
@@ -1738,7 +1753,7 @@ 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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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.",
buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
@@ -1750,7 +1765,7 @@ 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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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
@@ -1767,7 +1782,11 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
if (is_mono) {
e->n_channels = 1;
- if (!e->override_map) {
+ if ((e->override_map & (1 << (e->n_channels-1))) && e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] == 0) {
+ pa_log_warn("Override map for mono element %s is invalid, ignoring override map", e->path->name);
+ e->override_map &= ~(1 << (e->n_channels-1));
+ }
+ if (!(e->override_map & (1 << (e->n_channels-1)))) {
for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
continue;
@@ -1791,27 +1810,28 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
}
if (e->n_channels <= 0) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Volume element %s with no channels?", buf);
return false;
- } else if (e->n_channels > 2) {
+ } else if (e->n_channels > POSITION_MASK_CHANNELS) {
/* FIXME: In some places code like this is used:
*
* e->masks[alsa_channel_ids[p]][e->n_channels-1]
*
* The definition of e->masks is
*
- * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2];
+ * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS];
*
- * Since the array size is fixed at 2, we obviously
- * don't support elements with more than two
+ * Since the array size is fixed at POSITION_MASK_CHANNELS, we obviously
+ * don't support elements with more than POSITION_MASK_CHANNELS
* channels... */
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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;
}
- if (!e->override_map) {
+retry:
+ if (!(e->override_map & (1 << (e->n_channels-1)))) {
for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
bool has_channel;
@@ -1834,6 +1854,17 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1];
}
+
+ if (e->merged_mask == 0) {
+ if (!(e->override_map & (1 << (e->n_channels-1)))) {
+ pa_log_warn("Channel map for element %s is invalid", e->path->name);
+ return false;
+ }
+ pa_log_warn("Override map for element %s has empty result, ignoring override map", e->path->name);
+ e->override_map &= ~(1 << (e->n_channels-1));
+ goto retry;
+ }
+
return true;
}
@@ -1943,12 +1974,12 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m)
}
new_name = pa_sprintf_malloc("%s,pcm=%i Jack", j->name, mapping->hw_device_index);
- pa_xfree(j->alsa_name);
- j->alsa_name = new_name;
+ pa_xfree(j->alsa_id.name);
+ j->alsa_id.name = new_name;
j->append_pcm_to_name = false;
}
- has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL;
+ has_control = pa_alsa_mixer_find_card(m, &j->alsa_id, 0) != NULL;
pa_alsa_jack_set_has_control(j, has_control);
if (j->has_control) {
@@ -2011,19 +2042,26 @@ finish:
static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
pa_alsa_jack *j;
+ char *name;
+ int index;
if (!pa_startswith(section, "Jack "))
return NULL;
section += 5;
- if (p->last_jack && pa_streq(p->last_jack->name, section))
+ name = alloca(strlen(section) + 1);
+ if (alsa_id_decode(section, name, &index))
+ return NULL;
+
+ if (p->last_jack && pa_streq(p->last_jack->name, name) &&
+ p->last_jack->alsa_id.index == index)
return p->last_jack;
PA_LLIST_FOREACH(j, p->jacks)
- if (pa_streq(j->name, section))
+ if (pa_streq(j->name, name) && j->alsa_id.index == index)
goto finish;
- j = pa_alsa_jack_new(p, NULL, section);
+ j = pa_alsa_jack_new(p, NULL, name, index);
PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j);
finish:
@@ -2427,6 +2465,16 @@ static int element_parse_volume_limit(pa_config_parser_state *state) {
return 0;
}
+static unsigned int parse_channel_position(const char *m)
+{
+ pa_channel_position_t p;
+
+ if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID)
+ return SND_MIXER_SCHN_UNKNOWN;
+
+ return alsa_channel_ids[p];
+}
+
static pa_channel_position_mask_t parse_mask(const char *m) {
pa_channel_position_mask_t v;
@@ -2464,7 +2512,9 @@ static int element_parse_override_map(pa_config_parser_state *state) {
pa_alsa_path *p;
pa_alsa_element *e;
const char *split_state = NULL;
+ char *s;
unsigned i = 0;
+ int channel_count = 0;
char *n;
pa_assert(state);
@@ -2476,31 +2526,60 @@ static int element_parse_override_map(pa_config_parser_state *state) {
return -1;
}
+ s = strstr(state->lvalue, ".");
+ if (s) {
+ pa_atoi(s + 1, &channel_count);
+ if (channel_count < 1 || channel_count > POSITION_MASK_CHANNELS) {
+ pa_log("[%s:%u] Override map index '%s' invalid in '%s'", state->filename, state->lineno, state->lvalue, state->section);
+ return 0;
+ }
+ } else {
+ pa_log("[%s:%u] Invalid override map syntax '%s' in '%s'", state->filename, state->lineno, state->lvalue, state->section);
+ return -1;
+ }
+
while ((n = pa_split(state->rvalue, ",", &split_state))) {
pa_channel_position_mask_t m;
+ snd_mixer_selem_channel_id_t channel_position;
+
+ if (i >= (unsigned)channel_count) {
+ pa_log("[%s:%u] Invalid override map size (>%d) in '%s'", state->filename, state->lineno, channel_count, state->section);
+ return -1;
+ }
+ channel_position = alsa_channel_positions[i];
if (!*n)
m = 0;
else {
- if ((m = parse_mask(n)) == 0) {
- pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, n, state->section);
+ s = strstr(n, ":");
+ if (s) {
+ *s = '\0';
+ s++;
+ channel_position = parse_channel_position(n);
+ if (channel_position == SND_MIXER_SCHN_UNKNOWN) {
+ pa_log("[%s:%u] Override map position '%s' invalid in '%s'", state->filename, state->lineno, n, state->section);
+ pa_xfree(n);
+ return -1;
+ }
+ }
+ if ((m = parse_mask(s ? s : n)) == 0) {
+ pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, s ? s : n, state->section);
pa_xfree(n);
return -1;
}
}
- if (pa_streq(state->lvalue, "override-map.1"))
- e->masks[i++][0] = m;
- else
- e->masks[i++][1] = m;
-
- /* Later on we might add override-map.3 and so on here ... */
-
+ if (e->masks[channel_position][channel_count-1]) {
+ pa_log("[%s:%u] Override map '%s' duplicate position '%s' in '%s'", state->filename, state->lineno, s ? s : n, snd_mixer_selem_channel_name(channel_position), state->section);
+ pa_xfree(n);
+ return -1;
+ }
+ e->override_map |= (1 << (channel_count - 1));
+ e->masks[channel_position][channel_count-1] = m;
pa_xfree(n);
+ i++;
}
- e->override_map = true;
-
return 0;
}
@@ -2574,7 +2653,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return -1;
}
@@ -2587,7 +2666,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
if (r < 0) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
}
@@ -2595,7 +2674,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno));
}
}
@@ -2652,7 +2731,7 @@ 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) {
- alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id);
pa_log("Element %s of option %s not set for select.", buf, o->name);
return -1;
}
@@ -2660,7 +2739,7 @@ static int option_verify(pa_alsa_option *o) {
if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
!pa_streq(o->alsa_name, "on") &&
!pa_streq(o->alsa_name, "off")) {
- alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id);
pa_log("Switch %s options need be named off or on ", buf);
return -1;
}
@@ -2686,13 +2765,13 @@ 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)) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(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) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log("Element %s cannot set select for both switch and enumeration.", buf);
return -1;
}
@@ -2718,6 +2797,7 @@ static int path_verify(pa_alsa_path *p) {
{ "analog-input-video", N_("Video"), PA_DEVICE_PORT_TYPE_VIDEO },
{ "analog-output", N_("Analog Output"), PA_DEVICE_PORT_TYPE_ANALOG },
{ "analog-output-headphones", N_("Headphones"), PA_DEVICE_PORT_TYPE_HEADPHONES },
+ { "analog-output-headphones-2", N_("Headphones 2"), PA_DEVICE_PORT_TYPE_HEADPHONES },
{ "analog-output-headphones-mono", N_("Headphones Mono Output"), PA_DEVICE_PORT_TYPE_HEADPHONES },
{ "analog-output-lineout", N_("Line Out"), PA_DEVICE_PORT_TYPE_LINE },
{ "analog-output-mono", N_("Analog Mono Output"), PA_DEVICE_PORT_TYPE_ANALOG },
@@ -2800,6 +2880,15 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa
{ "enumeration", element_parse_enumeration, NULL, NULL },
{ "override-map.1", element_parse_override_map, NULL, NULL },
{ "override-map.2", element_parse_override_map, NULL, NULL },
+ { "override-map.3", element_parse_override_map, NULL, NULL },
+ { "override-map.4", element_parse_override_map, NULL, NULL },
+ { "override-map.5", element_parse_override_map, NULL, NULL },
+ { "override-map.6", element_parse_override_map, NULL, NULL },
+ { "override-map.7", element_parse_override_map, NULL, NULL },
+ { "override-map.8", element_parse_override_map, NULL, NULL },
+#if POSITION_MASK_CHANNELS > 8
+#error "Add override-map.9+ definitions"
+#endif
/* ... later on we might add override-map.3 and so on here ... */
{ "required", element_parse_required, NULL, NULL },
{ "required-any", element_parse_required, NULL, NULL },
@@ -3017,6 +3106,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;
+ bool min_dB_set, max_dB_set;
char buf[64];
pa_assert(p);
@@ -3032,22 +3122,23 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
pa_log_debug("Probing path '%s'", p->name);
PA_LLIST_FOREACH(j, p->jacks) {
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &j->alsa_id);
if (jack_probe(j, mapping, m) < 0) {
p->supported = false;
- pa_log_debug("Probe of jack '%s' failed.", j->alsa_name);
+ pa_log_debug("Probe of jack %s failed.", buf);
return -1;
}
- pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found");
+ pa_log_debug("Probe of jack %s succeeded (%s)", buf, j->has_control ? "found!" : "not found");
}
PA_LLIST_FOREACH(e, p->elements) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
if (element_probe(e, m) < 0) {
p->supported = false;
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).", buf, e->volume_use, e->switch_use, e->enumeration_use);
+ pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d, has_dB=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use, e->has_dB);
if (ignore_dB)
e->has_dB = false;
@@ -3111,18 +3202,30 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
p->supported = true;
p->min_dB = INFINITY;
+ min_dB_set = false;
p->max_dB = -INFINITY;
+ max_dB_set = false;
for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) {
if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) {
- if (p->min_dB > min_dB[t])
+ if (p->min_dB > min_dB[t]) {
p->min_dB = min_dB[t];
+ min_dB_set = true;
+ }
- if (p->max_dB < max_dB[t])
+ if (p->max_dB < max_dB[t]) {
p->max_dB = max_dB[t];
+ max_dB_set = true;
+ }
}
}
+ /* this is probably a wrong prediction, but it should be safe */
+ if (!min_dB_set)
+ p->min_dB = -INFINITY;
+ if (!max_dB_set)
+ p->max_dB = 0;
+
return 0;
}
@@ -3138,7 +3241,7 @@ void pa_alsa_setting_dump(pa_alsa_setting *s) {
void pa_alsa_jack_dump(pa_alsa_jack *j) {
pa_assert(j);
- pa_log_debug("Jack %s, alsa_name='%s', detection %s", j->name, j->alsa_name, j->has_control ? "possible" : "unavailable");
+ pa_log_debug("Jack %s, alsa_name='%s', index='%d', detection %s", j->name, j->alsa_id.name, j->alsa_id.index, j->has_control ? "possible" : "unavailable");
}
void pa_alsa_option_dump(pa_alsa_option *o) {
@@ -3158,8 +3261,8 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
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",
+ pa_alsa_mixer_id_to_string(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=%02x",
buf,
e->direction,
e->switch_use,
@@ -3171,7 +3274,7 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
e->required_absent,
(long long unsigned) e->merged_mask,
e->n_channels,
- pa_yes_no(e->override_map));
+ e->override_map);
PA_LLIST_FOREACH(o, e->options)
pa_alsa_option_dump(o);
@@ -3218,7 +3321,7 @@ static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_e
SELEM_INIT(sid, &e->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return;
}
@@ -3513,7 +3616,7 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
SELEM_INIT(sid, &a->alsa_id);
if (!(me = snd_mixer_find_selem(m, sid))) {
- alsa_id_str(buf, sizeof(buf), &a->alsa_id);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id);
pa_log_warn("Element %s seems to have disappeared.", buf);
return false;
}
@@ -3544,7 +3647,7 @@ 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_alsa_mixer_id_to_string(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",
buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
return false;
@@ -3621,7 +3724,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
continue;
PA_LLIST_FOREACH(jb, p2->jacks) {
- if (jb->has_control && pa_streq(jb->alsa_name, ja->alsa_name) &&
+ if (jb->has_control && pa_streq(ja->alsa_id.name, jb->alsa_id.name) &&
+ (ja->alsa_id.index == jb->alsa_id.index) &&
(ja->state_plugged == jb->state_plugged) &&
(ja->state_unplugged == jb->state_unplugged)) {
exists = true;
@@ -4311,7 +4415,8 @@ static void profile_set_set_availability_groups(pa_alsa_profile_set *ps) {
PA_LLIST_FOREACH(j2, p2->jacks) {
if (!j2->has_control || j2->state_plugged == PA_AVAILABLE_NO)
continue;
- if (pa_streq(j->alsa_name, j2->alsa_name)) {
+ if (pa_streq(j->alsa_id.name, j2->alsa_id.name) &&
+ j->alsa_id.index == j2->alsa_id.index) {
j->state_plugged = PA_AVAILABLE_UNKNOWN;
j2->state_plugged = PA_AVAILABLE_UNKNOWN;
found = p2->availability_group;
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 905e3128b..db8310258 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -50,6 +50,8 @@ typedef struct pa_alsa_port_data pa_alsa_port_data;
#include "alsa-util.h"
#include "alsa-ucm.h"
+#define POSITION_MASK_CHANNELS 8
+
typedef enum pa_alsa_switch_use {
PA_ALSA_SWITCH_IGNORE,
PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */
@@ -113,6 +115,8 @@ struct pa_alsa_mixer_id {
int index;
};
+char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id);
+
/* 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. */
@@ -152,7 +156,7 @@ struct pa_alsa_element {
long constant_volume;
- bool override_map:1;
+ unsigned int override_map;
bool direction_try_other:1;
bool has_dB:1;
@@ -160,7 +164,7 @@ struct pa_alsa_element {
long volume_limit; /* -1 for no configured limit */
double min_dB, max_dB;
- pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2];
+ pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS];
unsigned n_channels;
pa_channel_position_mask_t merged_mask;
@@ -177,8 +181,8 @@ struct pa_alsa_jack {
snd_mixer_t *mixer_handle;
char *mixer_device_name;
+ struct pa_alsa_mixer_id alsa_id;
char *name; /* E g "Headphone" */
- char *alsa_name; /* E g "Headphone Jack" */
bool has_control; /* is the jack itself present? */
bool plugged_in; /* is this jack currently plugged in? */
snd_mixer_elem_t *melem; /* Jack detection handle */
@@ -194,7 +198,7 @@ struct pa_alsa_jack {
bool append_pcm_to_name;
};
-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name);
+pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index);
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);
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index 18925b792..d9cea6105 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -1719,7 +1719,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
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);
+ j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0);
PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
finish:
@@ -1953,7 +1953,7 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) {
continue;
}
- has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL;
+ has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 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);
}
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index bf35b6112..172a7bb51 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1635,8 +1635,8 @@ static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer,
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_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) {
+ return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device);
}
snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) {
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index cb8be22fb..2eed3eac3 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -148,7 +148,7 @@ const char* pa_alsa_strerror(int errnum);
bool pa_alsa_may_tsched(bool want);
-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_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, 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);
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
index 30815d0a8..178999088 100644
--- a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
@@ -13,17 +13,24 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see .
-; Path for mixers that have a 'Headphone2' control
+; Path for the second headphone output on dual-headphone machines.
;
; See analog-output.conf.common for an explanation on the directives
[General]
priority = 98
-description-key = analog-output-headphones
[Properties]
device.icon_name = audio-headphones
+; HP EliteDesk 800 SFF Headphone
+[Jack Front Headphone,1]
+required-any = any
+
+; HP EliteDesk 800 DM Headphone
+[Jack Front Headphone Surround]
+required-any = any
+
[Element Hardware Master]
switch = mute
volume = merge
@@ -47,6 +54,13 @@ volume = off
switch = mute
volume = zero
+[Element Headphone,1]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
[Element Headphone+LO]
switch = mute
volume = zero
@@ -56,7 +70,7 @@ switch = off
volume = off
[Element Headphone2]
-required = any
+required-any = any
switch = mute
volume = merge
override-map.1 = all
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
index d4ad7777d..88907f081 100644
--- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
@@ -35,6 +35,10 @@ state.unplugged = unknown
[Jack Front Headphone]
required-any = any
+; HP EliteDesk 800 DM Headset
+[Jack Front Headphone Front]
+required-any = any
+
[Jack Front Headphone Phantom]
required-any = any
state.plugged = unknown
@@ -89,6 +93,13 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
+; This path is intended to control the first headphones, not
+; the second headphones. But it should not hurt if we leave the second
+; headphone jack enabled nonetheless.
+[Element Headphone,1]
+switch = mute
+volume = zero
+
[Element Headset]
required-any = any
switch = mute
diff --git a/src/modules/alsa/mixer/paths/analog-output-lineout.conf b/src/modules/alsa/mixer/paths/analog-output-lineout.conf
index 9a6af3ad8..2dde15923 100644
--- a/src/modules/alsa/mixer/paths/analog-output-lineout.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-lineout.conf
@@ -127,6 +127,10 @@ required-any = any
switch = off
volume = off
+[Element Headphone,1]
+switch = off
+volume = off
+
[Element Headphone2]
switch = off
volume = off
diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf
index 989654334..5e4940598 100644
--- a/src/modules/alsa/mixer/paths/analog-output-mono.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf
@@ -44,6 +44,10 @@ override-map.2 = all-left,all-right
switch = mute
volume = zero
+[Element Headphone,1]
+switch = mute
+volume = zero
+
[Element Headphone+LO]
switch = mute
volume = zero
diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf
index 71f356dce..4ee72f504 100644
--- a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf
@@ -76,6 +76,10 @@ volume = off
switch = mute
volume = zero
+[Element Headphone,1]
+switch = mute
+volume = zero
+
[Element Headphone2]
switch = mute
volume = zero
diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
index 27a3983d5..fcf2f5c2f 100644
--- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
@@ -94,6 +94,10 @@ volume = off
switch = off
volume = off
+[Element Headphone,1]
+switch = off
+volume = off
+
[Element Headphone2]
switch = off
volume = off
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index de2fe9cc4..08e655e26 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -621,6 +621,7 @@ static void init_jacks(struct userdata *u) {
void *state;
pa_alsa_path* path;
pa_alsa_jack* jack;
+ char buf[64];
u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
@@ -663,9 +664,10 @@ static void init_jacks(struct userdata *u) {
}
}
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);
+ jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0);
if (!jack->melem) {
- pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
+ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id);
+ pa_log_warn("Jack %s seems to have disappeared.", buf);
pa_alsa_jack_set_has_control(jack, false);
continue;
}
diff --git a/src/modules/bluetooth/backend-ofono.c b/src/modules/bluetooth/backend-ofono.c
index 0e5bbe8b7..d7a13efd0 100644
--- a/src/modules/bluetooth/backend-ofono.c
+++ b/src/modules/bluetooth/backend-ofono.c
@@ -627,8 +627,6 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
card = pa_hashmap_get(backend->cards, path);
- card->connecting = false;
-
if (!card || codec != HFP_AUDIO_CODEC_CVSD || card->fd >= 0) {
pa_log_warn("New audio connection invalid arguments (path=%s fd=%d, codec=%d)", path, fd, codec);
pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Invalid arguments in method call"));
@@ -639,6 +637,7 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", path, fd, codec);
+ card->connecting = false;
card->fd = fd;
card->transport->codec = codec;
diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c
index 80506cd04..b35cf3e7e 100644
--- a/src/modules/module-card-restore.c
+++ b/src/modules/module-card-restore.c
@@ -618,7 +618,7 @@ static pa_hook_result_t card_preferred_port_changed_callback(pa_core *core, pa_c
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
- char *fname;
+ char *state_path;
bool restore_bluetooth_profile;
pa_assert(m);
@@ -648,17 +648,15 @@ int pa__init(pa_module*m) {
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_added_callback, u);
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) port_offset_change_callback, u);
- if (!(fname = pa_state_path("card-database", true)))
+ if (!(state_path = pa_state_path(NULL, true)))
goto fail;
- if (!(u->database = pa_database_open(fname, true))) {
- pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
- pa_xfree(fname);
+ if (!(u->database = pa_database_open(state_path, "card-database", true, true))) {
+ pa_xfree(state_path);
goto fail;
}
- pa_log_info("Successfully opened database file '%s'.", fname);
- pa_xfree(fname);
+ pa_xfree(state_path);
pa_modargs_free(ma);
return 0;
diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c
index 308ef0b57..b51b6c8a0 100644
--- a/src/modules/module-device-manager.c
+++ b/src/modules/module-device-manager.c
@@ -1544,7 +1544,7 @@ struct prioritised_indexes {
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
- char *fname;
+ char *state_path;
pa_sink *sink;
pa_source *source;
uint32_t idx;
@@ -1601,17 +1601,15 @@ int pa__init(pa_module*m) {
u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u);
}
- if (!(fname = pa_state_path("device-manager", true)))
+ if (!(state_path = pa_state_path(NULL, true)))
goto fail;
- if (!(u->database = pa_database_open(fname, true))) {
- pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
- pa_xfree(fname);
+ if (!(u->database = pa_database_open(state_path, "device-manager", true, true))) {
+ pa_xfree(state_path);
goto fail;
}
- pa_log_info("Successfully opened database file '%s'.", fname);
- pa_xfree(fname);
+ pa_xfree(state_path);
/* Attempt to inject the devices into the list in priority order */
total_devices = PA_MAX(pa_idxset_size(m->core->sinks), pa_idxset_size(m->core->sources));
diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
index d15d9ffa3..a861f6f18 100644
--- a/src/modules/module-device-restore.c
+++ b/src/modules/module-device-restore.c
@@ -1195,7 +1195,7 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
- char *fname;
+ char *state_path;
pa_sink *sink;
pa_source *source;
uint32_t idx;
@@ -1252,17 +1252,15 @@ int pa__init(pa_module*m) {
if (restore_formats)
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_put_hook_callback, u);
- if (!(fname = pa_state_path("device-volumes", true)))
+ if (!(state_path = pa_state_path(NULL, true)))
goto fail;
- if (!(u->database = pa_database_open(fname, true))) {
- pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
- pa_xfree(fname);
+ if (!(u->database = pa_database_open(state_path, "device-volumes", true, true))) {
+ pa_xfree(state_path);
goto fail;
}
- pa_log_info("Successfully opened database file '%s'.", fname);
- pa_xfree(fname);
+ pa_xfree(state_path);
PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c
index 4cfe3ed48..f8a9e9514 100644
--- a/src/modules/module-equalizer-sink.c
+++ b/src/modules/module-equalizer-sink.c
@@ -946,7 +946,7 @@ static void save_state(struct userdata *u) {
float *H;
pa_datum key, data;
pa_database *database;
- char *dbname;
+ char *state_path;
char *packed;
size_t packed_length;
@@ -969,9 +969,9 @@ static void save_state(struct userdata *u) {
data.data = state;
data.size = filter_state_size + packed_length;
//thread safety for 0.9.17?
- pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, false));
- pa_assert_se(database = pa_database_open(dbname, true));
- pa_xfree(dbname);
+ pa_assert_se(state_path = pa_state_path(NULL, false));
+ pa_assert_se(database = pa_database_open(state_path, EQ_STATE_DB, false, true));
+ pa_xfree(state_path);
pa_database_set(database, &key, &data, true);
pa_database_sync(database);
@@ -1020,10 +1020,10 @@ static void load_state(struct userdata *u) {
float *H;
pa_datum key, value;
pa_database *database;
- char *dbname;
- pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, false));
- database = pa_database_open(dbname, false);
- pa_xfree(dbname);
+ char *state_path;
+ pa_assert_se(state_path = pa_state_path(NULL, false));
+ database = pa_database_open(state_path, EQ_STATE_DB, false, false);
+ pa_xfree(state_path);
if (!database) {
pa_log("No resume state");
return;
@@ -1626,12 +1626,12 @@ void dbus_init(struct userdata *u) {
sink_list = pa_shared_get(u->sink->core, SINKLIST);
u->database = pa_shared_get(u->sink->core, EQDB);
if (sink_list == NULL) {
- char *dbname;
+ char *state_path;
sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func);
pa_shared_set(u->sink->core, SINKLIST, sink_list);
- pa_assert_se(dbname = pa_state_path("equalizer-presets", false));
- pa_assert_se(u->database = pa_database_open(dbname, true));
- pa_xfree(dbname);
+ pa_assert_se(state_path = pa_state_path(NULL, false));
+ pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true));
+ pa_xfree(state_path);
pa_shared_set(u->sink->core, EQDB, u->database);
pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core);
pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME);
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index f4fed6d64..5726d0818 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -714,6 +714,9 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, p
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
+ if (!PA_SINK_IS_LINKED(u->sink->state))
+ return;
+
if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE)
pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE);
else
diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
index 7144a664b..c10310d03 100644
--- a/src/modules/module-stream-restore.c
+++ b/src/modules/module-stream-restore.c
@@ -2259,7 +2259,7 @@ static void clean_up_db(struct userdata *u) {
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
- char *fname;
+ char *state_path;
pa_sink_input *si;
pa_source_output *so;
uint32_t idx;
@@ -2317,17 +2317,15 @@ int pa__init(pa_module*m) {
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_fixate_hook_callback, u);
}
- if (!(fname = pa_state_path("stream-volumes", true)))
+ if (!(state_path = pa_state_path(NULL, true)))
goto fail;
- if (!(u->database = pa_database_open(fname, true))) {
- pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
- pa_xfree(fname);
+ if (!(u->database = pa_database_open(state_path, "stream-volumes", true, true))) {
+ pa_xfree(state_path);
goto fail;
}
- pa_log_info("Successfully opened database file '%s'.", fname);
- pa_xfree(fname);
+ pa_xfree(state_path);
clean_up_db(u);
diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
index 84b856659..99d61a4b8 100644
--- a/src/modules/module-switch-on-port-available.c
+++ b/src/modules/module-switch-on-port-available.c
@@ -228,9 +228,7 @@ static struct port_pointers find_port_pointers(pa_device_port *port) {
}
/* Switches to a port, switching profiles if necessary or preferred */
-static void switch_to_port(pa_device_port *port) {
- struct port_pointers pp = find_port_pointers(port);
-
+static void switch_to_port(pa_device_port *port, struct port_pointers pp) {
if (pp.is_port_active)
return; /* Already selected */
@@ -252,8 +250,7 @@ static void switch_to_port(pa_device_port *port) {
}
/* Switches away from a port, switching profiles if necessary or preferred */
-static void switch_from_port(pa_device_port *port) {
- struct port_pointers pp = find_port_pointers(port);
+static void switch_from_port(pa_device_port *port, struct port_pointers pp) {
pa_device_port *p, *best_port = NULL;
void *state;
@@ -281,13 +278,15 @@ static void switch_from_port(pa_device_port *port) {
* profile is still available in the
* PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED callback, as at this point
* the profile availability hasn't been updated yet. */
- if (best_port)
- switch_to_port(best_port);
+ if (best_port) {
+ struct port_pointers best_pp = find_port_pointers(best_port);
+ switch_to_port(best_port, best_pp);
+ }
}
static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) {
- pa_assert(port);
+ struct port_pointers pp = find_port_pointers(port);
if (!port->card) {
pa_log_warn("Port %s does not have a card", port->name);
@@ -314,6 +313,15 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
* functionality for setups that can't trigger this kind of
* interaction.
*
+ * For headset or microphone, if they are part of some availability group
+ * and they become unknown from off, it needs to check if their source is
+ * unlinked or not, if their source is unlinked, let switch_to_port()
+ * process them, then with the running of pa_card_set_profile(), their
+ * source will be created, otherwise the headset or microphone can't be used
+ * to record sound since there is no source for these 2 ports. This issue
+ * is observed on Dell machines which have multi-function audio jack but no
+ * internal mic.
+ *
* We should make this configurable so that users can optionally
* override the default to a headset or mic. */
@@ -323,20 +331,22 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
break;
}
- /* For no we only switch the headphone port */
- if (port->direction != PA_DIRECTION_OUTPUT) {
+ /* Switch the headphone port, the input ports without source and the
+ * input ports their source->active_port is part of a group of ports.
+ */
+ if (port->direction == PA_DIRECTION_INPUT && pp.source && !pp.source->active_port->availability_group) {
pa_log_debug("Not switching to input port %s, its availability is unknown.", port->name);
break;
}
- switch_to_port(port);
+ switch_to_port(port, pp);
break;
case PA_AVAILABLE_YES:
- switch_to_port(port);
+ switch_to_port(port, pp);
break;
case PA_AVAILABLE_NO:
- switch_from_port(port);
+ switch_from_port(port, pp);
break;
default:
break;
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index 5205349bd..58f3d1e2c 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -2074,34 +2074,34 @@ int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *b
}
closedir(d);
+ if ((count = pa_dynarray_size(files))) {
+ sorted_files = pa_xnew(char*, count);
+ for (i = 0; i < count; ++i)
+ sorted_files[i] = pa_dynarray_get(files, i);
+ pa_dynarray_free(files);
- count = pa_dynarray_size(files);
- sorted_files = pa_xnew(char*, count);
- for (i = 0; i < count; ++i)
- sorted_files[i] = pa_dynarray_get(files, i);
- pa_dynarray_free(files);
-
- for (i = 0; i < count; ++i) {
- for (unsigned j = 0; j < count; ++j) {
- if (strcmp(sorted_files[i], sorted_files[j]) < 0) {
- char *tmp = sorted_files[i];
- sorted_files[i] = sorted_files[j];
- sorted_files[j] = tmp;
+ for (i = 0; i < count; ++i) {
+ for (unsigned j = 0; j < count; ++j) {
+ if (strcmp(sorted_files[i], sorted_files[j]) < 0) {
+ char *tmp = sorted_files[i];
+ sorted_files[i] = sorted_files[j];
+ sorted_files[j] = tmp;
+ }
}
}
- }
- for (i = 0; i < count; ++i) {
- if (!failed) {
- if (pa_cli_command_execute_file(c, sorted_files[i], buf, fail) < 0 && *fail)
- failed = true;
+ for (i = 0; i < count; ++i) {
+ if (!failed) {
+ if (pa_cli_command_execute_file(c, sorted_files[i], buf, fail) < 0 && *fail)
+ failed = true;
+ }
+
+ pa_xfree(sorted_files[i]);
}
-
- pa_xfree(sorted_files[i]);
+ pa_xfree(sorted_files);
+ if (failed)
+ return -1;
}
- pa_xfree(sorted_files);
- if (failed)
- return -1;
}
} else if (pa_cli_command_execute_file(c, filename, buf, fail) < 0 && *fail) {
return -1;
diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c
index b1da9df9f..b39f7dee2 100644
--- a/src/pulsecore/database-gdbm.c
+++ b/src/pulsecore/database-gdbm.c
@@ -59,17 +59,16 @@ void pa_datum_free(pa_datum *d) {
pa_zero(d);
}
-pa_database* pa_database_open(const char *fn, bool for_write) {
+const char* pa_database_get_filename_suffix(void) {
+ return ".gdbm";
+}
+
+pa_database* pa_database_open_internal(const char *path, bool for_write) {
GDBM_FILE f;
int gdbm_cache_size;
- char *path;
- pa_assert(fn);
+ pa_assert(path);
- /* We include the host identifier in the file name because gdbm
- * files are CPU dependent, and we don't want things to go wrong
- * if we are on a multiarch system. */
- path = pa_sprintf_malloc("%s."CANONICAL_HOST".gdbm", fn);
errno = 0;
/* We need to set the block size explicitly here, since otherwise
@@ -80,8 +79,6 @@ pa_database* pa_database_open(const char *fn, bool for_write) {
if (f)
pa_log_debug("Opened GDBM database '%s'", path);
- pa_xfree(path);
-
if (!f) {
if (errno == 0)
errno = EIO;
diff --git a/src/pulsecore/database-simple.c b/src/pulsecore/database-simple.c
index 387648769..96af8e098 100644
--- a/src/pulsecore/database-simple.c
+++ b/src/pulsecore/database-simple.c
@@ -222,14 +222,16 @@ static int fill_data(simple_data *db, FILE *f) {
return pa_hashmap_size(db->map);
}
-pa_database* pa_database_open(const char *fn, bool for_write) {
+const char* pa_database_get_filename_suffix(void) {
+ return ".simple";
+}
+
+pa_database* pa_database_open_internal(const char *path, bool for_write) {
FILE *f;
- char *path;
simple_data *db;
- pa_assert(fn);
+ pa_assert(path);
- path = pa_sprintf_malloc("%s."CANONICAL_HOST".simple", fn);
errno = 0;
f = pa_fopen_cloexec(path, "r");
@@ -251,8 +253,6 @@ pa_database* pa_database_open(const char *fn, bool for_write) {
db = NULL;
}
- pa_xfree(path);
-
return (pa_database*) db;
}
diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c
index 282f58061..6605ed8fc 100644
--- a/src/pulsecore/database-tdb.c
+++ b/src/pulsecore/database-tdb.c
@@ -97,18 +97,18 @@ finish:
return c;
}
-pa_database* pa_database_open(const char *fn, bool for_write) {
+const char* pa_database_get_filename_suffix(void) {
+ return ".tdb";
+}
+
+pa_database* pa_database_open_internal(const char *path, bool for_write) {
struct tdb_context *c;
- char *path;
- pa_assert(fn);
+ pa_assert(path);
- path = pa_sprintf_malloc("%s.tdb", fn);
if ((c = tdb_open_cloexec(path, 0, TDB_NOSYNC|TDB_NOLOCK, (for_write ? O_RDWR|O_CREAT : O_RDONLY), 0644)))
pa_log_debug("Opened TDB database '%s'", path);
- pa_xfree(path);
-
if (!c) {
if (errno == 0)
errno = EIO;
diff --git a/src/pulsecore/database.c b/src/pulsecore/database.c
new file mode 100644
index 000000000..8b3b89c56
--- /dev/null
+++ b/src/pulsecore/database.c
@@ -0,0 +1,107 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2020 Igor V. Kovalenko
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see .
+***/
+
+#ifdef HAVE_CONFIG_H
+#include
+#endif
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include "database.h"
+#include "core-error.h"
+
+pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write) {
+
+ const char *filename_suffix = pa_database_get_filename_suffix();
+
+ char *machine_id = NULL, *filename_prefix, *full_path;
+
+ DIR *database_dir = NULL;
+ struct dirent *de;
+
+ pa_database *f;
+
+ pa_assert(filename_suffix && filename_suffix[0]);
+
+ if (prependmid && !(machine_id = pa_machine_id())) {
+ return NULL;
+ }
+
+ /* Database file name starts with ${machine_id}-${fn} */
+ if (machine_id)
+ filename_prefix = pa_sprintf_malloc("%s-%s", machine_id, fn);
+ else
+ filename_prefix = pa_xstrdup(fn);
+
+ /* Search for existing database directory entry name matching architecture suffix and filename suffix. */
+ database_dir = opendir(path);
+
+ if (database_dir) {
+ for (;;) {
+ errno = 0;
+ de = readdir(database_dir);
+ if (!de) {
+ if (errno) {
+ pa_log_warn("Unable to search for existing database file, readdir() failed: %s", pa_cstrerror(errno));
+ /* can continue as if there is no matching database file candidate */
+ }
+
+ break;
+ }
+
+ if (pa_startswith(de->d_name, filename_prefix)
+ && de->d_name[strlen(filename_prefix)] == '.'
+ && pa_endswith(de->d_name + strlen(filename_prefix) + 1, filename_suffix)) {
+
+ /* candidate filename found, replace filename_prefix with this one */
+
+ pa_log_debug("Found existing database file '%s/%s', using it", path, de->d_name);
+ pa_xfree(filename_prefix);
+ filename_prefix = pa_xstrndup(de->d_name, strlen(de->d_name) - strlen(filename_suffix));
+ break;
+ }
+ }
+
+ closedir(database_dir);
+ } else {
+ pa_log_warn("Unable to search for existing database file, failed to open directory %s: %s", path, pa_cstrerror(errno));
+ }
+
+ full_path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s%s", path, filename_prefix, filename_suffix);
+
+ f = pa_database_open_internal(full_path, for_write);
+
+ if (f)
+ pa_log_info("Successfully opened '%s' database file '%s'.", fn, full_path);
+ else
+ pa_log("Failed to open '%s' database file '%s': %s", fn, full_path, pa_cstrerror(errno));
+
+ pa_xfree(full_path);
+ pa_xfree(filename_prefix);
+
+ /* deallocate machine_id if it was used to construct file name */
+ pa_xfree(machine_id);
+
+ return f;
+}
diff --git a/src/pulsecore/database.h b/src/pulsecore/database.h
index 3a1c7ceaf..7fa489c67 100644
--- a/src/pulsecore/database.h
+++ b/src/pulsecore/database.h
@@ -38,8 +38,29 @@ typedef struct pa_datum {
void pa_datum_free(pa_datum *d);
-/* This will append a suffix to the filename */
-pa_database* pa_database_open(const char *fn, bool for_write);
+/* Database implementation; returns non-empty database filename extension string */
+const char* pa_database_get_filename_suffix(void);
+
+/* Opens a database file. The file is loaded from the directory indicated by
+ * path. The file name is constructed by using fn as the base and then adding
+ * several parts:
+ * 1) If prependmid is true, the machine id is prepended to the file name.
+ * 2) The database implementation specific suffix is added.
+ * 3) Older versions of PulseAudio in some cases added the CPU architecture
+ * to the file name, which was later deemed unnecessary, but for
+ * compatibility reasons we still need to look for those files, so we scan
+ * the directory for files that match the prefix (possible machine id plus
+ * fn) and the suffix, and if any matches are found, we use the first one.
+ *
+ * When no existing file is found, we create a new file for the database
+ * (without the CPU architecture part in the name).
+ *
+ * For a read-only database, set for_write to false. */
+
+pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write);
+
+/* Database implementation; opens specified database file using provided path. */
+pa_database* pa_database_open_internal(const char *path, bool for_write);
void pa_database_close(pa_database *db);
pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data);
diff --git a/src/pulsecore/meson.build b/src/pulsecore/meson.build
index 5f78be012..99a702ea1 100644
--- a/src/pulsecore/meson.build
+++ b/src/pulsecore/meson.build
@@ -14,6 +14,7 @@ libpulsecore_sources = [
'cpu-orc.c',
'cpu-x86.c',
'device-port.c',
+ 'database.c',
'ffmpeg/resample2.c',
'filter/biquad.c',
'filter/crossover.c',