Compare commits

...

21 commits

Author SHA1 Message Date
Tanu Kaskinen
6a487c5244 Update NEWS 2021-01-16 19:31:30 +02:00
Tanu Kaskinen
a9bd710596 switch-on-port-available: Pass correct port_pointers to switch_to_port()
The pp variable contains information for the port that became
unavailable, but switch_to_port() needs the information for the port
that we're switching to.

Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/1096
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/468>
2021-01-16 19:27:02 +02:00
Arun Raghavan
d6c8c9ce37 Update NEWS
Add notes for 14.1.
2021-01-12 21:44:48 -05:00
Arun Raghavan
0a8b93f4cd Update NEWS
Missed this in the 14.0 release.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/465>
2021-01-12 21:44:38 -05:00
Patrick Gaskin
d15a6e543f cli: Fix crash when using .include with an empty directory
This would previously fail the size > 0 assertion in pa_xmalloc.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/454>
2021-01-12 21:04:10 -05:00
Igor V. Kovalenko
e68aa03f29 database: clean up remaining references to CANONICAL_HOST
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/425>
2021-01-12 21:03:17 -05:00
Igor V. Kovalenko
c6b7837df3 database: pick old database file from any arch
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/425>
2021-01-12 21:03:13 -05:00
Igor V. Kovalenko
f8653fa5c1 database: drop arch from newly created database file name
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/425>
2021-01-12 21:03:09 -05:00
Igor V. Kovalenko
c8d851b4b7 database: use existing database matching same architecture prefix
State database binary file format may depend on system architecture,
for instance gdbm binary format depends on architecture word size,
making x86 and x64 gdbm files incompatible.

If this is the case, it is handled by adding system architecture name to
database file name using automatically configured CANONICAL_HOST string.
Meson build define CANONICAL_HOST to be system architecture name, while
autotools build extends this with vendor and and operating system components.

Switch autotools build to use host_cpu for CANONICAL_HOST to match Meson
configuration. For backwards compatibility always use existing database file
matching CANONICAL_HOST prefix if it exists.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/425>
2021-01-12 21:03:03 -05:00
Igor V. Kovalenko
f031cc28f4 database: extract common method to handle machine id and architecture
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/425>
2021-01-12 21:02:55 -05:00
Igor V. Kovalenko
374d937752 build-sys: meson: use target_machine.cpu_family() for CANONICAL_HOST
target_machine provides information about the machine on which the compiled
binary's output will run.

cpu_family() returns CPU family name (such as x86_64, not more specific amd64)

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/426>
2021-01-12 21:02:49 -05:00
Igor V. Kovalenko
80474384f5 ladspa-sink: do not call pa_sink_suspend if PA_SINK_IS_LINKED is false
While module-ladspa-sink is still being loaded and before pa_sink_put() is
called there may be an attempt to reconfigure master sink when avoid-resampling
is true. This breaks attempting to suspend ladspa-sink which is still in INIT
state.

Fix this by skipping pa_sink_suspend if PA_SINK_IS_LINKED is false.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/445>
2021-01-07 19:18:48 -05:00
Tanu Kaskinen
72b6d24adb alsa-mixer: Add support for the Headphone,1 element
This is seen at least on HP EliteDesk 800 DM and HP EliteDesk 800 SFF.

