From b546beef21d1918b7c08f2d1838ef6f32d7ead73 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 23 Jul 2020 19:36:06 -0400 Subject: [PATCH 001/865] proplist-util: Drop deprecated G_CONST_RETURN macro The preference in glib is now to just use the const qualifier directly. --- src/pulsecore/proplist-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c index f966579b4..16ea9e006 100644 --- a/src/pulsecore/proplist-util.c +++ b/src/pulsecore/proplist-util.c @@ -45,14 +45,14 @@ extern char **environ; #if defined(HAVE_GLIB) && defined(PA_GCC_WEAKREF) #include -static G_CONST_RETURN gchar* _g_get_application_name(void) PA_GCC_WEAKREF(g_get_application_name); +static const gchar* _g_get_application_name(void) PA_GCC_WEAKREF(g_get_application_name); #endif #if defined(HAVE_GTK) && defined(PA_GCC_WEAKREF) #pragma GCC diagnostic ignored "-Wstrict-prototypes" #include #include -static G_CONST_RETURN gchar* _gtk_window_get_default_icon_name(void) PA_GCC_WEAKREF(gtk_window_get_default_icon_name); +static const gchar* _gtk_window_get_default_icon_name(void) PA_GCC_WEAKREF(gtk_window_get_default_icon_name); static Display *_gdk_display PA_GCC_WEAKREF(gdk_display); #endif From c442227c6bc64789cb4bd47dbd0b73b11e7a60e4 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 23 Jul 2020 19:36:11 -0400 Subject: [PATCH 002/865] glib-mainloop: Drop deprecated g_get_current_time() usage This uses the year 2038-safe g_get_real_time() as recommended instead. Bumps GLib dependency to 2.28 as a result. --- configure.ac | 2 +- meson.build | 2 +- src/pulse/glib-mainloop.c | 21 +++++++++------------ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/configure.ac b/configure.ac index a519e63d7..caabef6ed 100644 --- a/configure.ac +++ b/configure.ac @@ -894,7 +894,7 @@ AC_ARG_ENABLE([glib2], AS_HELP_STRING([--disable-glib2],[Disable optional GLib 2 support])) AS_IF([test "x$enable_glib2" != "xno"], - [PKG_CHECK_MODULES(GLIB20, [ glib-2.0 >= 2.4.0 ], HAVE_GLIB20=1, HAVE_GLIB20=0)], + [PKG_CHECK_MODULES(GLIB20, [ glib-2.0 >= 2.28.0 ], HAVE_GLIB20=1, HAVE_GLIB20=0)], HAVE_GLIB20=0) AS_IF([test "x$enable_glib2" = "xyes" && test "x$HAVE_GLIB20" = "x0"], diff --git a/meson.build b/meson.build index 658eeee57..1c671d32c 100644 --- a/meson.build +++ b/meson.build @@ -556,7 +556,7 @@ if gio_dep.found() cdata.set('HAVE_GSETTINGS', 1) endif -glib_dep = dependency('glib-2.0', version : '>= 2.4.0', required: get_option('glib')) +glib_dep = dependency('glib-2.0', version : '>= 2.28.0', required: get_option('glib')) if glib_dep.found() cdata.set('HAVE_GLIB', 1) endif diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c index 1ce3cd39e..77295def8 100644 --- a/src/pulse/glib-mainloop.c +++ b/src/pulse/glib-mainloop.c @@ -482,16 +482,15 @@ static gboolean prepare_func(GSource *source, gint *timeout) { return TRUE; } else if (g->n_enabled_time_events) { pa_time_event *t; - GTimeVal now; + gint64 now; struct timeval tvnow; pa_usec_t usec; t = find_next_time_event(g); g_assert(t); - g_get_current_time(&now); - tvnow.tv_sec = now.tv_sec; - tvnow.tv_usec = now.tv_usec; + now = g_get_real_time(); + pa_timeval_store(&tvnow, now); if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { *timeout = 0; @@ -514,15 +513,14 @@ static gboolean check_func(GSource *source) { return TRUE; else if (g->n_enabled_time_events) { pa_time_event *t; - GTimeVal now; + gint64 now; struct timeval tvnow; t = find_next_time_event(g); g_assert(t); - g_get_current_time(&now); - tvnow.tv_sec = now.tv_sec; - tvnow.tv_usec = now.tv_usec; + now = g_get_real_time(); + pa_timeval_store(&tvnow, now); if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) return TRUE; @@ -558,16 +556,15 @@ static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer us } if (g->n_enabled_time_events) { - GTimeVal now; + gint64 now; struct timeval tvnow; pa_time_event *t; t = find_next_time_event(g); g_assert(t); - g_get_current_time(&now); - tvnow.tv_sec = now.tv_sec; - tvnow.tv_usec = now.tv_usec; + now = g_get_real_time(); + pa_timeval_store(&tvnow, now); if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { From 9055f5baf37e379d5e3b556bff1741565442723e Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 23 Jul 2020 19:42:47 -0400 Subject: [PATCH 003/865] stream-restore,device-restore: Avoid unaligned access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer GCC warns us that the channel_map and volume in legacy entries are accessed via pointers, and these might be unaligned as the legacy entry is a packed structure. For this reason, we read out those values into local variables before accessing them as pointers. The warnings are: [146/433] Compiling C object src/modules/module-device-restore.so.p/module-device-restore.c.o ../src/modules/module-device-restore.c: In function ‘legacy_entry_read’: ../src/modules/module-device-restore.c:554:51: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 554 | if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) { | ^~~~~~~~~~~~~~~~ ../src/modules/module-device-restore.c:559:48: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 559 | if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { | ^~~~~~~~~~~ ../src/modules/module-device-restore.c:559:104: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 559 | if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { | ^~~~~~~~~~~ ../src/modules/module-device-restore.c:559:117: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 559 | if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { | ^~~~~~~~~~~~~~~~ [211/433] Compiling C object src/modules/module-stream-restore.so.p/module-stream-restore.c.o ../src/modules/module-stream-restore.c: In function ‘legacy_entry_read’: ../src/modules/module-stream-restore.c:1076:51: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 1076 | if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) { | ^~~~~~~~~~~~~~~~ ../src/modules/module-stream-restore.c:1081:48: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 1081 | if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { | ^~~~~~~~~~~ ../src/modules/module-stream-restore.c:1081:104: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 1081 | if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { | ^~~~~~~~~~~ ../src/modules/module-stream-restore.c:1081:117: warning: taking address of packed member of ‘struct legacy_entry’ may result in an unaligned pointer value [-Waddress-of-packed-member] 1081 | if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { | --- src/modules/module-device-restore.c | 11 +++++++++-- src/modules/module-stream-restore.c | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index d15d9ffa3..684c8360d 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -528,6 +528,8 @@ static bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry * char port[PA_NAME_MAX]; } PA_GCC_PACKED; struct legacy_entry *le; + pa_channel_map channel_map; + pa_cvolume volume; pa_assert(u); pa_assert(data); @@ -551,12 +553,17 @@ static bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry * return false; } - if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) { + /* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these + * members to be accessed directly */ + channel_map = le->channel_map; + volume = le->volume; + + if (le->volume_valid && !pa_channel_map_valid(&channel_map)) { pa_log_warn("Invalid channel map."); return false; } - if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { + if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) { pa_log_warn("Volume and channel map don't match."); return false; } diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 7144a664b..d26543bde 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -1029,6 +1029,8 @@ static struct entry *legacy_entry_read(struct userdata *u, const char *name) { pa_datum data; struct legacy_entry *le; struct entry *e; + pa_channel_map channel_map; + pa_cvolume volume; pa_assert(u); pa_assert(name); @@ -1073,12 +1075,17 @@ static struct entry *legacy_entry_read(struct userdata *u, const char *name) { goto fail; } - if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) { + /* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these + * members to be accessed directly */ + channel_map = le->channel_map; + volume = le->volume; + + if (le->volume_valid && !pa_channel_map_valid(&channel_map)) { pa_log_warn("Invalid channel map stored in database for legacy stream"); goto fail; } - if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { + if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) { pa_log_warn("Invalid volume stored in database for legacy stream"); goto fail; } From 093dd2f18180f38df0304946933661b116d314dc Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Sun, 11 Oct 2020 15:47:32 +0300 Subject: [PATCH 004/865] raop-sink: Don't set device.intended_roles=music If there are RAOP devices in the network, it doesn't necessarily mean that the user wants to play music to them. Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/993 --- src/modules/raop/raop-sink.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c index 114f6d1e2..deba8d49a 100644 --- a/src/modules/raop/raop-sink.c +++ b/src/modules/raop/raop-sink.c @@ -826,7 +826,6 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) { pa_sink_new_data_set_channel_map(&data, &map); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music"); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server); if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { From 757eb264485b11ca9163a260d8772c59ac3108a1 Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Thu, 24 Sep 2020 12:07:07 +0300 Subject: [PATCH 005/865] mutex-posix: Log pthread_mutex_unlock() error This call has been seen failing on FreeBSD, and it's difficult to debug without seeing which error the function returned. Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/988 --- src/pulsecore/mutex-posix.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c index a835be1b7..41309a0a3 100644 --- a/src/pulsecore/mutex-posix.c +++ b/src/pulsecore/mutex-posix.c @@ -25,6 +25,8 @@ #include #include + +#include #include #include "mutex.h" @@ -103,9 +105,14 @@ bool pa_mutex_try_lock(pa_mutex *m) { } void pa_mutex_unlock(pa_mutex *m) { + int err; + pa_assert(m); - pa_assert_se(pthread_mutex_unlock(&m->mutex) == 0); + if ((err = pthread_mutex_unlock(&m->mutex) != 0)) { + pa_log("pthread_mutex_unlock() failed: %s", pa_cstrerror(err)); + pa_assert_not_reached(); + } } pa_cond *pa_cond_new(void) { From d5d08035913be89ad0dec99b4759ab9377bc1eea Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Tue, 22 Sep 2020 14:12:37 +0300 Subject: [PATCH 006/865] main: set LC_NUMERIC to C The webrtc backend of module-echo-cancel uses sscanf() to parse floating point numbers from module arguments, which didn't work when the locale used a comma for the decimal point. Setting the LC_NUMERIC locale variable to C makes the pulseaudio process use a period as the decimal point regardless of the user's locale configuration. Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/89 --- src/daemon/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/daemon/main.c b/src/daemon/main.c index 59f931219..c7c434288 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -480,7 +480,13 @@ int main(int argc, char *argv[]) { pa_unblock_sigs(-1); pa_reset_priority(); + /* Load locale from the environment. */ setlocale(LC_ALL, ""); + + /* Set LC_NUMERIC to C so that floating point strings are consistently + * formatted and parsed across locales. */ + setlocale(LC_NUMERIC, "C"); + pa_init_i18n(); conf = pa_daemon_conf_new(); From a28f2e7293a055588f7bba568b5049ff91b3aad0 Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Thu, 3 Sep 2020 14:28:29 +0300 Subject: [PATCH 007/865] alsa-mixer: Add support for the Center/LFE element We already supported the CLFE element, which should be semantically equivalent, so I just copied all the CLFE element definitions. The Center/LFE element is seen on Creative X-Fi with 20K1 chipset cards. Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/978 --- src/modules/alsa/mixer/paths/analog-output-headphones.conf | 4 ++++ src/modules/alsa/mixer/paths/analog-output-lineout.conf | 6 ++++++ .../alsa/mixer/paths/analog-output-speaker-always.conf | 6 ++++++ src/modules/alsa/mixer/paths/analog-output-speaker.conf | 6 ++++++ src/modules/alsa/mixer/paths/analog-output.conf | 6 ++++++ 5 files changed, 28 insertions(+) diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf index d4ad7777d..14fb81481 100644 --- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf +++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf @@ -160,4 +160,8 @@ volume = off switch = off volume = off +[Element Speaker Center/LFE] +switch = off +volume = off + .include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-lineout.conf b/src/modules/alsa/mixer/paths/analog-output-lineout.conf index 9a6af3ad8..61df49965 100644 --- a/src/modules/alsa/mixer/paths/analog-output-lineout.conf +++ b/src/modules/alsa/mixer/paths/analog-output-lineout.conf @@ -181,6 +181,12 @@ volume = merge override-map.1 = all-center override-map.2 = all-center,lfe +[Element Center/LFE] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,lfe + [Element Bass Speaker] switch = off volume = off 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..bf781c671 100644 --- a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf +++ b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf @@ -174,4 +174,10 @@ volume = merge override-map.1 = all-center override-map.2 = all-center,lfe +[Element Center/LFE] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,lfe + .include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf index 27a3983d5..1610b9a05 100644 --- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf +++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf @@ -220,6 +220,12 @@ volume = merge override-map.1 = all-center override-map.2 = all-center,lfe +[Element Center/LFE] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,lfe + [Element Speaker CLFE] switch = mute volume = merge diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf index e6ba98358..0f6b5f5a0 100644 --- a/src/modules/alsa/mixer/paths/analog-output.conf +++ b/src/modules/alsa/mixer/paths/analog-output.conf @@ -79,4 +79,10 @@ volume = merge override-map.1 = all-center override-map.2 = all-center,lfe +[Element Center/LFE] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,lfe + .include analog-output.conf.common From 45abd0b43c7901b375afcba6d9b0e1cca2f349a8 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 2 Sep 2020 19:19:07 +0530 Subject: [PATCH 008/865] Add default.pa.d folder support The .include meta command already supports specifying a directory and when including a directory, all files with the extension '.pa' in that directory will be parsed in alphabetical order. This feature can be used to add support for default.pa.d directory, so that packages for other applications or users can just drop in a file for configuration without changing the default.pa which is shipped. We use the PA_DEFAULT_CONFIG_DIR for this, however, since meson quotes this build variable, introduce an unquoted version for this purpose and use it with .include. Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/909 Signed-off-by: Sanchayan Maity --- configure.ac | 1 + meson.build | 1 + src/daemon/default.pa.in | 5 +++++ src/daemon/system.pa.in | 5 +++++ 4 files changed, 12 insertions(+) diff --git a/configure.ac b/configure.ac index caabef6ed..6e6049fec 100644 --- a/configure.ac +++ b/configure.ac @@ -1394,6 +1394,7 @@ AC_DEFINE(PA_SOEXT, [".so"], [Shared object extension]) AC_SUBST(pulseconfdir, ["${sysconfdir}/pulse"]) AX_DEFINE_DIR(PA_DEFAULT_CONFIG_DIR, pulseconfdir, [Location of configuration files]) +AX_DEFINE_DIR(PA_DEFAULT_CONFIG_DIR_UNQUOTED, pulseconfdir, [Location of configuration files]) #### Mac OSX specific stuff ##### diff --git a/meson.build b/meson.build index 1c671d32c..217ba0d85 100644 --- a/meson.build +++ b/meson.build @@ -136,6 +136,7 @@ cdata.set_quoted('PA_SRCDIR', join_paths(meson.current_source_dir(), 'src')) cdata.set_quoted('PA_BUILDDIR', meson.current_build_dir()) cdata.set_quoted('PA_SOEXT', '.so') cdata.set_quoted('PA_DEFAULT_CONFIG_DIR', pulsesysconfdir) +cdata.set('PA_DEFAULT_CONFIG_DIR_UNQUOTED', pulsesysconfdir) cdata.set_quoted('PA_BINARY', join_paths(bindir, 'pulseaudio')) cdata.set_quoted('PA_SYSTEM_RUNTIME_PATH', join_paths(localstatedir, 'run', 'pulse')) cdata.set_quoted('PA_SYSTEM_CONFIG_PATH', join_paths(localstatedir, 'lib', 'pulse')) diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index 030334f36..3468a89b9 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -173,3 +173,8 @@ load-module module-filter-apply ### Make some devices default #set-default-sink output #set-default-source input + +### Allow including a default.pa.d directory, which if present, can be used +### for additional configuration snippets. +.nofail +.include @PA_DEFAULT_CONFIG_DIR_UNQUOTED@/default.pa.d diff --git a/src/daemon/system.pa.in b/src/daemon/system.pa.in index 73e39ec93..1470e2368 100755 --- a/src/daemon/system.pa.in +++ b/src/daemon/system.pa.in @@ -60,3 +60,8 @@ load-module module-suspend-on-idle ### Enable positioned event sounds load-module module-position-event-sounds + +### Allow including a system.pa.d directory, which if present, can be used +### for additional configuration snippets. +.nofail +.include @PA_DEFAULT_CONFIG_DIR_UNQUOTED@/system.pa.d From 69ba5a2b58ad6e98c8899aa297afd8276dbbe052 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Sat, 21 Dec 2019 14:01:44 +0200 Subject: [PATCH 009/865] alsa-mixer: add mono input support for Behringer UMC22 --- src/modules/alsa/90-pulseaudio.rules | 1 + src/modules/alsa/alsa-mixer.c | 2 + .../mixer/profile-sets/behringer-umc22.conf | 68 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/modules/alsa/mixer/profile-sets/behringer-umc22.conf diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules index 7bfacda09..c81f05b51 100644 --- a/src/modules/alsa/90-pulseaudio.rules +++ b/src/modules/alsa/90-pulseaudio.rules @@ -110,6 +110,7 @@ ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudi ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf" ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf" ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" +ATTRS{idVendor}=="08bb", ATTRS{idProduct}=="2902", ENV{PULSE_PROFILE_SET}="behringer-umc22.conf" # ID 1038:12ad is for the 2018 refresh of the Arctis 7. # ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index 063179052..a05ae7e08 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -4392,6 +4392,8 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { static const struct description_map well_known_descriptions[] = { { "analog-mono", N_("Analog Mono") }, + { "analog-mono-left", N_("Analog Mono (Left)") }, + { "analog-mono-right", N_("Analog Mono (Right)") }, { "analog-stereo", N_("Analog Stereo") }, { "mono-fallback", N_("Mono") }, { "stereo-fallback", N_("Stereo") }, diff --git a/src/modules/alsa/mixer/profile-sets/behringer-umc22.conf b/src/modules/alsa/mixer/profile-sets/behringer-umc22.conf new file mode 100644 index 000000000..cc74852c3 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/behringer-umc22.conf @@ -0,0 +1,68 @@ +# This file is part of PulseAudio. +# +# 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 +# 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 . + +; Behringer U-Phoria UMC22 +; +; Default mapping only allows to use stereo input and sound card has two +; physical input channels. +; +; However in case of only using a single input channel (like condenser +; microphone) only one channel will have any sound, which is often +; inconvenient for casual use. +; +; This config includes mono input options which makes it much more +; friendly in single input configuration. +; +; This config also removes default digital input/output mappings that do +; not physically exist on this card. +; +; Added by Nazar Mokrynskyi + +[General] +auto-profiles = yes + +[Mapping analog-stereo-input] +device-strings = hw:%f +channel-map = left,right +paths-input = analog-input-mic +direction = input +priority = 4 + +[Mapping analog-mono] +device-strings = hw:%f +channel-map = mono,mono +paths-input = analog-input-mic +direction = input +priority = 3 + +[Mapping analog-mono-left] +device-strings = hw:%f +channel-map = mono,aux1 +paths-input = analog-input-mic +direction = input +priority = 2 + +[Mapping analog-mono-right] +device-strings = hw:%f +channel-map = aux1,mono +paths-input = analog-input-mic +direction = input +priority = 1 + +[Mapping analog-stereo-output] +device-strings = front:%f +channel-map = left,right +paths-output = analog-output +direction = output From 05c373d939506f31f96e58d8390b92e737e12afc Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Wed, 2 Sep 2020 00:23:54 +0800 Subject: [PATCH 010/865] alsa-mixer: Add support for HP Thunderbolt Dock The HP Thunderbolt Dock [1] has two separate USB cards, a headset jack and an optional module which is a speakerphone. This patch adds new description for them, and mark the intended-roles as phone for the speakerphone module. [1] https://store.hp.com/us/en/pdp/hp-thunderbolt-dock-120w-g2-with-audio --- src/Makefile.am | 4 ++- src/modules/alsa/90-pulseaudio.rules | 2 ++ src/modules/alsa/alsa-mixer.c | 4 +++ .../profile-sets/hp-tbt-dock-120w-g2.conf | 30 ++++++++++++++++++ .../hp-tbt-dock-audio-module.conf | 31 +++++++++++++++++++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf create mode 100644 src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf diff --git a/src/Makefile.am b/src/Makefile.am index bd764037b..478274cce 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1367,7 +1367,9 @@ dist_alsaprofilesets_DATA = \ modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf \ modules/alsa/mixer/profile-sets/usb-gaming-headset.conf \ modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf \ - modules/alsa/mixer/profile-sets/cmedia-high-speed-true-hdaudio.conf + modules/alsa/mixer/profile-sets/cmedia-high-speed-true-hdaudio.conf \ + modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf \ + modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf if HAVE_UDEV dist_udevrules_DATA = \ diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules index c81f05b51..83b257df6 100644 --- a/src/modules/alsa/90-pulseaudio.rules +++ b/src/modules/alsa/90-pulseaudio.rules @@ -111,6 +111,8 @@ ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinec ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf" ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" ATTRS{idVendor}=="08bb", ATTRS{idProduct}=="2902", ENV{PULSE_PROFILE_SET}="behringer-umc22.conf" +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0269", ENV{PULSE_PROFILE_SET}="hp-tbt-dock-120w-g2.conf" +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0567", ENV{PULSE_PROFILE_SET}="hp-tbt-dock-audio-module.conf" # ID 1038:12ad is for the 2018 refresh of the Arctis 7. # ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index a05ae7e08..937771d43 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -4404,6 +4404,8 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { * multichannel-input and multichannel-output. */ { "analog-stereo-input", N_("Analog Stereo") }, { "analog-stereo-output", N_("Analog Stereo") }, + { "analog-stereo-headset", N_("Headset") }, + { "analog-stereo-speakerphone", N_("Speakerphone") }, { "multichannel-input", N_("Multichannel") }, { "multichannel-output", N_("Multichannel") }, { "analog-surround-21", N_("Analog Surround 2.1") }, @@ -4560,6 +4562,8 @@ static int profile_verify(pa_alsa_profile *p) { static const struct description_map well_known_descriptions[] = { { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") }, { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") }, + { "output:analog-stereo-headset+input:analog-stereo-headset", N_("Headset") }, + { "output:analog-stereo-speakerphone+input:analog-stereo-speakerphone", N_("Speakerphone") }, { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") }, { "output:multichannel-output+input:multichannel-input", N_("Multichannel Duplex") }, { "output:unknown-stereo+input:unknown-stereo", N_("Stereo Duplex") }, diff --git a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf new file mode 100644 index 000000000..3f652816b --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf @@ -0,0 +1,30 @@ +# This file is part of PulseAudio. +# +# 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 +# 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 . + +; HP Thunderbolt Dock Audio Headset +; +; This card has one stereo input and one stereo output. + +[General] +auto-profiles = no + +[Mapping analog-stereo-headset] +device-strings = hw:%f,0,0 +channel-map = left,right + +[Profile output:analog-stereo-headset+input:analog-stereo-headset] +output-mappings = analog-stereo-headset +input-mappings = analog-stereo-headset +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf new file mode 100644 index 000000000..c03275cbe --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf @@ -0,0 +1,31 @@ +# This file is part of PulseAudio. +# +# 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 +# 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 . + +; HP Thunderbolt Dock Audio Module +; +; This card has one stereo input and one stereo output. + +[General] +auto-profiles = no + +[Mapping analog-stereo-speakerphone] +device-strings = hw:%f,0,0 +channel-map = left,right +intended-roles = phone + +[Profile output:analog-stereo-speakerphone+input:analog-stereo-speakerphone] +output-mappings = analog-stereo-speakerphone +input-mappings = analog-stereo-speakerphone +skip-probe = yes From a73ec2a3f4f03829575761dc17546cf3226861c7 Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Thu, 3 Sep 2020 13:33:15 +0300 Subject: [PATCH 011/865] alsa-mixer: Expand comments in the HP Thunderbolt Dock configuration --- .../alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf | 9 +++++++-- .../mixer/profile-sets/hp-tbt-dock-audio-module.conf | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf index 3f652816b..a683a4e4e 100644 --- a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf +++ b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf @@ -13,9 +13,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . -; HP Thunderbolt Dock Audio Headset +; HP Thunderbolt Dock 120W G2 ; -; This card has one stereo input and one stereo output. +; This dock has a 3.5mm headset connector. Both input and output are stereo. +; +; There's a separate speakerphone module called "HP Thunderbolt Dock Audio +; Module", which can be attached to this dock. The module will appear in ALSA +; as a separate USB sound card, configuration for it is in +; hp-tbt-dock-audio-module.conf. [General] auto-profiles = no diff --git a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf index c03275cbe..692ab8dd0 100644 --- a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf +++ b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf @@ -15,7 +15,12 @@ ; HP Thunderbolt Dock Audio Module ; -; This card has one stereo input and one stereo output. +; This device attaches to the "HP Thunderbolt Dock 120W G2" dock. The audio +; module provides a speakerphone with echo cancellation and appears in ALSA as +; a USB sound card with stereo input and output. +; +; The dock itself has a 3.5mm headset connector and appears as a separate USB +; sound card, configuration for it is in hp-tbt-dock-120w-g2.conf. [General] auto-profiles = no From 5f3717f39cc3e373a3caaebd0d1625486fdd3b31 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 23 Nov 2020 17:01:01 -0500 Subject: [PATCH 012/865] mutex-posix: Fix error assignment for unlock() check This comment was missed in 757eb264485b11ca9163a260d8772c59ac3108a1. --- src/pulsecore/mutex-posix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c index 41309a0a3..86f029c7c 100644 --- a/src/pulsecore/mutex-posix.c +++ b/src/pulsecore/mutex-posix.c @@ -109,7 +109,7 @@ void pa_mutex_unlock(pa_mutex *m) { pa_assert(m); - if ((err = pthread_mutex_unlock(&m->mutex) != 0)) { + if ((err = pthread_mutex_unlock(&m->mutex)) != 0) { pa_log("pthread_mutex_unlock() failed: %s", pa_cstrerror(err)); pa_assert_not_reached(); } From 00bd8e1ef8e131b05ca7b52b28e4daf0e1086555 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 23 Jan 2020 21:21:59 -0800 Subject: [PATCH 013/865] virtual-surround-sink: Use FFTW3 instead of naive approach This replaces the original virtual surround sink with a total rewrite, aiming to implement any number of hrir use cases, including asymmetrical impulses as two separate left and right output files. It uses FFTW3 FFT convolution, using the overlap- save method, with full rewind support. It operates in steps equal to the resampled length of the hrir, and overlaps input blocks in increments equal to the size of the FFT block. If using paired hrirs, it requires matched sample spec and sample rates and channel maps. For best results, the input files should have speaker maps, rather than expecting the sample loader to auto detect the mapping. Part-of: --- src/Makefile.am | 10 +- src/modules/meson.build | 7 +- src/modules/module-virtual-surround-sink.c | 877 ++++++++++++++------- 3 files changed, 603 insertions(+), 291 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 478274cce..d298d85d3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1268,7 +1268,6 @@ modlibexec_LTLIBRARIES += \ module-loopback.la \ module-virtual-sink.la \ module-virtual-source.la \ - module-virtual-surround-sink.la \ module-switch-on-connect.la \ module-switch-on-port-available.la \ module-filter-apply.la \ @@ -1530,6 +1529,11 @@ endif endif endif +if HAVE_FFTW +modlibexec_LTLIBRARIES += \ + module-virtual-surround-sink.la +endif + if HAVE_DBUS if HAVE_FFTW modlibexec_LTLIBRARIES += \ @@ -1775,9 +1779,9 @@ module_virtual_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_virtual_source_la_LIBADD = $(MODULE_LIBADD) module_virtual_surround_sink_la_SOURCES = modules/module-virtual-surround-sink.c -module_virtual_surround_sink_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) -DPA_MODULE_NAME=module_virtual_surround_sink +module_virtual_surround_sink_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(FFTW_CFLAGS) -DPA_MODULE_NAME=module_virtual_surround_sink module_virtual_surround_sink_la_LDFLAGS = $(MODULE_LDFLAGS) -module_virtual_surround_sink_la_LIBADD = $(MODULE_LIBADD) +module_virtual_surround_sink_la_LIBADD = $(MODULE_LIBADD) $(FFTW_LIBS) # X11 diff --git a/src/modules/meson.build b/src/modules/meson.build index 5f0437164..9a394c3b9 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -61,7 +61,6 @@ all_modules = [ [ 'module-tunnel-source-new', 'module-tunnel-source-new.c' ], [ 'module-virtual-sink', 'module-virtual-sink.c' ], [ 'module-virtual-source', 'module-virtual-source.c' ], - [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c' ], [ 'module-volume-restore', 'module-volume-restore.c' ], # [ 'module-waveout', 'module-waveout.c' ], ] @@ -137,6 +136,12 @@ if dbus_dep.found() ] endif +if fftw_dep.found() + all_modules += [ + [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c', [], [], [fftw_dep, libm_dep] ], + ] +endif + if dbus_dep.found() and fftw_dep.found() all_modules += [ [ 'module-equalizer-sink', 'module-equalizer-sink.c', [], [], [dbus_dep, fftw_dep, libm_dep] ], diff --git a/src/modules/module-virtual-surround-sink.c b/src/modules/module-virtual-surround-sink.c index c32107388..0506370e8 100644 --- a/src/modules/module-virtual-surround-sink.c +++ b/src/modules/module-virtual-surround-sink.c @@ -4,6 +4,8 @@ Copyright 2010 Intel Corporation Contributor: Pierre-Louis Bossart Copyright 2012 Niels Ole Salscheider + Contributor: Alexander E. Patrakov + Copyright 2020 Christopher Snowhill PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -23,6 +25,10 @@ #include #endif +#include + +#include + #include #include @@ -39,9 +45,8 @@ #include #include -#include -PA_MODULE_AUTHOR("Niels Ole Salscheider"); +PA_MODULE_AUTHOR("Christopher Snowhill"); PA_MODULE_DESCRIPTION(_("Virtual surround sink")); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(false); @@ -57,6 +62,8 @@ PA_MODULE_USAGE( "use_volume_sharing= " "force_flat_volume= " "hrir=/path/to/left_hrir.wav " + "hrir_left=/path/to/left_hrir.wav " + "hrir_right=/path/to/optional/right_hrir.wav " "autoloaded= " )); @@ -66,32 +73,26 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ - /* bool autoloaded; */ + bool autoloaded; pa_sink *sink; pa_sink_input *sink_input; - pa_memblockq *memblockq; + pa_memblockq *memblockq_sink; bool auto_desc; - unsigned channels; - unsigned hrir_channels; - unsigned fs, sink_fs; + size_t fftlen; + size_t hrir_samples; + size_t inputs; - unsigned *mapping_left; - unsigned *mapping_right; - - unsigned hrir_samples; - float *hrir_data; - - float *input_buffer; - int input_buffer_offset; - - bool autoloaded; + fftwf_plan *p_fw, p_bw; + fftwf_complex *f_in, *f_out, **f_ir; + float *revspace, *outspace[2], **inspace; }; +#define BLOCK_SIZE (512) + static const char* const valid_modargs[] = { "sink_name", "sink_properties", @@ -103,11 +104,157 @@ static const char* const valid_modargs[] = { "channel_map", "use_volume_sharing", "force_flat_volume", - "hrir", "autoloaded", + "hrir", + "hrir_left", + "hrir_right", NULL }; +/* Vector size of 4 floats */ +#define v_size 4 +static void * alloc(size_t x, size_t s) { + size_t f; + float *t; + + f = PA_ROUND_UP(x*s, sizeof(float)*v_size); + pa_assert_se(t = fftwf_malloc(f)); + pa_memzero(t, f); + + return t; +} + +static size_t sink_input_samples(size_t nbytes) +{ + return nbytes / 8; +} + +static size_t sink_input_bytes(size_t nsamples) +{ + return nsamples * 8; +} + +static size_t sink_samples(const struct userdata *u, size_t nbytes) +{ + return nbytes / (u->inputs * 4); +} + +static size_t sink_bytes(const struct userdata *u, size_t nsamples) +{ + return nsamples * (u->inputs * 4); +} + +/* Mirror channels for symmetrical impulse */ +static pa_channel_position_t mirror_channel(pa_channel_position_t channel) { + switch (channel) { + case PA_CHANNEL_POSITION_FRONT_LEFT: + return PA_CHANNEL_POSITION_FRONT_RIGHT; + + case PA_CHANNEL_POSITION_FRONT_RIGHT: + return PA_CHANNEL_POSITION_FRONT_LEFT; + + case PA_CHANNEL_POSITION_REAR_LEFT: + return PA_CHANNEL_POSITION_REAR_RIGHT; + + case PA_CHANNEL_POSITION_REAR_RIGHT: + return PA_CHANNEL_POSITION_REAR_LEFT; + + case PA_CHANNEL_POSITION_SIDE_LEFT: + return PA_CHANNEL_POSITION_SIDE_RIGHT; + + case PA_CHANNEL_POSITION_SIDE_RIGHT: + return PA_CHANNEL_POSITION_SIDE_LEFT; + + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: + return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: + return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: + return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: + return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: + return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: + return PA_CHANNEL_POSITION_TOP_REAR_LEFT; + + default: + return channel; + } +} + +/* Normalize the hrir */ +static void normalize_hrir(float * hrir_data, unsigned hrir_samples, unsigned hrir_channels) { + /* normalize hrir to avoid audible clipping + * + * The following heuristic tries to avoid audible clipping. It cannot avoid + * clipping in the worst case though, because the scaling factor would + * become too large resulting in a too quiet signal. + * The idea of the heuristic is to avoid clipping when a single click is + * played back on all channels. The scaling factor describes the additional + * factor that is necessary to avoid clipping for "normal" signals. + * + * This algorithm doesn't pretend to be perfect, it's just something that + * appears to work (not too quiet, no audible clipping) on the material that + * it has been tested on. If you find a real-world example where this + * algorithm results in audible clipping, please write a patch that adjusts + * the scaling factor constants or improves the algorithm (or if you can't + * write a patch, at least report the problem to the PulseAudio mailing list + * or bug tracker). */ + + const float scaling_factor = 2.5; + + float hrir_sum, hrir_max; + unsigned i, j; + + hrir_max = 0; + for (i = 0; i < hrir_samples; i++) { + hrir_sum = 0; + for (j = 0; j < hrir_channels; j++) + hrir_sum += fabs(hrir_data[i * hrir_channels + j]); + + if (hrir_sum > hrir_max) + hrir_max = hrir_sum; + } + + for (i = 0; i < hrir_samples; i++) { + for (j = 0; j < hrir_channels; j++) + hrir_data[i * hrir_channels + j] /= hrir_max * scaling_factor; + } +} + +/* Normalize a stereo hrir */ +static void normalize_hrir_stereo(float * hrir_data, float * hrir_right_data, unsigned hrir_samples, unsigned hrir_channels) { + const float scaling_factor = 2.5; + + float hrir_sum, hrir_max; + unsigned i, j; + + hrir_max = 0; + for (i = 0; i < hrir_samples; i++) { + hrir_sum = 0; + for (j = 0; j < hrir_channels; j++) { + hrir_sum += fabs(hrir_data[i * hrir_channels + j]); + hrir_sum += fabs(hrir_right_data[i * hrir_channels + j]); + } + + if (hrir_sum > hrir_max) + hrir_max = hrir_sum; + } + + for (i = 0; i < hrir_samples; i++) { + for (j = 0; j < hrir_channels; j++) { + hrir_data[i * hrir_channels + j] /= hrir_max * scaling_factor; + hrir_right_data[i * hrir_channels + j] /= hrir_max * scaling_factor; + } + } +} + /* Called from I/O thread context */ static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; @@ -121,11 +268,11 @@ static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t of * sink input is first shut down, the sink second. */ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { - *((int64_t*) data) = 0; + *((pa_usec_t*) data) = 0; return 0; } - *((int64_t*) data) = + *((pa_usec_t*) data) = /* Get the latency of the master sink */ pa_sink_get_latency_within_thread(u->sink_input->sink, true) + @@ -174,6 +321,7 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, /* Called from I/O thread context */ static void sink_request_rewind_cb(pa_sink *s) { struct userdata *u; + size_t nbytes_sink, nbytes_input; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); @@ -182,10 +330,11 @@ static void sink_request_rewind_cb(pa_sink *s) { !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) return; + nbytes_sink = s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq_sink); + nbytes_input = sink_input_bytes(sink_samples(u, nbytes_sink)); + /* Just hand this one over to the master sink */ - pa_sink_input_request_rewind(u->sink_input, - s->thread_info.rewind_nbytes + - pa_memblockq_get_length(u->memblockq), true, false, false); + pa_sink_input_request_rewind(u->sink_input, nbytes_input, true, false, false); } /* Called from I/O thread context */ @@ -233,136 +382,177 @@ static void sink_set_mute_cb(pa_sink *s) { pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); } +static size_t memblockq_missing(pa_memblockq *bq) { + size_t l, tlength; + pa_assert(bq); + + tlength = pa_memblockq_get_tlength(bq); + if ((l = pa_memblockq_get_length(bq)) >= tlength) + return 0; + + l = tlength - l; + return l >= pa_memblockq_get_minreq(bq) ? l : 0; +} + /* Called from I/O thread context */ -static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes_input, pa_memchunk *chunk) { struct userdata *u; float *src, *dst; - unsigned n; + int c, ear; + size_t s, bytes_missing, fftlen; pa_memchunk tchunk; - - unsigned j, k, l; - float sum_right, sum_left; - float current_sample; + float fftlen_if, *revspace; pa_sink_input_assert_ref(i); pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return -1; - /* Hmm, process any rewind request that might be queued up */ pa_sink_process_rewind(u->sink, 0); - while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { + while ((bytes_missing = memblockq_missing(u->memblockq_sink)) != 0) { pa_memchunk nchunk; - pa_sink_render(u->sink, nbytes * u->sink_fs / u->fs, &nchunk); - pa_memblockq_push(u->memblockq, &nchunk); + pa_sink_render(u->sink, bytes_missing, &nchunk); + pa_memblockq_push(u->memblockq_sink, &nchunk); pa_memblock_unref(nchunk.memblock); } - tchunk.length = PA_MIN(nbytes * u->sink_fs / u->fs, tchunk.length); - pa_assert(tchunk.length > 0); + pa_memblockq_rewind(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE)); + pa_memblockq_peek_fixed_size(u->memblockq_sink, sink_bytes(u, u->fftlen), &tchunk); - n = (unsigned) (tchunk.length / u->sink_fs); + pa_memblockq_drop(u->memblockq_sink, tchunk.length); - pa_assert(n > 0); + /* Now tchunk contains enough data to perform the FFT + * This should be equal to u->fftlen */ chunk->index = 0; - chunk->length = n * u->fs; + chunk->length = sink_input_bytes(BLOCK_SIZE); chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); - pa_memblockq_drop(u->memblockq, n * u->sink_fs); - src = pa_memblock_acquire_chunk(&tchunk); - dst = pa_memblock_acquire(chunk->memblock); - for (l = 0; l < n; l++) { - memcpy(((char*) u->input_buffer) + u->input_buffer_offset * u->sink_fs, ((char *) src) + l * u->sink_fs, u->sink_fs); - - sum_right = 0; - sum_left = 0; - - /* fold the input buffer with the impulse response */ - for (j = 0; j < u->hrir_samples; j++) { - for (k = 0; k < u->channels; k++) { - current_sample = u->input_buffer[((u->input_buffer_offset + j) % u->hrir_samples) * u->channels + k]; - - sum_left += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_left[k]]; - sum_right += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_right[k]]; - } + for (c = 0; c < u->inputs; c++) { + for (s = 0, fftlen = u->fftlen; s < fftlen; s++) { + u->inspace[c][s] = src[s * u->inputs + c]; } - - dst[2 * l] = PA_CLAMP_UNLIKELY(sum_left, -1.0f, 1.0f); - dst[2 * l + 1] = PA_CLAMP_UNLIKELY(sum_right, -1.0f, 1.0f); - - u->input_buffer_offset--; - if (u->input_buffer_offset < 0) - u->input_buffer_offset += u->hrir_samples; } pa_memblock_release(tchunk.memblock); - pa_memblock_release(chunk->memblock); - pa_memblock_unref(tchunk.memblock); + fftlen_if = 1.0f / (float)u->fftlen; + revspace = u->revspace + u->fftlen - BLOCK_SIZE; + + pa_memzero(u->outspace[0], BLOCK_SIZE * 4); + pa_memzero(u->outspace[1], BLOCK_SIZE * 4); + + for (c = 0; c < u->inputs; c++) { + fftwf_complex *f_in = u->f_in; + fftwf_complex *f_out = u->f_out; + + fftwf_execute(u->p_fw[c]); + + for (ear = 0; ear < 2; ear++) { + fftwf_complex *f_ir = u->f_ir[c * 2 + ear]; + float *outspace = u->outspace[ear]; + + for (s = 0, fftlen = u->fftlen / 2 + 1; s < fftlen; s++) { + float re = f_ir[s][0] * f_in[s][0] - f_ir[s][1] * f_in[s][1]; + float im = f_ir[s][1] * f_in[s][0] + f_ir[s][0] * f_in[s][1]; + f_out[s][0] = re; + f_out[s][1] = im; + } + + fftwf_execute(u->p_bw); + + for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; ++s) + outspace[s] += revspace[s] * fftlen_if; + } + } + + dst = pa_memblock_acquire_chunk(chunk); + + for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; s++) { + float output; + float *outspace = u->outspace[0]; + + output = outspace[s]; + if (output < -1.0) output = -1.0; + if (output > 1.0) output = 1.0; + dst[s * 2 + 0] = output; + + outspace = u->outspace[1]; + + output = outspace[s]; + if (output < -1.0) output = -1.0; + if (output > 1.0) output = 1.0; + dst[s * 2 + 1] = output; + } + + pa_memblock_release(chunk->memblock); + return 0; } /* Called from I/O thread context */ -static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes_input) { struct userdata *u; size_t amount = 0; + size_t nbytes_sink; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - /* If the sink is not yet linked, there is nothing to rewind */ - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input)); if (u->sink->thread_info.rewind_nbytes > 0) { size_t max_rewrite; - max_rewrite = nbytes * u->sink_fs / u->fs + pa_memblockq_get_length(u->memblockq); - amount = PA_MIN(u->sink->thread_info.rewind_nbytes * u->sink_fs / u->fs, max_rewrite); + max_rewrite = nbytes_sink + pa_memblockq_get_length(u->memblockq_sink); + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); u->sink->thread_info.rewind_nbytes = 0; if (amount > 0) { - pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true); - - /* Reset the input buffer */ - memset(u->input_buffer, 0, u->hrir_samples * u->sink_fs); - u->input_buffer_offset = 0; + pa_memblockq_seek(u->memblockq_sink, - (int64_t) amount, PA_SEEK_RELATIVE, true); } } pa_sink_process_rewind(u->sink, amount); - pa_memblockq_rewind(u->memblockq, nbytes * u->sink_fs / u->fs); + + pa_memblockq_rewind(u->memblockq_sink, nbytes_sink); } /* Called from I/O thread context */ -static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes_input) { struct userdata *u; + size_t nbytes_sink, nbytes_memblockq; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input)); + nbytes_memblockq = sink_bytes(u, sink_input_samples(nbytes_input) + u->fftlen); + /* FIXME: Too small max_rewind: * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_memblockq_set_maxrewind(u->memblockq, nbytes * u->sink_fs / u->fs); - pa_sink_set_max_rewind_within_thread(u->sink, nbytes * u->sink_fs / u->fs); + pa_memblockq_set_maxrewind(u->memblockq_sink, nbytes_memblockq); + pa_sink_set_max_rewind_within_thread(u->sink, nbytes_sink); } /* Called from I/O thread context */ -static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes_input) { struct userdata *u; + size_t nbytes_sink; + pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_sink_set_max_request_within_thread(u->sink, nbytes * u->sink_fs / u->fs); + nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input)); + + nbytes_sink = PA_ROUND_UP(nbytes_sink, sink_bytes(u, BLOCK_SIZE)); + pa_sink_set_max_request_within_thread(u->sink, nbytes_sink); } /* Called from I/O thread context */ @@ -401,6 +591,7 @@ static void sink_input_detach_cb(pa_sink_input *i) { /* Called from I/O thread context */ static void sink_input_attach_cb(pa_sink_input *i) { struct userdata *u; + size_t max_request; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); @@ -410,14 +601,15 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i) * u->sink_fs / u->fs); + max_request = sink_bytes(u, sink_input_samples(pa_sink_input_get_max_request(i))); + max_request = PA_ROUND_UP(max_request, sink_bytes(u, BLOCK_SIZE)); + pa_sink_set_max_request_within_thread(u->sink, max_request); /* FIXME: Too small max_rewind: * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i) * u->sink_fs / u->fs); + pa_sink_set_max_rewind_within_thread(u->sink, sink_bytes(u, sink_input_samples(pa_sink_input_get_max_rewind(i)))); - if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) - pa_sink_attach_within_thread(u->sink); + pa_sink_attach_within_thread(u->sink); } /* Called from main context */ @@ -427,12 +619,12 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - /* The order here matters! We first kill the sink so that streams - * can properly be moved away while the sink input is still connected - * to the master. */ + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ pa_sink_input_cork(u->sink_input, true); - pa_sink_unlink(u->sink); pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); pa_sink_input_unref(u->sink_input); u->sink_input = NULL; @@ -503,119 +695,57 @@ static void sink_input_mute_changed_cb(pa_sink_input *i) { pa_sink_mute_changed(u->sink, i->muted); } -static pa_channel_position_t mirror_channel(pa_channel_position_t channel) { - switch (channel) { - case PA_CHANNEL_POSITION_FRONT_LEFT: - return PA_CHANNEL_POSITION_FRONT_RIGHT; - - case PA_CHANNEL_POSITION_FRONT_RIGHT: - return PA_CHANNEL_POSITION_FRONT_LEFT; - - case PA_CHANNEL_POSITION_REAR_LEFT: - return PA_CHANNEL_POSITION_REAR_RIGHT; - - case PA_CHANNEL_POSITION_REAR_RIGHT: - return PA_CHANNEL_POSITION_REAR_LEFT; - - case PA_CHANNEL_POSITION_SIDE_LEFT: - return PA_CHANNEL_POSITION_SIDE_RIGHT; - - case PA_CHANNEL_POSITION_SIDE_RIGHT: - return PA_CHANNEL_POSITION_SIDE_LEFT; - - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: - return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; - - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: - return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; - - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: - return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; - - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: - return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; - - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: - return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; - - case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: - return PA_CHANNEL_POSITION_TOP_REAR_LEFT; - - default: - return channel; - } -} - -static void normalize_hrir(struct userdata *u) { - /* normalize hrir to avoid audible clipping - * - * The following heuristic tries to avoid audible clipping. It cannot avoid - * clipping in the worst case though, because the scaling factor would - * become too large resulting in a too quiet signal. - * The idea of the heuristic is to avoid clipping when a single click is - * played back on all channels. The scaling factor describes the additional - * factor that is necessary to avoid clipping for "normal" signals. - * - * This algorithm doesn't pretend to be perfect, it's just something that - * appears to work (not too quiet, no audible clipping) on the material that - * it has been tested on. If you find a real-world example where this - * algorithm results in audible clipping, please write a patch that adjusts - * the scaling factor constants or improves the algorithm (or if you can't - * write a patch, at least report the problem to the PulseAudio mailing list - * or bug tracker). */ - - const float scaling_factor = 2.5; - - float hrir_sum, hrir_max; - unsigned i, j; - - hrir_max = 0; - for (i = 0; i < u->hrir_samples; i++) { - hrir_sum = 0; - for (j = 0; j < u->hrir_channels; j++) - hrir_sum += fabs(u->hrir_data[i * u->hrir_channels + j]); - - if (hrir_sum > hrir_max) - hrir_max = hrir_sum; - } - - for (i = 0; i < u->hrir_samples; i++) { - for (j = 0; j < u->hrir_channels; j++) - u->hrir_data[i * u->hrir_channels + j] /= hrir_max * scaling_factor; - } -} - int pa__init(pa_module*m) { struct userdata *u; - pa_sample_spec ss, sink_input_ss; - pa_channel_map map, sink_input_map; + pa_sample_spec ss_input, ss_output; + pa_channel_map map_output; pa_modargs *ma; const char *master_name; - pa_sink *master = NULL; + const char *hrir_left_file; + const char *hrir_right_file; + pa_sink *master=NULL; pa_sink_input_new_data sink_input_data; pa_sink_new_data sink_data; bool use_volume_sharing = true; bool force_flat_volume = false; pa_memchunk silence; + const char* z; + unsigned i, j, ear, found_channel_left, found_channel_right; - const char *hrir_file; - unsigned i, j, found_channel_left, found_channel_right; - float *hrir_data; + pa_sample_spec ss; + pa_channel_map map; - pa_sample_spec hrir_ss; - pa_channel_map hrir_map; + float *hrir_data=NULL, *hrir_right_data=NULL; + float *hrir_temp_data; + size_t hrir_samples; + size_t hrir_copied_length, hrir_total_length; + int hrir_channels; + int fftlen; - pa_sample_spec hrir_temp_ss; - pa_memchunk hrir_temp_chunk, hrir_temp_chunk_resampled; + float *impulse_temp=NULL; + + unsigned *mapping_left=NULL; + unsigned *mapping_right=NULL; + + fftwf_plan p; + + pa_channel_map hrir_map, hrir_right_map; + + pa_sample_spec hrir_left_temp_ss; + pa_memchunk hrir_left_temp_chunk, hrir_left_temp_chunk_resampled; pa_resampler *resampler; - size_t hrir_copied_length, hrir_total_length; - hrir_temp_chunk.memblock = NULL; - hrir_temp_chunk_resampled.memblock = NULL; + pa_sample_spec hrir_right_temp_ss; + pa_memchunk hrir_right_temp_chunk, hrir_right_temp_chunk_resampled; pa_assert(m); + hrir_left_temp_chunk.memblock = NULL; + hrir_left_temp_chunk_resampled.memblock = NULL; + hrir_right_temp_chunk.memblock = NULL; + hrir_right_temp_chunk_resampled.memblock = NULL; + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments."); goto fail; @@ -629,45 +759,61 @@ int pa__init(pa_module*m) { "please use the 'sink_master' argument instead."); } - master = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK); - if (!master) { - pa_log("Master sink not found."); + if (!(master = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK))) { + pa_log("Master sink not found"); goto fail; } + hrir_left_file = pa_modargs_get_value(ma, "hrir_left", NULL); + if (!hrir_left_file) { + hrir_left_file = pa_modargs_get_value(ma, "hrir", NULL); + if (!hrir_left_file) { + pa_log("Either the 'hrir' or 'hrir_left' module arguments are required."); + goto fail; + } + } + + hrir_right_file = pa_modargs_get_value(ma, "hrir_right", NULL); + pa_assert(master); - u = pa_xnew0(struct userdata, 1); - u->module = m; - m->userdata = u; - - /* Initialize hrir and input buffer */ - /* this is the hrir file for the left ear! */ - if (!(hrir_file = pa_modargs_get_value(ma, "hrir", NULL))) { - pa_log("The mandatory 'hrir' module argument is missing."); - goto fail; - } - - if (pa_sound_file_load(master->core->mempool, hrir_file, &hrir_temp_ss, &hrir_map, &hrir_temp_chunk, NULL) < 0) { + if (pa_sound_file_load(master->core->mempool, hrir_left_file, &hrir_left_temp_ss, &hrir_map, &hrir_left_temp_chunk, NULL) < 0) { pa_log("Cannot load hrir file."); goto fail; } - /* sample spec / map of hrir */ - hrir_ss.format = PA_SAMPLE_FLOAT32; - hrir_ss.rate = master->sample_spec.rate; - hrir_ss.channels = hrir_temp_ss.channels; + if (hrir_right_file) { + if (pa_sound_file_load(master->core->mempool, hrir_right_file, &hrir_right_temp_ss, &hrir_right_map, &hrir_right_temp_chunk, NULL) < 0) { + pa_log("Cannot load hrir_right file."); + goto fail; + } + if (!pa_sample_spec_equal(&hrir_left_temp_ss, &hrir_right_temp_ss)) { + pa_log("Both hrir_left and hrir_right must have the same sample format"); + goto fail; + } + if (!pa_channel_map_equal(&hrir_map, &hrir_right_map)) { + pa_log("Both hrir_left and hrir_right must have the same channel layout"); + goto fail; + } + } - /* sample spec of sink */ - ss = hrir_ss; + ss_input.format = PA_SAMPLE_FLOAT32NE; + ss_input.rate = master->sample_spec.rate; + ss_input.channels = hrir_left_temp_ss.channels; + + ss = ss_input; map = hrir_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { pa_log("Invalid sample format specification or channel map"); goto fail; } - ss.format = PA_SAMPLE_FLOAT32; - hrir_ss.rate = ss.rate; - u->channels = ss.channels; + + ss.format = PA_SAMPLE_FLOAT32NE; + ss_input.rate = ss.rate; + ss_input.channels = ss.channels; + + ss_output = ss_input; + ss_output.channels = 2; if (pa_modargs_get_value_boolean(ma, "use_volume_sharing", &use_volume_sharing) < 0) { pa_log("use_volume_sharing= expects a boolean argument"); @@ -684,14 +830,11 @@ int pa__init(pa_module*m) { goto fail; } - /* sample spec / map of sink input */ - pa_channel_map_init_stereo(&sink_input_map); - sink_input_ss.channels = 2; - sink_input_ss.format = PA_SAMPLE_FLOAT32; - sink_input_ss.rate = ss.rate; + pa_channel_map_init_stereo(&map_output); - u->sink_fs = pa_frame_size(&ss); - u->fs = pa_frame_size(&sink_input_ss); + u = pa_xnew0(struct userdata, 1); + u->module = m; + m->userdata = u; /* Create sink */ pa_sink_new_data_init(&sink_data); @@ -699,7 +842,7 @@ int pa__init(pa_module*m) { sink_data.module = m; if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) sink_data.name = pa_sprintf_malloc("%s.vsurroundsink", master->name); - pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_sample_spec(&sink_data, &ss_input); pa_sink_new_data_set_channel_map(&sink_data, &map); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); @@ -718,8 +861,6 @@ int pa__init(pa_module*m) { } if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *z; - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround Sink %s on %s", sink_data.name, z ? z : master->name); } @@ -743,7 +884,7 @@ int pa__init(pa_module*m) { pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); pa_sink_enable_decibel_volume(u->sink, true); } - /* Normally this flag would be enabled automatically be we can force it. */ + /* Normally this flag would be enabled automatically but we can force it. */ if (force_flat_volume) u->sink->flags |= PA_SINK_FLAT_VOLUME; u->sink->userdata = u; @@ -758,9 +899,8 @@ int pa__init(pa_module*m) { sink_input_data.origin_sink = u->sink; pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Surround Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_sink_input_new_data_set_sample_spec(&sink_input_data, &sink_input_ss); - pa_sink_input_new_data_set_channel_map(&sink_input_data, &sink_input_map); - sink_input_data.flags |= PA_SINK_INPUT_START_CORKED; + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss_output); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map_output); pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); pa_sink_input_new_data_done(&sink_input_data); @@ -786,78 +926,107 @@ int pa__init(pa_module*m) { u->sink->input_to_master = u->sink_input; pa_sink_input_get_silence(u->sink_input, &silence); - u->memblockq = pa_memblockq_new("module-virtual-surround-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); - pa_memblock_unref(silence.memblock); - /* resample hrir */ - resampler = pa_resampler_new(u->sink->core->mempool, &hrir_temp_ss, &hrir_map, &hrir_ss, &hrir_map, u->sink->core->lfe_crossover_freq, + resampler = pa_resampler_new(u->sink->core->mempool, &hrir_left_temp_ss, &hrir_map, &ss_input, &hrir_map, u->sink->core->lfe_crossover_freq, PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP); - u->hrir_samples = hrir_temp_chunk.length / pa_frame_size(&hrir_temp_ss) * hrir_ss.rate / hrir_temp_ss.rate; - if (u->hrir_samples > 64) { - u->hrir_samples = 64; - pa_log("The (resampled) hrir contains more than 64 samples. Only the first 64 samples will be used to limit processor usage."); - } + hrir_samples = hrir_left_temp_chunk.length / pa_frame_size(&hrir_left_temp_ss) * ss_input.rate / hrir_left_temp_ss.rate; - hrir_total_length = u->hrir_samples * pa_frame_size(&hrir_ss); - u->hrir_channels = hrir_ss.channels; + hrir_total_length = hrir_samples * pa_frame_size(&ss_input); + hrir_channels = ss_input.channels; - u->hrir_data = (float *) pa_xmalloc(hrir_total_length); + hrir_data = (float *) pa_xmalloc(hrir_total_length); hrir_copied_length = 0; + u->hrir_samples = hrir_samples; + u->inputs = hrir_channels; + /* add silence to the hrir until we get enough samples out of the resampler */ while (hrir_copied_length < hrir_total_length) { - pa_resampler_run(resampler, &hrir_temp_chunk, &hrir_temp_chunk_resampled); - if (hrir_temp_chunk.memblock != hrir_temp_chunk_resampled.memblock) { + pa_resampler_run(resampler, &hrir_left_temp_chunk, &hrir_left_temp_chunk_resampled); + if (hrir_left_temp_chunk.memblock != hrir_left_temp_chunk_resampled.memblock) { /* Silence input block */ - pa_silence_memblock(hrir_temp_chunk.memblock, &hrir_temp_ss); + pa_silence_memblock(hrir_left_temp_chunk.memblock, &hrir_left_temp_ss); } - if (hrir_temp_chunk_resampled.memblock) { + if (hrir_left_temp_chunk_resampled.memblock) { /* Copy hrir data */ - hrir_data = (float *) pa_memblock_acquire(hrir_temp_chunk_resampled.memblock); + hrir_temp_data = (float *) pa_memblock_acquire(hrir_left_temp_chunk_resampled.memblock); - if (hrir_total_length - hrir_copied_length >= hrir_temp_chunk_resampled.length) { - memcpy(u->hrir_data + hrir_copied_length, hrir_data, hrir_temp_chunk_resampled.length); - hrir_copied_length += hrir_temp_chunk_resampled.length; + if (hrir_total_length - hrir_copied_length >= hrir_left_temp_chunk_resampled.length) { + memcpy(hrir_data + hrir_copied_length, hrir_temp_data, hrir_left_temp_chunk_resampled.length); + hrir_copied_length += hrir_left_temp_chunk_resampled.length; } else { - memcpy(u->hrir_data + hrir_copied_length, hrir_data, hrir_total_length - hrir_copied_length); + memcpy(hrir_data + hrir_copied_length, hrir_temp_data, hrir_total_length - hrir_copied_length); hrir_copied_length = hrir_total_length; } - pa_memblock_release(hrir_temp_chunk_resampled.memblock); - pa_memblock_unref(hrir_temp_chunk_resampled.memblock); - hrir_temp_chunk_resampled.memblock = NULL; + pa_memblock_release(hrir_left_temp_chunk_resampled.memblock); + pa_memblock_unref(hrir_left_temp_chunk_resampled.memblock); + hrir_left_temp_chunk_resampled.memblock = NULL; } } + pa_memblock_unref(hrir_left_temp_chunk.memblock); + hrir_left_temp_chunk.memblock = NULL; + + if (hrir_right_file) { + pa_resampler_reset(resampler); + + hrir_right_data = (float *) pa_xmalloc(hrir_total_length); + hrir_copied_length = 0; + + while (hrir_copied_length < hrir_total_length) { + pa_resampler_run(resampler, &hrir_right_temp_chunk, &hrir_right_temp_chunk_resampled); + if (hrir_right_temp_chunk.memblock != hrir_right_temp_chunk_resampled.memblock) { + /* Silence input block */ + pa_silence_memblock(hrir_right_temp_chunk.memblock, &hrir_right_temp_ss); + } + + if (hrir_right_temp_chunk_resampled.memblock) { + /* Copy hrir data */ + hrir_temp_data = (float *) pa_memblock_acquire(hrir_right_temp_chunk_resampled.memblock); + + if (hrir_total_length - hrir_copied_length >= hrir_right_temp_chunk_resampled.length) { + memcpy(hrir_right_data + hrir_copied_length, hrir_temp_data, hrir_right_temp_chunk_resampled.length); + hrir_copied_length += hrir_right_temp_chunk_resampled.length; + } else { + memcpy(hrir_right_data + hrir_copied_length, hrir_temp_data, hrir_total_length - hrir_copied_length); + hrir_copied_length = hrir_total_length; + } + + pa_memblock_release(hrir_right_temp_chunk_resampled.memblock); + pa_memblock_unref(hrir_right_temp_chunk_resampled.memblock); + hrir_right_temp_chunk_resampled.memblock = NULL; + } + } + + pa_memblock_unref(hrir_right_temp_chunk.memblock); + hrir_right_temp_chunk.memblock = NULL; + } + pa_resampler_free(resampler); - pa_memblock_unref(hrir_temp_chunk.memblock); - hrir_temp_chunk.memblock = NULL; - - if (hrir_map.channels < map.channels) { - pa_log("hrir file does not have enough channels!"); - goto fail; - } - - normalize_hrir(u); + if (hrir_right_data) + normalize_hrir_stereo(hrir_data, hrir_right_data, hrir_samples, hrir_channels); + else + normalize_hrir(hrir_data, hrir_samples, hrir_channels); /* create mapping between hrir and input */ - u->mapping_left = (unsigned *) pa_xnew0(unsigned, u->channels); - u->mapping_right = (unsigned *) pa_xnew0(unsigned, u->channels); + mapping_left = (unsigned *) pa_xnew0(unsigned, hrir_channels); + mapping_right = (unsigned *) pa_xnew0(unsigned, hrir_channels); for (i = 0; i < map.channels; i++) { found_channel_left = 0; found_channel_right = 0; for (j = 0; j < hrir_map.channels; j++) { if (hrir_map.map[j] == map.map[i]) { - u->mapping_left[i] = j; + mapping_left[i] = j; found_channel_left = 1; } if (hrir_map.map[j] == mirror_channel(map.map[i])) { - u->mapping_right[i] = j; + mapping_right[i] = j; found_channel_right = 1; } } @@ -872,25 +1041,130 @@ int pa__init(pa_module*m) { } } - u->input_buffer = pa_xmalloc0(u->hrir_samples * u->sink_fs); - u->input_buffer_offset = 0; + fftlen = (hrir_samples + BLOCK_SIZE + 1); /* Grow a bit for overlap */ + { + /* Round up to a power of two */ + int pow = 1; + while (fftlen > 2) { pow++; fftlen /= 2; } + fftlen = 2 << pow; + } + + u->fftlen = fftlen; + + u->f_in = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1)); + u->f_out = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1)); + + u->f_ir = (fftwf_complex**) alloc(sizeof(fftwf_complex*), (hrir_channels*2)); + for (i = 0, j = hrir_channels*2; i < j; i++) + u->f_ir[i] = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1)); + + u->revspace = (float*) alloc(sizeof(float), fftlen); + + u->outspace[0] = (float*) alloc(sizeof(float), BLOCK_SIZE); + u->outspace[1] = (float*) alloc(sizeof(float), BLOCK_SIZE); + + u->inspace = (float**) alloc(sizeof(float*), hrir_channels); + for (i = 0; i < hrir_channels; i++) + u->inspace[i] = (float*) alloc(sizeof(float), fftlen); + + u->p_fw = (fftwf_plan*) alloc(sizeof(fftwf_plan), hrir_channels); + for (i = 0; i < hrir_channels; i++) + pa_assert_se(u->p_fw[i] = fftwf_plan_dft_r2c_1d(fftlen, u->inspace[i], u->f_in, FFTW_ESTIMATE)); + + pa_assert_se(u->p_bw = fftwf_plan_dft_c2r_1d(fftlen, u->f_out, u->revspace, FFTW_ESTIMATE)); + + impulse_temp = (float*) alloc(sizeof(float), fftlen); + + if (hrir_right_data) { + for (i = 0; i < hrir_channels; i++) { + for (ear = 0; ear < 2; ear++) { + size_t index = i * 2 + ear; + size_t impulse_index = mapping_left[i]; + float *impulse = (ear == 0) ? hrir_data : hrir_right_data; + for (j = 0; j < hrir_samples; j++) { + impulse_temp[j] = impulse[j * hrir_channels + impulse_index]; + } + + p = fftwf_plan_dft_r2c_1d(fftlen, impulse_temp, u->f_ir[index], FFTW_ESTIMATE); + if (p) { + fftwf_execute(p); + fftwf_destroy_plan(p); + } else { + pa_log("fftw plan creation failed for %s ear speaker index %d", (ear == 0) ? "left" : "right", i); + goto fail; + } + } + } + } else { + for (i = 0; i < hrir_channels; i++) { + for (ear = 0; ear < 2; ear++) { + size_t index = i * 2 + ear; + size_t impulse_index = (ear == 0) ? mapping_left[i] : mapping_right[i]; + for (j = 0; j < hrir_samples; j++) { + impulse_temp[j] = hrir_data[j * hrir_channels + impulse_index]; + } + + p = fftwf_plan_dft_r2c_1d(fftlen, impulse_temp, u->f_ir[index], FFTW_ESTIMATE); + if (p) { + fftwf_execute(p); + fftwf_destroy_plan(p); + } else { + pa_log("fftw plan creation failed for %s ear speaker index %d", (ear == 0) ? "left" : "right", i); + goto fail; + } + } + } + } + + pa_xfree(impulse_temp); + + pa_xfree(hrir_data); + if (hrir_right_data) + pa_xfree(hrir_right_data); + + pa_xfree(mapping_left); + pa_xfree(mapping_right); + + u->memblockq_sink = pa_memblockq_new("module-virtual-surround-sink memblockq (input)", 0, MEMBLOCKQ_MAXLENGTH, sink_bytes(u, BLOCK_SIZE), &ss_input, 0, 0, sink_bytes(u, u->fftlen), &silence); + pa_memblock_unref(silence.memblock); + + pa_memblockq_seek(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE), PA_SEEK_RELATIVE, false); + pa_memblockq_flush_read(u->memblockq_sink); - /* The order here is important. The input must be put first, - * otherwise streams might attach to the sink before the sink - * input is attached to the master. */ - pa_sink_input_put(u->sink_input); pa_sink_put(u->sink); - pa_sink_input_cork(u->sink_input, false); + pa_sink_input_put(u->sink_input); pa_modargs_free(ma); + return 0; fail: - if (hrir_temp_chunk.memblock) - pa_memblock_unref(hrir_temp_chunk.memblock); + if (impulse_temp) + pa_xfree(impulse_temp); - if (hrir_temp_chunk_resampled.memblock) - pa_memblock_unref(hrir_temp_chunk_resampled.memblock); + if (mapping_left) + pa_xfree(mapping_left); + + if (mapping_right) + pa_xfree(mapping_right); + + if (hrir_data) + pa_xfree(hrir_data); + + if (hrir_right_data) + pa_xfree(hrir_right_data); + + if (hrir_left_temp_chunk.memblock) + pa_memblock_unref(hrir_left_temp_chunk.memblock); + + if (hrir_left_temp_chunk_resampled.memblock) + pa_memblock_unref(hrir_left_temp_chunk_resampled.memblock); + + if (hrir_right_temp_chunk.memblock) + pa_memblock_unref(hrir_right_temp_chunk.memblock); + + if (hrir_right_temp_chunk_resampled.memblock) + pa_memblock_unref(hrir_right_temp_chunk_resampled.memblock); if (ma) pa_modargs_free(ma); @@ -910,6 +1184,7 @@ int pa__get_n_used(pa_module *m) { } void pa__done(pa_module*m) { + size_t i, j; struct userdata *u; pa_assert(m); @@ -921,32 +1196,60 @@ void pa__done(pa_module*m) { * destruction order! */ if (u->sink_input) - pa_sink_input_cork(u->sink_input, true); + pa_sink_input_unlink(u->sink_input); if (u->sink) pa_sink_unlink(u->sink); - if (u->sink_input) { - pa_sink_input_unlink(u->sink_input); + if (u->sink_input) pa_sink_input_unref(u->sink_input); - } if (u->sink) pa_sink_unref(u->sink); - if (u->memblockq) - pa_memblockq_free(u->memblockq); + if (u->memblockq_sink) + pa_memblockq_free(u->memblockq_sink); - if (u->hrir_data) - pa_xfree(u->hrir_data); + if (u->p_fw) { + for (i = 0, j = u->inputs; i < j; i++) { + if (u->p_fw[i]) + fftwf_destroy_plan(u->p_fw[i]); + } + fftwf_free(u->p_fw); + } - if (u->input_buffer) - pa_xfree(u->input_buffer); + if (u->p_bw) + fftwf_destroy_plan(u->p_bw); - if (u->mapping_left) - pa_xfree(u->mapping_left); - if (u->mapping_right) - pa_xfree(u->mapping_right); + if (u->f_ir) { + for (i = 0, j = u->inputs * 2; i < j; i++) { + if (u->f_ir[i]) + fftwf_free(u->f_ir[i]); + } + fftwf_free(u->f_ir); + } + + if (u->f_out) + fftwf_free(u->f_out); + + if (u->f_in) + fftwf_free(u->f_in); + + if (u->revspace) + fftwf_free(u->revspace); + + if (u->outspace[0]) + fftwf_free(u->outspace[0]); + if (u->outspace[1]) + fftwf_free(u->outspace[1]); + + if (u->inspace) { + for (i = 0, j = u->inputs; i < j; i++) { + if (u->inspace[i]) + fftwf_free(u->inspace[i]); + } + fftwf_free(u->inspace); + } pa_xfree(u); } From b8c656b6645a1ec7d8bef0f1cef557eaba4eaea8 Mon Sep 17 00:00:00 2001 From: Laurent Bigonville Date: Thu, 27 Aug 2020 15:58:55 +0200 Subject: [PATCH 014/865] tests: Fix incompatible pointer type on Debian kfreebsd Debian kfreebsd uses the GNU libc that uses cpu_set_t instead of cpuset_t Also do not include unnecessary headers on this platform Fixes: #851 Part-of: --- src/tests/once-test.c | 4 +--- src/tests/rtstutter.c | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tests/once-test.c b/src/tests/once-test.c index cb5618707..c4d4b4be6 100644 --- a/src/tests/once-test.c +++ b/src/tests/once-test.c @@ -22,10 +22,8 @@ #ifdef HAVE_PTHREAD #include #ifdef HAVE_PTHREAD_SETAFFINITY_NP -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #ifdef __FreeBSD__ #include -#endif #include #include #endif @@ -63,7 +61,7 @@ static void thread_func(void *data) { #ifdef HAVE_PTHREAD_SETAFFINITY_NP static pa_atomic_t i_cpu = PA_ATOMIC_INIT(0); -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#ifdef __FreeBSD__ cpuset_t mask; #else cpu_set_t mask; diff --git a/src/tests/rtstutter.c b/src/tests/rtstutter.c index 56b5146ca..9d74855a3 100644 --- a/src/tests/rtstutter.c +++ b/src/tests/rtstutter.c @@ -29,10 +29,8 @@ #ifdef HAVE_PTHREAD #include #ifdef HAVE_PTHREAD_SETAFFINITY_NP -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #ifdef __FreeBSD__ #include -#endif #include #include #endif @@ -61,7 +59,7 @@ static void work(void *p) { #ifdef HAVE_PTHREAD_SETAFFINITY_NP { -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#ifdef __FreeBSD__ cpuset_t mask; #else cpu_set_t mask; From 0fc54f9e7a9f441a33c87baec39a32ac8efe6e2c Mon Sep 17 00:00:00 2001 From: "Igor V. Kovalenko" Date: Wed, 25 Nov 2020 19:35:04 +0300 Subject: [PATCH 015/865] pa-info: look for alsa-info.sh in /usr/sbin as well At least Gentoo and OpenSUSE install alsa-info.sh in /usr/sbin, look there too. Part-of: --- src/utils/pa-info | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/pa-info b/src/utils/pa-info index 1b1cc29b9..7bee1d8df 100755 --- a/src/utils/pa-info +++ b/src/utils/pa-info @@ -40,6 +40,7 @@ function jacks_do { function alsa_info_do { alsa_info=$(which alsa-info.sh) [ $alsa_info ] || alsa_info=$(which alsa-info) + [ $alsa_info ] || alsa_info='/usr/sbin/alsa-info.sh' [ $alsa_info ] || alsa_info='/usr/share/alsa-base/alsa-info.sh' [ -f $alsa_info ] && { $alsa_info --stdout From 1e7adb4fdbc7db172482dab23e4509191fe5f45f Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Fri, 6 Nov 2020 10:52:33 +0900 Subject: [PATCH 016/865] null-sink: Change block size to 50 msec when norewinds is set playing sound through null sink takes almost 2 seconds at first time playback when norewinds is set. Because block_usec is set 2 seconds at initializing time. The value will be changed 50 msec after calling update_request_latency callback. Part-of: --- src/modules/module-null-sink.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index bbbf83435..6865663da 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -57,8 +57,8 @@ PA_MODULE_USAGE( "norewinds="); #define DEFAULT_SINK_NAME "null" -#define BLOCK_USEC (PA_USEC_PER_SEC * 2) -#define NOREWINDS_MAX_LATENCY_USEC (50*PA_USEC_PER_MSEC) +#define BLOCK_USEC (2 * PA_USEC_PER_SEC) +#define BLOCK_USEC_NOREWINDS (50 * PA_USEC_PER_MSEC) struct userdata { pa_core *core; @@ -318,6 +318,7 @@ int pa__init(pa_module*m) { u->core = m->core; u->module = m; u->rtpoll = pa_rtpoll_new(); + u->block_usec = BLOCK_USEC; if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) { pa_log("pa_thread_mq_init() failed."); @@ -381,13 +382,15 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - u->block_usec = BLOCK_USEC; - nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); - if(pa_modargs_get_value_boolean(ma, "norewinds", &u->norewinds) < 0){ pa_log("Invalid argument, norewinds expects a boolean value."); } + if (u->norewinds) + u->block_usec = BLOCK_USEC_NOREWINDS; + + nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + if(u->norewinds){ pa_sink_set_max_rewind(u->sink, 0); } else { @@ -401,11 +404,7 @@ int pa__init(pa_module*m) { goto fail; } - if(u->norewinds){ - pa_sink_set_latency_range(u->sink, 0, NOREWINDS_MAX_LATENCY_USEC); - } else { - pa_sink_set_latency_range(u->sink, 0, BLOCK_USEC); - } + pa_sink_set_latency_range(u->sink, 0, u->block_usec); pa_sink_put(u->sink); From ecd597995a587705752796e3b8e229430ee6e847 Mon Sep 17 00:00:00 2001 From: "Igor V. Kovalenko" Date: Fri, 27 Nov 2020 23:49:17 +0300 Subject: [PATCH 017/865] build-sys: meson: add oss-output option for OSS output support Restore an option to disable OSS output available with autotools. --- meson.build | 6 +++--- meson_options.txt | 3 +++ src/modules/meson.build | 2 +- src/utils/meson.build | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 217ba0d85..4dae29808 100644 --- a/meson.build +++ b/meson.build @@ -613,8 +613,8 @@ if x11_dep.found() endif # Module dependencies - -if cc.has_header('sys/soundcard.h') +if get_option('oss-output') + assert(cc.has_header('sys/soundcard.h'), 'Need header file for OSS support') cdata.set('HAVE_OSS_OUTPUT', 1) cdata.set('HAVE_OSS_WRAPPER', 1) cdata.set('PULSEDSP_LOCATION', pulsedsp_location) @@ -808,7 +808,7 @@ summary = [ '', 'Enable memfd shared memory: @0@'.format(cdata.has('HAVE_MEMFD')), 'Enable X11: @0@'.format(x11_dep.found()), -# 'Enable OSS Output: @0@'.format(${ENABLE_OSS_OUTPUT}), + 'Enable OSS Output: @0@'.format(get_option('oss-output')), # 'Enable OSS Wrapper: @0@'.format(${ENABLE_OSS_WRAPPER}), # 'Enable EsounD: @0@'.format(${ENABLE_ESOUND}), 'Enable Alsa: @0@'.format(alsa_dep.found()), diff --git a/meson_options.txt b/meson_options.txt index 824f24e08..5283a6d98 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -123,6 +123,9 @@ option('openssl', option('orc', type : 'feature', value : 'auto', description : 'Optimized Inner Loop Runtime Compiler') +option('oss-output', + type : 'boolean', + description : 'Optional OSS output support') option('samplerate', type : 'feature', value : 'disabled', description : 'Optional libsamplerate support (DEPRECATED)') diff --git a/src/modules/meson.build b/src/modules/meson.build index 9a394c3b9..9df6d0e61 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -73,7 +73,7 @@ if cc.has_header('linux/input.h') ] endif -if cc.has_header('sys/soundcard.h') +if get_option('oss-output') subdir('oss') all_modules += [ [ 'module-oss', 'oss/module-oss.c', [], [], [], liboss_util ], diff --git a/src/utils/meson.build b/src/utils/meson.build index dedf4e404..934abb850 100644 --- a/src/utils/meson.build +++ b/src/utils/meson.build @@ -84,7 +84,7 @@ if x11_dep.found() ) endif -if cc.has_header('sys/soundcard.h') +if get_option('oss-output') libpulsecommon_sources = [ 'padsp.c', ] From cb3d12377cb1131fb3627ece66b0b70c2c5e0479 Mon Sep 17 00:00:00 2001 From: "Igor V. Kovalenko" Date: Mon, 30 Nov 2020 13:31:19 +0300 Subject: [PATCH 018/865] 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: --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4dae29808..683f40e1f 100644 --- a/meson.build +++ b/meson.build @@ -125,7 +125,7 @@ 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_quoted('CANONICAL_HOST', target_machine.cpu_family()) cdata.set('PA_MAJOR', pa_version_major) cdata.set('PA_MINOR', pa_version_minor) cdata.set('PA_API_VERSION', pa_api_version) From 4cdc0053c01f83b4b1c55042e594e1951a084c4f Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Tue, 14 Jan 2020 10:15:36 +0100 Subject: [PATCH 019/865] protocol-native: add message sending capability This patch adds the PA_COMMAND_SEND_OBJECT_MESSAGE command to protocol-native so that clients can use the messaging feature introduced in the previous patch. Sending messages can in effect replace the extension system for modules. The approach is more flexible than the extension interface because a generic string format is used to exchange information. Furthermore the messaging system can be used for any object, not only for modules, and is easier to implement than extensions. Part-of: --- PROTOCOL | 17 +++++++++ configure.ac | 2 +- meson.build | 2 +- src/map-file | 1 + src/pulse/introspect.c | 64 +++++++++++++++++++++++++++++++++ src/pulse/introspect.h | 16 +++++++++ src/pulsecore/native-common.h | 3 ++ src/pulsecore/pdispatch.c | 3 ++ src/pulsecore/protocol-native.c | 52 +++++++++++++++++++++++++++ 9 files changed, 158 insertions(+), 2 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 4307898c2..72d3af3c0 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -435,6 +435,23 @@ sink, source and card ports): string availability_group uint32 type +## v35, implemented by >= 15.0 + +Added new command for communication with objects. + +PA_COMMAND_SEND_OBJECT_MESSAGE: +sends a message to an object identified by an object path + +parameters: + string object_path - unique path identifying the object + string message - message name + string message_parameters - additional parameters if required (may be + NULL, which should be treated the same as an + empty string) + +The command returns a string, which may be empty or NULL (NULL should be +treated the same as an empty string). + #### If you just changed the protocol, read this ## module-tunnel depends on the sink/source/sink-input/source-input protocol ## internals, so if you changed these, you might have broken module-tunnel. diff --git a/configure.ac b/configure.ac index 6e6049fec..fb1b518e9 100644 --- a/configure.ac +++ b/configure.ac @@ -42,7 +42,7 @@ AC_SUBST(PA_MINOR, pa_minor) AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor) AC_SUBST(PA_API_VERSION, 12) -AC_SUBST(PA_PROTOCOL_VERSION, 34) +AC_SUBST(PA_PROTOCOL_VERSION, 35) # The stable ABI for client applications, for the version info x:y:z # always will hold x=z diff --git a/meson.build b/meson.build index 683f40e1f..06c44d748 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,7 @@ endif pa_version_major_minor = pa_version_major + '.' + pa_version_minor pa_api_version = 12 -pa_protocol_version = 34 +pa_protocol_version = 35 # The stable ABI for client applications, for the version info x:y:z # always will hold x=z diff --git a/src/map-file b/src/map-file index b0cd1bfc4..ea3f70268 100644 --- a/src/map-file +++ b/src/map-file @@ -87,6 +87,7 @@ pa_context_remove_autoload_by_name; pa_context_remove_sample; pa_context_rttime_new; pa_context_rttime_restart; +pa_context_send_message_to_object; pa_context_set_card_profile_by_index; pa_context_set_card_profile_by_name; pa_context_set_default_sink; diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index 3027f38ed..7fefb9ce0 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -2205,3 +2205,67 @@ pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, in return o; } + +/** Object response string processing **/ + +static void context_string_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + const char *response; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + response = ""; + } else if (pa_tagstruct_gets(t, &response) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (!response) + response = ""; + + if (o->callback) { + pa_context_string_cb_t cb = (pa_context_string_cb_t) o->callback; + cb(o->context, success, response, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_send_message_to_object(pa_context *c, const char *object_path, const char *message, const char *message_parameters, pa_context_string_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SEND_OBJECT_MESSAGE, &tag); + + pa_tagstruct_puts(t, object_path); + pa_tagstruct_puts(t, message); + pa_tagstruct_puts(t, message_parameters); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_string_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index c547bde09..fae44a7d1 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -204,6 +204,12 @@ * Server modules can be remotely loaded and unloaded using * pa_context_load_module() and pa_context_unload_module(). * + * \subsection message_subsec Messages + * + * Server objects like sinks, sink inputs or modules can register a message + * handler to communicate with clients. A message can be sent to a named + * message handler using pa_context_send_message_to_object(). + * * \subsection client_subsec Clients * * The only operation supported on clients is the possibility of kicking @@ -489,6 +495,16 @@ pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_s /** @} */ +/** @{ \name Messages */ + +/** Callback prototype for pa_context_send_message_to_object() \since 15.0 */ +typedef void (*pa_context_string_cb_t)(pa_context *c, int success, const char *response, void *userdata); + +/** Send a message to an object that registered a message handler. \since 15.0 */ +pa_operation* pa_context_send_message_to_object(pa_context *c, const char *recipient_name, const char *message, const char *message_parameters, pa_context_string_cb_t cb, void *userdata); + +/** @} */ + /** @{ \name Clients */ /** Stores information about clients. Please note that this structure diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 70338b9f3..3de960def 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -187,6 +187,9 @@ enum { * BOTH DIRECTIONS */ PA_COMMAND_REGISTER_MEMFD_SHMID, + /* Supported since protocol v34 (14.0) */ + PA_COMMAND_SEND_OBJECT_MESSAGE, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index ab632a5ab..c2d26e63e 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -199,6 +199,9 @@ static const char *command_names[PA_COMMAND_MAX] = { /* Supported since protocol v31 (9.0) */ /* BOTH DIRECTIONS */ [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID", + + /* Supported since protocol v35 (15.0) */ + [PA_COMMAND_SEND_OBJECT_MESSAGE] = "SEND_OBJECT_MESSAGE", }; #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index e8559b239..f8dad57d6 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -4721,6 +4722,55 @@ static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, protocol_error(c); } +/* Send message to an object which registered a handler. Result must be returned as string. */ +static void command_send_object_message(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *object_path = NULL; + const char *message = NULL; + const char *message_parameters = NULL; + const char *client_name; + char *response = NULL; + int ret; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &object_path) < 0 || + pa_tagstruct_gets(t, &message) < 0 || + pa_tagstruct_gets(t, &message_parameters) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, object_path != NULL, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_utf8_valid(object_path), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, message != NULL, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_utf8_valid(message), tag, PA_ERR_INVALID); + if (message_parameters) + CHECK_VALIDITY(c->pstream, pa_utf8_valid(message_parameters), tag, PA_ERR_INVALID); + + client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + pa_log_debug("Client %s sent message %s to path %s", client_name, message, object_path); + if (message_parameters) + pa_log_debug("Message parameters: %s", message_parameters); + + ret = pa_message_handler_send_message(c->protocol->core, object_path, message, message_parameters, &response); + + if (ret < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + + reply = reply_new(tag); + pa_tagstruct_puts(reply, response); + pa_xfree(response); + + pa_pstream_send_tagstruct(c->pstream, reply); +} + static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); uint32_t idx = PA_INVALID_INDEX; @@ -4972,6 +5022,8 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid, + [PA_COMMAND_SEND_OBJECT_MESSAGE] = command_send_object_message, + [PA_COMMAND_EXTENSION] = command_extension }; From 68f2f1395d635576dae2a8aff34d38f06511d153 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Tue, 14 Jan 2020 10:24:36 +0100 Subject: [PATCH 020/865] pactl, pacmd, cli-command: Add send-message command Part-of: --- doc/messaging_api.txt | 15 +++++++++++ man/pactl.1.xml.in | 7 +++++ man/pulse-cli-syntax.5.xml.in | 7 +++++ shell-completion/bash/pulseaudio | 4 +-- shell-completion/zsh/_pulseaudio | 2 ++ src/pulse/introspect.h | 3 ++- src/pulsecore/cli-command.c | 44 ++++++++++++++++++++++++++++++++ src/utils/pacmd.c | 1 + src/utils/pactl.c | 43 ++++++++++++++++++++++++++++++- 9 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 doc/messaging_api.txt diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt new file mode 100644 index 000000000..cbc06e75a --- /dev/null +++ b/doc/messaging_api.txt @@ -0,0 +1,15 @@ +Message API reference + +The message API allows any object within pulseaudio to register a message +handler. A message handler is a function that can be called by clients using +PA_COMMAND_SEND_OBJECT_MESSAGE. A message consists at least of an object path +and a message command, both specified as strings. Additional parameters can +be specified using a single string, but are not mandatory. The message handler +returns an error number as defined in def.h and also returns a string in +the "response" variable. The following reference lists available messages, +their parameters and return values. + +Recipient: +Message: +Parameters: +Return value: diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in index cff628f34..9f4b7abd3 100644 --- a/man/pactl.1.xml.in +++ b/man/pactl.1.xml.in @@ -253,6 +253,13 @@ License along with PulseAudio; if not, see . for possible encodings.

+ + + +