This is used by the analog-output-headphones-2 path, but all other paths
on the same sink need to handle the element too. The existing
configuration is inconsistent between files regarding whether headphone
outputs should be muted or not when not using them. I chose to be
consistent within files, which means that Headphone,1 handling is
inconsistent between files in the same way that the existing Headphone
and Headphone2 handling is. (My opinion is that unused paths should be
always muted, but I didn't want to do that policy change in this patch.)

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/272>
2021-01-07 19:18:35 -05:00
Tanu Kaskinen
1804c9de1d alsa-mixer: Move HP EliteDesk 800 SFF/DM second headphone output to path analog-output-headphones-2
The two headphone outputs should be handled in separate paths so that
volume control can be implemented properly for both outputs.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/272>
2021-01-07 19:18:29 -05:00
Tanu Kaskinen
fdf91a035d alsa-mixer: Use unambiguous descriptions with dual headphone outputs
Previously both paths had description "Headphones", which I assume can
cause confusion with users who see two ports with identical names. I
don't have this kind of hardware myself nor have I heard complaints from
users, this is just something I noticed while reading the configuration
files.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/272>
2021-01-07 19:18:25 -05:00
Kai-Heng Feng
eb2c757d2d alsa-mixer: Support dual Front Headphone Jack
There are dual Front Headphone Jacks with different indices or with
different names, they can be found on HP EliteDesk 800 SFF and HP
EliteDesk 800 DM, respectively.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/272>
2021-01-07 19:18:20 -05:00
Kai-Heng Feng
8903d2ccc0 alsa-mixer: Handle the index for ALSA mixer jack identifiers
Some systems have two jacks with same name but different index, we need
to take index into consideration to use both jacks.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/272>
2021-01-07 19:18:15 -05:00
morrishoresh
0f89157261 bluetooth: correct possible segmentation fault
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/443>
2021-01-07 19:17:57 -05:00
Hui Wang
254cb4bd1f switch-on-port-available: Switch to headset or mic on unknown avail
On some Dell AIO machines, there is no internal mic, only a multi
function audio jack, so the only input devices are headphone-mic and
headset-mic, and they share the Jack with headphone.

When there is no headset plugged in that Jack, the headphone-mic
and headset-mic are off. And since there is no available port under
the analog input source, this source is unlinked (if there is
internal mic, the source will not be unlinked). so the only pa-source
left in the PA is analog-stereo-monitor.

After the headset is plugged, we need to let switch_to_port() handle
headset-mic and headphone-mic conditionally, this will guarantee the
source will be created if it is unlinked before plugging, and then the
input profile could be selected correctly.

Signed-off-by: Hui Wang <hui.wang@canonical.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/390>
2021-01-07 19:17:22 -05:00
Jaroslav Kysela
b3e6e2e0df alsa: mixer - add support up 8 mixer channels
We have at least one USB hardware which supports the 8
channels in one mixer element:

  https://github.com/alsa-project/alsa-ucm-conf/pull/25

POSITION_MASK_CHANNELS define was added for the future extensions.

The override_map variable was changed from bool to mask (unsigned int).
The channel map override settings is handled for channels up to eight now.

Also added missing override-map.3 .. override-map.8 to the configuration
parser array.

The driver channel position was added to the override mask arguments
(syntax is driver:pulseaudio like left:all-left). If ommited, the ALSA's
channel positions are guessed by index.

Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/292

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/389>
2021-01-07 19:17:21 -05:00
Jaroslav Kysela
de9299c7b7 alsa: mixer - use safe dB range values when the override mask is unset
Use safe values for the min_dB and max_dB fields when the position mask
is unset to avoid breakage for the upper levels.

If the range is incorrect, the volume range shown in pavucontrol shows
strange values.

(Thanks to Wim Taymans for the idea.)

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/389>
2021-01-07 19:17:13 -05:00
32 changed files with 619 additions and 191 deletions

151
NEWS
View file

@ -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:

View file

@ -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], [

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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) {

View file

@ -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);

View file

@ -13,17 +13,24 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; 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

View file

@ -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

View file

@ -127,6 +127,10 @@ required-any = any
switch = off
volume = off
[Element Headphone,1]
switch = off
volume = off
[Element Headphone2]
switch = off
volume = off

View file

@ -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

View file

@ -76,6 +76,10 @@ volume = off
switch = mute
volume = zero
[Element Headphone,1]
switch = mute
volume = zero
[Element Headphone2]
switch = mute
volume = zero

View file

@ -94,6 +94,10 @@ volume = off
switch = off
volume = off
[Element Headphone,1]
switch = off
volume = off
[Element Headphone2]
switch = off
volume = off

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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));

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

107
src/pulsecore/database.c Normal file
View file

@ -0,0 +1,107 @@
/***
This file is part of PulseAudio.
Copyright 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
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 <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <dirent.h>
#include <pulse/xmalloc.h>
#include <pulsecore/core-util.h>
#include <pulsecore/log.h>
#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;
}

View file

@ -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);

View file

@ -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',