diff options
author | Felipe Sateler <fsateler@debian.org> | 2016-05-13 20:52:16 -0300 |
---|---|---|
committer | Felipe Sateler <fsateler@debian.org> | 2016-05-13 20:52:16 -0300 |
commit | e8f565a24d9a69ae696f0600f2ea992ddbd9f7c6 (patch) | |
tree | 16aaf7ab080b2f314fe3a239774e8f3768bfcfe8 /src | |
parent | ae999803298bb86b891d5ecefb5672e199a8ff2b (diff) |
Imported Upstream version 8.99.1
Diffstat (limited to 'src')
97 files changed, 3782 insertions, 1717 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index b0ca2bc..b600dfb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,9 +48,10 @@ AM_CPPFLAGS = \ -DPA_SRCDIR=\"$(abs_srcdir)\" \ -DPA_BUILDDIR=\"$(abs_builddir)\" \ -DPULSE_LOCALEDIR=\"$(localedir)\" -AM_CFLAGS = \ +AM_CFLAGS = -std=gnu11 \ + $(PTHREAD_CFLAGS) +AM_CXXFLAGS = -std=c++11 \ $(PTHREAD_CFLAGS) -AM_CXXFLAGS = $(AM_CFLAGS) SERVER_CFLAGS = -D__INCLUDED_FROM_PULSE_AUDIO AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) @@ -313,12 +314,13 @@ endif if HAVE_GLIB20 TESTS_default += \ mainloop-test-glib -endif if HAVE_GTK30 +# gtk-test depends on both glib and gtk TESTS_norun += \ gtk-test endif +endif if HAVE_ALSA TESTS_norun += \ @@ -682,7 +684,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/memblock.c pulsecore/memblock.h \ pulsecore/memblockq.c pulsecore/memblockq.h \ pulsecore/memchunk.c pulsecore/memchunk.h \ - pulsecore/native-common.h \ + pulsecore/native-common.c pulsecore/native-common.h \ pulsecore/once.c pulsecore/once.h \ pulsecore/packet.c pulsecore/packet.h \ pulsecore/parseaddr.c pulsecore/parseaddr.h \ @@ -699,6 +701,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/refcnt.h \ pulsecore/srbchannel.c pulsecore/srbchannel.h \ pulsecore/sample-util.c pulsecore/sample-util.h \ + pulsecore/mem.h \ pulsecore/shm.c pulsecore/shm.h \ pulsecore/bitset.c pulsecore/bitset.h \ pulsecore/socket-client.c pulsecore/socket-client.h \ @@ -727,6 +730,11 @@ libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBS libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) +if HAVE_MEMFD +libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += \ + pulsecore/memfd-wrappers.h +endif + if HAVE_X11 libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += \ pulse/client-conf-x11.c pulse/client-conf-x11.h \ @@ -1939,7 +1947,8 @@ module_position_event_sounds_la_LIBADD = $(MODULE_LIBADD) module_position_event_sounds_la_CFLAGS = $(AM_CFLAGS) # Ducking effect based on stream roles -module_role_ducking_la_SOURCES = modules/module-role-ducking.c +module_role_ducking_la_SOURCES = modules/module-role-ducking.c \ + modules/stream-interaction.c modules/stream-interaction.h module_role_ducking_la_LDFLAGS = $(MODULE_LDFLAGS) module_role_ducking_la_LIBADD = $(MODULE_LIBADD) module_role_ducking_la_CFLAGS = $(AM_CFLAGS) @@ -1952,7 +1961,8 @@ module_augment_properties_la_LIBADD = $(MODULE_LIBADD) module_augment_properties_la_CFLAGS = $(AM_CFLAGS) -DDESKTOPFILEDIR=\"/usr/share/applications\" # Cork certain streams while others are active (e.g. cork music when phone streams appear) -module_role_cork_la_SOURCES = modules/module-role-cork.c +module_role_cork_la_SOURCES = modules/module-role-cork.c \ + modules/stream-interaction.c modules/stream-interaction.h module_role_cork_la_LDFLAGS = $(MODULE_LDFLAGS) module_role_cork_la_LIBADD = $(MODULE_LIBADD) module_role_cork_la_CFLAGS = $(AM_CFLAGS) diff --git a/src/Makefile.in b/src/Makefile.in index 158cabb..25c8668 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -171,8 +171,10 @@ bin_PROGRAMS = pulseaudio$(EXEEXT) pacat$(EXEEXT) pactl$(EXEEXT) \ @HAVE_GLIB20_TRUE@am__append_21 = \ @HAVE_GLIB20_TRUE@ mainloop-test-glib -@HAVE_GTK30_TRUE@am__append_22 = \ -@HAVE_GTK30_TRUE@ gtk-test + +# gtk-test depends on both glib and gtk +@HAVE_GLIB20_TRUE@@HAVE_GTK30_TRUE@am__append_22 = \ +@HAVE_GLIB20_TRUE@@HAVE_GTK30_TRUE@ gtk-test @HAVE_ALSA_TRUE@am__append_23 = \ @HAVE_ALSA_TRUE@ alsa-time-test @@ -193,214 +195,217 @@ bin_PROGRAMS = pulseaudio$(EXEEXT) pacat$(EXEEXT) pactl$(EXEEXT) \ @OS_IS_WIN32_TRUE@ pulsecore/winerrno.h @OS_IS_WIN32_FALSE@am__append_26 = pulsecore/poll-posix.c pulsecore/poll.h -@HAVE_X11_TRUE@am__append_27 = \ +@HAVE_MEMFD_TRUE@am__append_27 = \ +@HAVE_MEMFD_TRUE@ pulsecore/memfd-wrappers.h + +@HAVE_X11_TRUE@am__append_28 = \ @HAVE_X11_TRUE@ pulse/client-conf-x11.c pulse/client-conf-x11.h \ @HAVE_X11_TRUE@ pulsecore/x11prop.c pulsecore/x11prop.h -@HAVE_X11_TRUE@am__append_28 = $(X11_CFLAGS) -@HAVE_X11_TRUE@am__append_29 = $(X11_LIBS) -@HAVE_SYSTEMD_DAEMON_TRUE@am__append_30 = $(SYSTEMD_FLAGS) $(SYSTEMDDAEMON_FLAGS) -@HAVE_SYSTEMD_DAEMON_TRUE@am__append_31 = $(SYSTEMD_LIBS) $(SYSTEMDDAEMON_LIBS) -@HAVE_SYSTEMD_JOURNAL_TRUE@am__append_32 = $(SYSTEMD_FLAGS) $(SYSTEMDJOURNAL_FLAGS) -@HAVE_SYSTEMD_JOURNAL_TRUE@am__append_33 = $(SYSTEMD_LIBS) $(SYSTEMDJOURNAL_LIBS) -@OS_IS_WIN32_TRUE@am__append_34 = \ +@HAVE_X11_TRUE@am__append_29 = $(X11_CFLAGS) +@HAVE_X11_TRUE@am__append_30 = $(X11_LIBS) +@HAVE_SYSTEMD_DAEMON_TRUE@am__append_31 = $(SYSTEMD_FLAGS) $(SYSTEMDDAEMON_FLAGS) +@HAVE_SYSTEMD_DAEMON_TRUE@am__append_32 = $(SYSTEMD_LIBS) $(SYSTEMDDAEMON_LIBS) +@HAVE_SYSTEMD_JOURNAL_TRUE@am__append_33 = $(SYSTEMD_FLAGS) $(SYSTEMDJOURNAL_FLAGS) +@HAVE_SYSTEMD_JOURNAL_TRUE@am__append_34 = $(SYSTEMD_LIBS) $(SYSTEMDJOURNAL_LIBS) +@OS_IS_WIN32_TRUE@am__append_35 = \ @OS_IS_WIN32_TRUE@ pulsecore/mutex-win32.c pulsecore/mutex.h \ @OS_IS_WIN32_TRUE@ pulsecore/thread-win32.c pulsecore/thread.h \ @OS_IS_WIN32_TRUE@ pulsecore/semaphore-win32.c pulsecore/semaphore.h -@OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@am__append_35 = \ +@OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@am__append_36 = \ @OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@ pulsecore/mutex-posix.c pulsecore/mutex.h \ @OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@ pulsecore/thread-posix.c pulsecore/thread.h \ @OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@ pulsecore/semaphore-osx.c pulsecore/semaphore.h -@OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@am__append_36 = "-I/Developer/Headers/FlatCarbon/" +@OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@am__append_37 = "-I/Developer/Headers/FlatCarbon/" #libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS += "-framework CoreServices" -@OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@am__append_37 = \ +@OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@am__append_38 = \ @OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@ pulsecore/mutex-posix.c pulsecore/mutex.h \ @OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@ pulsecore/thread-posix.c pulsecore/thread.h \ @OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@ pulsecore/semaphore-posix.c pulsecore/semaphore.h -@HAVE_LIBASYNCNS_TRUE@am__append_38 = $(LIBASYNCNS_CFLAGS) -@HAVE_LIBASYNCNS_TRUE@am__append_39 = $(LIBASYNCNS_LIBS) -@OS_IS_WIN32_TRUE@am__append_40 = pulsecore/dllmain.c -@HAVE_DBUS_TRUE@am__append_41 = \ +@HAVE_LIBASYNCNS_TRUE@am__append_39 = $(LIBASYNCNS_CFLAGS) +@HAVE_LIBASYNCNS_TRUE@am__append_40 = $(LIBASYNCNS_LIBS) +@OS_IS_WIN32_TRUE@am__append_41 = pulsecore/dllmain.c +@HAVE_DBUS_TRUE@am__append_42 = \ @HAVE_DBUS_TRUE@ pulsecore/dbus-util.c pulsecore/dbus-util.h \ @HAVE_DBUS_TRUE@ pulsecore/rtkit.c pulsecore/rtkit.h -@HAVE_DBUS_TRUE@am__append_42 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_43 = $(DBUS_LIBS) -@HAVE_GLIB20_TRUE@am__append_44 = \ +@HAVE_DBUS_TRUE@am__append_43 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_44 = $(DBUS_LIBS) +@HAVE_GLIB20_TRUE@am__append_45 = \ @HAVE_GLIB20_TRUE@ pulse/glib-mainloop.h -@HAVE_GLIB20_TRUE@am__append_45 = \ +@HAVE_GLIB20_TRUE@am__append_46 = \ @HAVE_GLIB20_TRUE@ libpulse-mainloop-glib.la -@HAVE_DBUS_TRUE@am__append_46 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_47 = $(DBUS_LIBS) -@HAVE_OSS_WRAPPER_TRUE@am__append_48 = padsp +@HAVE_DBUS_TRUE@am__append_47 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_48 = $(DBUS_LIBS) @HAVE_OSS_WRAPPER_TRUE@am__append_49 = padsp -@HAVE_NEON_TRUE@am__append_50 = libpulsecore_sconv_neon.la libpulsecore_mix_neon.la libpulsecore_remap_neon.la +@HAVE_OSS_WRAPPER_TRUE@am__append_50 = padsp @HAVE_NEON_TRUE@am__append_51 = libpulsecore_sconv_neon.la libpulsecore_mix_neon.la libpulsecore_remap_neon.la -@HAVE_ORC_TRUE@am__append_52 = pulsecore/svolume_orc.c -@HAVE_ORC_TRUE@am__append_53 = $(ORC_CFLAGS) -@HAVE_ORC_TRUE@am__append_54 = $(ORC_LIBS) -@HAVE_X11_TRUE@am__append_55 = pulsecore/x11wrap.c pulsecore/x11wrap.h -@HAVE_X11_TRUE@am__append_56 = $(X11_CFLAGS) -@HAVE_X11_TRUE@am__append_57 = $(X11_LIBS) -@HAVE_DBUS_TRUE@am__append_58 = \ +@HAVE_NEON_TRUE@am__append_52 = libpulsecore_sconv_neon.la libpulsecore_mix_neon.la libpulsecore_remap_neon.la +@HAVE_ORC_TRUE@am__append_53 = pulsecore/svolume_orc.c +@HAVE_ORC_TRUE@am__append_54 = $(ORC_CFLAGS) +@HAVE_ORC_TRUE@am__append_55 = $(ORC_LIBS) +@HAVE_X11_TRUE@am__append_56 = pulsecore/x11wrap.c pulsecore/x11wrap.h +@HAVE_X11_TRUE@am__append_57 = $(X11_CFLAGS) +@HAVE_X11_TRUE@am__append_58 = $(X11_LIBS) +@HAVE_DBUS_TRUE@am__append_59 = \ @HAVE_DBUS_TRUE@ pulsecore/dbus-shared.c pulsecore/dbus-shared.h \ @HAVE_DBUS_TRUE@ pulsecore/protocol-dbus.c pulsecore/protocol-dbus.h -@HAVE_DBUS_TRUE@am__append_59 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_60 = $(DBUS_LIBS) -@HAVE_GDBM_TRUE@am__append_61 = pulsecore/database-gdbm.c -@HAVE_GDBM_TRUE@am__append_62 = $(GDBM_CFLAGS) -@HAVE_GDBM_TRUE@am__append_63 = $(GDBM_LIBS) -@HAVE_TDB_TRUE@am__append_64 = pulsecore/database-tdb.c -@HAVE_TDB_TRUE@am__append_65 = $(TDB_CFLAGS) -@HAVE_TDB_TRUE@am__append_66 = $(TDB_LIBS) -@HAVE_SIMPLEDB_TRUE@am__append_67 = pulsecore/database-simple.c -@HAVE_SPEEX_TRUE@am__append_68 = pulsecore/resampler/speex.c -@HAVE_SPEEX_TRUE@am__append_69 = $(LIBSPEEX_CFLAGS) -@HAVE_SPEEX_TRUE@am__append_70 = $(LIBSPEEX_LIBS) -@HAVE_SOXR_TRUE@am__append_71 = pulsecore/resampler/soxr.c -@HAVE_SOXR_TRUE@am__append_72 = $(LIBSOXR_CFLAGS) -@HAVE_SOXR_TRUE@am__append_73 = $(LIBSOXR_LIBS) -@HAVE_LIBSAMPLERATE_TRUE@am__append_74 = pulsecore/resampler/libsamplerate.c -@HAVE_LIBSAMPLERATE_TRUE@am__append_75 = $(LIBSAMPLERATE_CFLAGS) -@HAVE_LIBSAMPLERATE_TRUE@am__append_76 = $(LIBSAMPLERATE_LIBS) -@HAVE_WEBRTC_TRUE@am__append_77 = libwebrtc-util.la -@HAVE_ESOUND_TRUE@am__append_78 = \ +@HAVE_DBUS_TRUE@am__append_60 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_61 = $(DBUS_LIBS) +@HAVE_GDBM_TRUE@am__append_62 = pulsecore/database-gdbm.c +@HAVE_GDBM_TRUE@am__append_63 = $(GDBM_CFLAGS) +@HAVE_GDBM_TRUE@am__append_64 = $(GDBM_LIBS) +@HAVE_TDB_TRUE@am__append_65 = pulsecore/database-tdb.c +@HAVE_TDB_TRUE@am__append_66 = $(TDB_CFLAGS) +@HAVE_TDB_TRUE@am__append_67 = $(TDB_LIBS) +@HAVE_SIMPLEDB_TRUE@am__append_68 = pulsecore/database-simple.c +@HAVE_SPEEX_TRUE@am__append_69 = pulsecore/resampler/speex.c +@HAVE_SPEEX_TRUE@am__append_70 = $(LIBSPEEX_CFLAGS) +@HAVE_SPEEX_TRUE@am__append_71 = $(LIBSPEEX_LIBS) +@HAVE_SOXR_TRUE@am__append_72 = pulsecore/resampler/soxr.c +@HAVE_SOXR_TRUE@am__append_73 = $(LIBSOXR_CFLAGS) +@HAVE_SOXR_TRUE@am__append_74 = $(LIBSOXR_LIBS) +@HAVE_LIBSAMPLERATE_TRUE@am__append_75 = pulsecore/resampler/libsamplerate.c +@HAVE_LIBSAMPLERATE_TRUE@am__append_76 = $(LIBSAMPLERATE_CFLAGS) +@HAVE_LIBSAMPLERATE_TRUE@am__append_77 = $(LIBSAMPLERATE_LIBS) +@HAVE_WEBRTC_TRUE@am__append_78 = libwebrtc-util.la +@HAVE_ESOUND_TRUE@am__append_79 = \ @HAVE_ESOUND_TRUE@ libprotocol-esound.la # We need to emulate sendmsg/recvmsg to support this on Win32 -@OS_IS_WIN32_FALSE@am__append_79 = \ +@OS_IS_WIN32_FALSE@am__append_80 = \ @OS_IS_WIN32_FALSE@ librtp.la -@HAVE_AVAHI_TRUE@am__append_80 = \ +@HAVE_AVAHI_TRUE@am__append_81 = \ @HAVE_AVAHI_TRUE@ libavahi-wrap.la -@HAVE_DBUS_TRUE@am__append_81 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_82 = $(DBUS_LIBS) +@HAVE_DBUS_TRUE@am__append_82 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_83 = $(DBUS_LIBS) ################################### # Plug-in libraries # ################################### # Serveral module (e.g. libalsa-util.la) -@HAVE_DBUS_TRUE@am__append_83 = \ +@HAVE_DBUS_TRUE@am__append_84 = \ @HAVE_DBUS_TRUE@ module-console-kit.la -@HAVE_ESOUND_TRUE@am__append_84 = \ +@HAVE_ESOUND_TRUE@am__append_85 = \ @HAVE_ESOUND_TRUE@ module-esound-protocol-tcp.la \ @HAVE_ESOUND_TRUE@ module-esound-sink.la # See comment at librtp.la above -@OS_IS_WIN32_FALSE@am__append_85 = \ +@OS_IS_WIN32_FALSE@am__append_86 = \ @OS_IS_WIN32_FALSE@ module-rtp-send.la \ @OS_IS_WIN32_FALSE@ module-rtp-recv.la -@HAVE_AF_UNIX_TRUE@am__append_86 = \ +@HAVE_AF_UNIX_TRUE@am__append_87 = \ @HAVE_AF_UNIX_TRUE@ module-cli-protocol-unix.la \ @HAVE_AF_UNIX_TRUE@ module-simple-protocol-unix.la \ @HAVE_AF_UNIX_TRUE@ module-http-protocol-unix.la \ @HAVE_AF_UNIX_TRUE@ module-native-protocol-unix.la -@HAVE_AF_UNIX_TRUE@@HAVE_ESOUND_TRUE@am__append_87 = \ +@HAVE_AF_UNIX_TRUE@@HAVE_ESOUND_TRUE@am__append_88 = \ @HAVE_AF_UNIX_TRUE@@HAVE_ESOUND_TRUE@ module-esound-protocol-unix.la -@HAVE_MKFIFO_TRUE@am__append_88 = \ +@HAVE_MKFIFO_TRUE@am__append_89 = \ @HAVE_MKFIFO_TRUE@ module-pipe-sink.la \ @HAVE_MKFIFO_TRUE@ module-pipe-source.la -@HAVE_ESOUND_TRUE@@OS_IS_WIN32_FALSE@am__append_89 = \ +@HAVE_ESOUND_TRUE@@OS_IS_WIN32_FALSE@am__append_90 = \ @HAVE_ESOUND_TRUE@@OS_IS_WIN32_FALSE@ module-esound-compat-spawnfd.la \ @HAVE_ESOUND_TRUE@@OS_IS_WIN32_FALSE@ module-esound-compat-spawnpid.la -@HAVE_REGEX_TRUE@am__append_90 = \ +@HAVE_REGEX_TRUE@am__append_91 = \ @HAVE_REGEX_TRUE@ module-match.la -@HAVE_X11_TRUE@am__append_91 = \ +@HAVE_X11_TRUE@am__append_92 = \ @HAVE_X11_TRUE@ module-x11-bell.la \ @HAVE_X11_TRUE@ module-x11-publish.la \ @HAVE_X11_TRUE@ module-x11-xsmp.la \ @HAVE_X11_TRUE@ module-x11-cork-request.la -@HAVE_OSS_OUTPUT_TRUE@am__append_92 = \ +@HAVE_OSS_OUTPUT_TRUE@am__append_93 = \ @HAVE_OSS_OUTPUT_TRUE@ liboss-util.la \ @HAVE_OSS_OUTPUT_TRUE@ module-oss.la -@HAVE_COREAUDIO_TRUE@am__append_93 = \ +@HAVE_COREAUDIO_TRUE@am__append_94 = \ @HAVE_COREAUDIO_TRUE@ module-coreaudio-detect.la \ @HAVE_COREAUDIO_TRUE@ module-coreaudio-device.la pulselibexec_PROGRAMS = $(am__EXEEXT_15) -@HAVE_ALSA_TRUE@am__append_94 = \ +@HAVE_ALSA_TRUE@am__append_95 = \ @HAVE_ALSA_TRUE@ libalsa-util.la \ @HAVE_ALSA_TRUE@ module-alsa-sink.la \ @HAVE_ALSA_TRUE@ module-alsa-source.la \ @HAVE_ALSA_TRUE@ module-alsa-card.la -@HAVE_SOLARIS_TRUE@am__append_95 = \ +@HAVE_SOLARIS_TRUE@am__append_96 = \ @HAVE_SOLARIS_TRUE@ module-solaris.la -@HAVE_AVAHI_TRUE@am__append_96 = \ +@HAVE_AVAHI_TRUE@am__append_97 = \ @HAVE_AVAHI_TRUE@ module-zeroconf-publish.la \ @HAVE_AVAHI_TRUE@ module-zeroconf-discover.la -@HAVE_BONJOUR_TRUE@am__append_97 = \ +@HAVE_BONJOUR_TRUE@am__append_98 = \ @HAVE_BONJOUR_TRUE@ module-bonjour-publish.la -@HAVE_LIRC_TRUE@am__append_98 = \ +@HAVE_LIRC_TRUE@am__append_99 = \ @HAVE_LIRC_TRUE@ module-lirc.la -@HAVE_XEN_TRUE@am__append_99 = \ +@HAVE_XEN_TRUE@am__append_100 = \ @HAVE_XEN_TRUE@ module-xenpv-sink.la -@HAVE_EVDEV_TRUE@am__append_100 = \ +@HAVE_EVDEV_TRUE@am__append_101 = \ @HAVE_EVDEV_TRUE@ module-mmkbd-evdev.la -@HAVE_JACK_TRUE@am__append_101 = \ +@HAVE_JACK_TRUE@am__append_102 = \ @HAVE_JACK_TRUE@ module-jack-sink.la \ @HAVE_JACK_TRUE@ module-jack-source.la -@HAVE_DBUS_TRUE@@HAVE_JACK_TRUE@am__append_102 = \ +@HAVE_DBUS_TRUE@@HAVE_JACK_TRUE@am__append_103 = \ @HAVE_DBUS_TRUE@@HAVE_JACK_TRUE@ module-jackdbus-detect.la -@HAVE_GCONF_TRUE@am__append_103 = \ +@HAVE_GCONF_TRUE@am__append_104 = \ @HAVE_GCONF_TRUE@ module-gconf.la -@HAVE_GCONF_TRUE@am__append_104 = \ +@HAVE_GCONF_TRUE@am__append_105 = \ @HAVE_GCONF_TRUE@ gconf-helper -@HAVE_WAVEOUT_TRUE@am__append_105 = \ +@HAVE_WAVEOUT_TRUE@am__append_106 = \ @HAVE_WAVEOUT_TRUE@ module-waveout.la -@HAVE_HAL_COMPAT_TRUE@am__append_106 = \ +@HAVE_HAL_COMPAT_TRUE@am__append_107 = \ @HAVE_HAL_COMPAT_TRUE@ module-hal-detect.la -@HAVE_UDEV_TRUE@am__append_107 = \ +@HAVE_UDEV_TRUE@am__append_108 = \ @HAVE_UDEV_TRUE@ module-udev-detect.la -@HAVE_SYSTEMD_LOGIN_TRUE@am__append_108 = \ +@HAVE_SYSTEMD_LOGIN_TRUE@am__append_109 = \ @HAVE_SYSTEMD_LOGIN_TRUE@ module-systemd-login.la -@HAVE_DBUS_TRUE@am__append_109 = \ +@HAVE_DBUS_TRUE@am__append_110 = \ @HAVE_DBUS_TRUE@ module-rygel-media-server.la \ @HAVE_DBUS_TRUE@ module-dbus-protocol.la -@HAVE_BLUEZ_TRUE@am__append_110 = \ +@HAVE_BLUEZ_TRUE@am__append_111 = \ @HAVE_BLUEZ_TRUE@ module-bluetooth-discover.la \ @HAVE_BLUEZ_TRUE@ module-bluetooth-policy.la -@HAVE_BLUEZ_4_TRUE@am__append_111 = \ +@HAVE_BLUEZ_4_TRUE@am__append_112 = \ @HAVE_BLUEZ_4_TRUE@ libbluez4-util.la \ @HAVE_BLUEZ_4_TRUE@ module-bluez4-discover.la \ @HAVE_BLUEZ_4_TRUE@ module-bluez4-device.la -@HAVE_BLUEZ_5_TRUE@am__append_112 = \ +@HAVE_BLUEZ_5_TRUE@am__append_113 = \ @HAVE_BLUEZ_5_TRUE@ libbluez5-util.la \ @HAVE_BLUEZ_5_TRUE@ module-bluez5-discover.la \ @HAVE_BLUEZ_5_TRUE@ module-bluez5-device.la @@ -408,53 +413,53 @@ pulselibexec_PROGRAMS = $(am__EXEEXT_15) # RAOP depends on RTP, and we don't support RTP on Windows, see comment at # librtp.la above. -@HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@am__append_113 = \ +@HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@am__append_114 = \ @HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@ libraop.la \ @HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@ module-raop-sink.la -@HAVE_AVAHI_TRUE@@HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@am__append_114 = \ +@HAVE_AVAHI_TRUE@@HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@am__append_115 = \ @HAVE_AVAHI_TRUE@@HAVE_OPENSSL_TRUE@@OS_IS_WIN32_FALSE@ module-raop-discover.la -@HAVE_DBUS_TRUE@@HAVE_FFTW_TRUE@am__append_115 = \ +@HAVE_DBUS_TRUE@@HAVE_FFTW_TRUE@am__append_116 = \ @HAVE_DBUS_TRUE@@HAVE_FFTW_TRUE@ module-equalizer-sink.la -@HAVE_DBUS_TRUE@@HAVE_FFTW_TRUE@am__append_116 = utils/qpaeq -@HAVE_ESOUND_TRUE@am__append_117 = \ +@HAVE_DBUS_TRUE@@HAVE_FFTW_TRUE@am__append_117 = utils/qpaeq +@HAVE_ESOUND_TRUE@am__append_118 = \ @HAVE_ESOUND_TRUE@ module-esound-protocol-tcp-symdef.h \ @HAVE_ESOUND_TRUE@ module-esound-protocol-unix-symdef.h \ @HAVE_ESOUND_TRUE@ module-esound-compat-spawnfd-symdef.h \ @HAVE_ESOUND_TRUE@ module-esound-compat-spawnpid-symdef.h \ @HAVE_ESOUND_TRUE@ module-esound-sink-symdef.h -@HAVE_DBUS_TRUE@am__append_118 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_119 = $(DBUS_LIBS) -@HAVE_UDEV_TRUE@am__append_120 = modules/udev-util.h modules/udev-util.c -@HAVE_UDEV_TRUE@am__append_121 = $(UDEV_LIBS) -@HAVE_UDEV_TRUE@am__append_122 = $(UDEV_CFLAGS) -@HAVE_DBUS_TRUE@am__append_123 = modules/reserve.h modules/reserve.c modules/reserve-monitor.h modules/reserve-monitor.c -@HAVE_DBUS_TRUE@am__append_124 = $(DBUS_LIBS) -@HAVE_DBUS_TRUE@am__append_125 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_126 = $(DBUS_LIBS) -@HAVE_DBUS_TRUE@am__append_127 = $(DBUS_CFLAGS) -@HAVE_DBUS_TRUE@am__append_128 = $(DBUS_LIBS) -@HAVE_DBUS_TRUE@am__append_129 = $(DBUS_CFLAGS) -@HAVE_ADRIAN_EC_TRUE@am__append_130 = \ +@HAVE_DBUS_TRUE@am__append_119 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_120 = $(DBUS_LIBS) +@HAVE_UDEV_TRUE@am__append_121 = modules/udev-util.h modules/udev-util.c +@HAVE_UDEV_TRUE@am__append_122 = $(UDEV_LIBS) +@HAVE_UDEV_TRUE@am__append_123 = $(UDEV_CFLAGS) +@HAVE_DBUS_TRUE@am__append_124 = modules/reserve.h modules/reserve.c modules/reserve-monitor.h modules/reserve-monitor.c +@HAVE_DBUS_TRUE@am__append_125 = $(DBUS_LIBS) +@HAVE_DBUS_TRUE@am__append_126 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_127 = $(DBUS_LIBS) +@HAVE_DBUS_TRUE@am__append_128 = $(DBUS_CFLAGS) +@HAVE_DBUS_TRUE@am__append_129 = $(DBUS_LIBS) +@HAVE_DBUS_TRUE@am__append_130 = $(DBUS_CFLAGS) +@HAVE_ADRIAN_EC_TRUE@am__append_131 = \ @HAVE_ADRIAN_EC_TRUE@ modules/echo-cancel/adrian-aec.c modules/echo-cancel/adrian-aec.h \ @HAVE_ADRIAN_EC_TRUE@ modules/echo-cancel/adrian.c modules/echo-cancel/adrian.h -@HAVE_ADRIAN_EC_TRUE@am__append_131 = -DHAVE_ADRIAN_EC=1 -@HAVE_ADRIAN_EC_TRUE@am__append_132 = modules/echo-cancel/adrian-aec -@HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@am__append_133 = $(ORC_LIBS) -@HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@am__append_134 = $(ORC_CFLAGS) -I$(top_builddir)/src/modules/echo-cancel -@HAVE_SPEEX_TRUE@am__append_135 = modules/echo-cancel/speex.c -@HAVE_SPEEX_TRUE@am__append_136 = $(LIBSPEEX_CFLAGS) -@HAVE_SPEEX_TRUE@am__append_137 = $(LIBSPEEX_LIBS) -@HAVE_WEBRTC_TRUE@am__append_138 = -DHAVE_WEBRTC=1 -@HAVE_WEBRTC_TRUE@am__append_139 = libwebrtc-util.la -@HAVE_BLUEZ_5_OFONO_HEADSET_TRUE@am__append_140 = \ +@HAVE_ADRIAN_EC_TRUE@am__append_132 = -DHAVE_ADRIAN_EC=1 +@HAVE_ADRIAN_EC_TRUE@am__append_133 = modules/echo-cancel/adrian-aec +@HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@am__append_134 = $(ORC_LIBS) +@HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@am__append_135 = $(ORC_CFLAGS) -I$(top_builddir)/src/modules/echo-cancel +@HAVE_SPEEX_TRUE@am__append_136 = modules/echo-cancel/speex.c +@HAVE_SPEEX_TRUE@am__append_137 = $(LIBSPEEX_CFLAGS) +@HAVE_SPEEX_TRUE@am__append_138 = $(LIBSPEEX_LIBS) +@HAVE_WEBRTC_TRUE@am__append_139 = -DHAVE_WEBRTC=1 +@HAVE_WEBRTC_TRUE@am__append_140 = libwebrtc-util.la +@HAVE_BLUEZ_5_OFONO_HEADSET_TRUE@am__append_141 = \ @HAVE_BLUEZ_5_OFONO_HEADSET_TRUE@ modules/bluetooth/backend-ofono.c -@HAVE_BLUEZ_5_NATIVE_HEADSET_TRUE@am__append_141 = \ +@HAVE_BLUEZ_5_NATIVE_HEADSET_TRUE@am__append_142 = \ @HAVE_BLUEZ_5_NATIVE_HEADSET_TRUE@ modules/bluetooth/backend-native.c subdir = src @@ -462,6 +467,8 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/acx_libwrap.m4 \ $(top_srcdir)/m4/ax_check_define.m4 \ $(top_srcdir)/m4/ax_check_flag.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ $(top_srcdir)/m4/ax_define_dir.m4 \ $(top_srcdir)/m4/ax_pthread.m4 $(top_srcdir)/m4/ax_tls.m4 \ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \ @@ -762,25 +769,26 @@ am__libpulsecommon_@PA_MAJORMINOR@_la_SOURCES_DIST = \ pulsecore/mcalign.c pulsecore/mcalign.h pulsecore/memblock.c \ pulsecore/memblock.h pulsecore/memblockq.c \ pulsecore/memblockq.h pulsecore/memchunk.c \ - pulsecore/memchunk.h pulsecore/native-common.h \ - pulsecore/once.c pulsecore/once.h pulsecore/packet.c \ - pulsecore/packet.h pulsecore/parseaddr.c pulsecore/parseaddr.h \ - pulsecore/pdispatch.c pulsecore/pdispatch.h pulsecore/pid.c \ - pulsecore/pid.h pulsecore/pipe.c pulsecore/pipe.h \ - pulsecore/memtrap.c pulsecore/memtrap.h pulsecore/aupdate.c \ - pulsecore/aupdate.h pulsecore/proplist-util.c \ - pulsecore/proplist-util.h pulsecore/pstream-util.c \ - pulsecore/pstream-util.h pulsecore/pstream.c \ - pulsecore/pstream.h pulsecore/queue.c pulsecore/queue.h \ - pulsecore/random.c pulsecore/random.h pulsecore/refcnt.h \ - pulsecore/srbchannel.c pulsecore/srbchannel.h \ - pulsecore/sample-util.c pulsecore/sample-util.h \ - pulsecore/shm.c pulsecore/shm.h pulsecore/bitset.c \ - pulsecore/bitset.h pulsecore/socket-client.c \ - pulsecore/socket-client.h pulsecore/socket-server.c \ - pulsecore/socket-server.h pulsecore/socket-util.c \ - pulsecore/socket-util.h pulsecore/strbuf.c pulsecore/strbuf.h \ - pulsecore/strlist.c pulsecore/strlist.h pulsecore/svolume_c.c \ + pulsecore/memchunk.h pulsecore/native-common.c \ + pulsecore/native-common.h pulsecore/once.c pulsecore/once.h \ + pulsecore/packet.c pulsecore/packet.h pulsecore/parseaddr.c \ + pulsecore/parseaddr.h pulsecore/pdispatch.c \ + pulsecore/pdispatch.h pulsecore/pid.c pulsecore/pid.h \ + pulsecore/pipe.c pulsecore/pipe.h pulsecore/memtrap.c \ + pulsecore/memtrap.h pulsecore/aupdate.c pulsecore/aupdate.h \ + pulsecore/proplist-util.c pulsecore/proplist-util.h \ + pulsecore/pstream-util.c pulsecore/pstream-util.h \ + pulsecore/pstream.c pulsecore/pstream.h pulsecore/queue.c \ + pulsecore/queue.h pulsecore/random.c pulsecore/random.h \ + pulsecore/refcnt.h pulsecore/srbchannel.c \ + pulsecore/srbchannel.h pulsecore/sample-util.c \ + pulsecore/sample-util.h pulsecore/mem.h pulsecore/shm.c \ + pulsecore/shm.h pulsecore/bitset.c pulsecore/bitset.h \ + pulsecore/socket-client.c pulsecore/socket-client.h \ + pulsecore/socket-server.c pulsecore/socket-server.h \ + pulsecore/socket-util.c pulsecore/socket-util.h \ + pulsecore/strbuf.c pulsecore/strbuf.h pulsecore/strlist.c \ + pulsecore/strlist.h pulsecore/svolume_c.c \ pulsecore/svolume_arm.c pulsecore/svolume_mmx.c \ pulsecore/svolume_sse.c pulsecore/tagstruct.c \ pulsecore/tagstruct.h pulsecore/time-smoother.c \ @@ -789,9 +797,10 @@ am__libpulsecommon_@PA_MAJORMINOR@_la_SOURCES_DIST = \ pulsecore/usergroup.h pulsecore/sndfile-util.c \ pulsecore/sndfile-util.h pulsecore/socket.h \ pulsecore/poll-win32.c pulsecore/poll.h pulsecore/winerrno.h \ - pulsecore/poll-posix.c pulse/client-conf-x11.c \ - pulse/client-conf-x11.h pulsecore/x11prop.c \ - pulsecore/x11prop.h pulsecore/mutex-win32.c pulsecore/mutex.h \ + pulsecore/poll-posix.c pulsecore/memfd-wrappers.h \ + pulse/client-conf-x11.c pulse/client-conf-x11.h \ + pulsecore/x11prop.c pulsecore/x11prop.h \ + pulsecore/mutex-win32.c pulsecore/mutex.h \ pulsecore/thread-win32.c pulsecore/thread.h \ pulsecore/semaphore-win32.c pulsecore/semaphore.h \ pulsecore/mutex-posix.c pulsecore/thread-posix.c \ @@ -800,19 +809,20 @@ am__libpulsecommon_@PA_MAJORMINOR@_la_SOURCES_DIST = \ pulsecore/dbus-util.h pulsecore/rtkit.c pulsecore/rtkit.h @OS_IS_WIN32_TRUE@am__objects_5 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-poll-win32.lo @OS_IS_WIN32_FALSE@am__objects_6 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-poll-posix.lo -@HAVE_X11_TRUE@am__objects_7 = pulse/libpulsecommon_@PA_MAJORMINOR@_la-client-conf-x11.lo \ +am__objects_7 = +@HAVE_X11_TRUE@am__objects_8 = pulse/libpulsecommon_@PA_MAJORMINOR@_la-client-conf-x11.lo \ @HAVE_X11_TRUE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-x11prop.lo -@OS_IS_WIN32_TRUE@am__objects_8 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-mutex-win32.lo \ +@OS_IS_WIN32_TRUE@am__objects_9 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-mutex-win32.lo \ @OS_IS_WIN32_TRUE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-thread-win32.lo \ @OS_IS_WIN32_TRUE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-semaphore-win32.lo -@OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@am__objects_9 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-mutex-posix.lo \ +@OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@am__objects_10 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-mutex-posix.lo \ @OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-thread-posix.lo \ @OS_IS_DARWIN_TRUE@@OS_IS_WIN32_FALSE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-semaphore-osx.lo -@OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@am__objects_10 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-mutex-posix.lo \ +@OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@am__objects_11 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-mutex-posix.lo \ @OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-thread-posix.lo \ @OS_IS_DARWIN_FALSE@@OS_IS_WIN32_FALSE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-semaphore-posix.lo -@OS_IS_WIN32_TRUE@am__objects_11 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-dllmain.lo -@HAVE_DBUS_TRUE@am__objects_12 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-dbus-util.lo \ +@OS_IS_WIN32_TRUE@am__objects_12 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-dllmain.lo +@HAVE_DBUS_TRUE@am__objects_13 = pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-dbus-util.lo \ @HAVE_DBUS_TRUE@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-rtkit.lo am_libpulsecommon_@PA_MAJORMINOR@_la_OBJECTS = \ pulse/libpulsecommon_@PA_MAJORMINOR@_la-client-conf.lo \ @@ -851,6 +861,7 @@ am_libpulsecommon_@PA_MAJORMINOR@_la_OBJECTS = \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memblock.lo \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memblockq.lo \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memchunk.lo \ + pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-once.lo \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-packet.lo \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-parseaddr.lo \ @@ -884,7 +895,7 @@ am_libpulsecommon_@PA_MAJORMINOR@_la_OBJECTS = \ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-sndfile-util.lo \ $(am__objects_5) $(am__objects_6) $(am__objects_7) \ $(am__objects_8) $(am__objects_9) $(am__objects_10) \ - $(am__objects_11) $(am__objects_12) + $(am__objects_11) $(am__objects_12) $(am__objects_13) libpulsecommon_@PA_MAJORMINOR@_la_OBJECTS = \ $(am_libpulsecommon_@PA_MAJORMINOR@_la_OBJECTS) libpulsecommon_@PA_MAJORMINOR@_la_LINK = $(LIBTOOL) $(AM_V_lt) \ @@ -901,7 +912,7 @@ libpulsecore_@PA_MAJORMINOR@_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ libpulsecommon-@PA_MAJORMINOR@.la libpulse.la \ - libpulsecore-foreign.la $(am__append_51) $(am__DEPENDENCIES_7) \ + libpulsecore-foreign.la $(am__append_52) $(am__DEPENDENCIES_7) \ $(am__DEPENDENCIES_5) $(am__DEPENDENCIES_8) \ $(am__DEPENDENCIES_9) $(am__DEPENDENCIES_10) \ $(am__DEPENDENCIES_11) $(am__DEPENDENCIES_12) @@ -955,16 +966,16 @@ am__libpulsecore_@PA_MAJORMINOR@_la_SOURCES_DIST = \ pulsecore/database-tdb.c pulsecore/database-simple.c \ pulsecore/resampler/speex.c pulsecore/resampler/soxr.c \ pulsecore/resampler/libsamplerate.c -@HAVE_ORC_TRUE@am__objects_13 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-svolume_orc.lo -@HAVE_X11_TRUE@am__objects_14 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-x11wrap.lo -@HAVE_DBUS_TRUE@am__objects_15 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-dbus-shared.lo \ +@HAVE_ORC_TRUE@am__objects_14 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-svolume_orc.lo +@HAVE_X11_TRUE@am__objects_15 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-x11wrap.lo +@HAVE_DBUS_TRUE@am__objects_16 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-dbus-shared.lo \ @HAVE_DBUS_TRUE@ pulsecore/libpulsecore_@PA_MAJORMINOR@_la-protocol-dbus.lo -@HAVE_GDBM_TRUE@am__objects_16 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-database-gdbm.lo -@HAVE_TDB_TRUE@am__objects_17 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-database-tdb.lo -@HAVE_SIMPLEDB_TRUE@am__objects_18 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-database-simple.lo -@HAVE_SPEEX_TRUE@am__objects_19 = pulsecore/resampler/libpulsecore_@PA_MAJORMINOR@_la-speex.lo -@HAVE_SOXR_TRUE@am__objects_20 = pulsecore/resampler/libpulsecore_@PA_MAJORMINOR@_la-soxr.lo -@HAVE_LIBSAMPLERATE_TRUE@am__objects_21 = pulsecore/resampler/libpulsecore_@PA_MAJORMINOR@_la-libsamplerate.lo +@HAVE_GDBM_TRUE@am__objects_17 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-database-gdbm.lo +@HAVE_TDB_TRUE@am__objects_18 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-database-tdb.lo +@HAVE_SIMPLEDB_TRUE@am__objects_19 = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-database-simple.lo +@HAVE_SPEEX_TRUE@am__objects_20 = pulsecore/resampler/libpulsecore_@PA_MAJORMINOR@_la-speex.lo +@HAVE_SOXR_TRUE@am__objects_21 = pulsecore/resampler/libpulsecore_@PA_MAJORMINOR@_la-soxr.lo +@HAVE_LIBSAMPLERATE_TRUE@am__objects_22 = pulsecore/resampler/libpulsecore_@PA_MAJORMINOR@_la-libsamplerate.lo am_libpulsecore_@PA_MAJORMINOR@_la_OBJECTS = pulsecore/filter/libpulsecore_@PA_MAJORMINOR@_la-lfe-filter.lo \ pulsecore/filter/libpulsecore_@PA_MAJORMINOR@_la-biquad.lo \ pulsecore/filter/libpulsecore_@PA_MAJORMINOR@_la-crossover.lo \ @@ -1017,9 +1028,9 @@ am_libpulsecore_@PA_MAJORMINOR@_la_OBJECTS = pulsecore/filter/libpulsecore_@PA_M pulsecore/libpulsecore_@PA_MAJORMINOR@_la-source.lo \ pulsecore/libpulsecore_@PA_MAJORMINOR@_la-start-child.lo \ pulsecore/libpulsecore_@PA_MAJORMINOR@_la-thread-mq.lo \ - $(am__objects_13) $(am__objects_14) $(am__objects_15) \ - $(am__objects_16) $(am__objects_17) $(am__objects_18) \ - $(am__objects_19) $(am__objects_20) $(am__objects_21) + $(am__objects_14) $(am__objects_15) $(am__objects_16) \ + $(am__objects_17) $(am__objects_18) $(am__objects_19) \ + $(am__objects_20) $(am__objects_21) $(am__objects_22) @HAVE_ORC_TRUE@nodist_libpulsecore_@PA_MAJORMINOR@_la_OBJECTS = pulsecore/libpulsecore_@PA_MAJORMINOR@_la-svolume-orc-gen.lo libpulsecore_@PA_MAJORMINOR@_la_OBJECTS = \ $(am_libpulsecore_@PA_MAJORMINOR@_la_OBJECTS) \ @@ -1377,19 +1388,19 @@ module_device_restore_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ @HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@ $(am__DEPENDENCIES_1) module_echo_cancel_la_DEPENDENCIES = $(am__DEPENDENCIES_3) \ $(am__DEPENDENCIES_13) $(am__DEPENDENCIES_10) \ - $(am__append_139) + $(am__append_140) am__module_echo_cancel_la_SOURCES_DIST = \ modules/echo-cancel/module-echo-cancel.c \ modules/echo-cancel/null.c modules/echo-cancel/echo-cancel.h \ modules/echo-cancel/adrian-aec.c \ modules/echo-cancel/adrian-aec.h modules/echo-cancel/adrian.c \ modules/echo-cancel/adrian.h modules/echo-cancel/speex.c -@HAVE_ADRIAN_EC_TRUE@am__objects_22 = modules/echo-cancel/module_echo_cancel_la-adrian-aec.lo \ +@HAVE_ADRIAN_EC_TRUE@am__objects_23 = modules/echo-cancel/module_echo_cancel_la-adrian-aec.lo \ @HAVE_ADRIAN_EC_TRUE@ modules/echo-cancel/module_echo_cancel_la-adrian.lo -@HAVE_SPEEX_TRUE@am__objects_23 = modules/echo-cancel/module_echo_cancel_la-speex.lo +@HAVE_SPEEX_TRUE@am__objects_24 = modules/echo-cancel/module_echo_cancel_la-speex.lo am_module_echo_cancel_la_OBJECTS = modules/echo-cancel/module_echo_cancel_la-module-echo-cancel.lo \ modules/echo-cancel/module_echo_cancel_la-null.lo \ - $(am__objects_22) $(am__objects_23) + $(am__objects_23) $(am__objects_24) @HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@nodist_module_echo_cancel_la_OBJECTS = modules/echo-cancel/module_echo_cancel_la-adrian-aec-orc-gen.lo module_echo_cancel_la_OBJECTS = $(am_module_echo_cancel_la_OBJECTS) \ $(nodist_module_echo_cancel_la_OBJECTS) @@ -1742,7 +1753,8 @@ module_rescue_streams_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(module_rescue_streams_la_LDFLAGS) $(LDFLAGS) -o $@ module_role_cork_la_DEPENDENCIES = $(am__DEPENDENCIES_3) am_module_role_cork_la_OBJECTS = \ - modules/module_role_cork_la-module-role-cork.lo + modules/module_role_cork_la-module-role-cork.lo \ + modules/module_role_cork_la-stream-interaction.lo module_role_cork_la_OBJECTS = $(am_module_role_cork_la_OBJECTS) module_role_cork_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -1750,7 +1762,8 @@ module_role_cork_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(module_role_cork_la_LDFLAGS) $(LDFLAGS) -o $@ module_role_ducking_la_DEPENDENCIES = $(am__DEPENDENCIES_3) am_module_role_ducking_la_OBJECTS = \ - modules/module_role_ducking_la-module-role-ducking.lo + modules/module_role_ducking_la-module-role-ducking.lo \ + modules/module_role_ducking_la-stream-interaction.lo module_role_ducking_la_OBJECTS = $(am_module_role_ducking_la_OBJECTS) module_role_ducking_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -2073,7 +2086,7 @@ am__EXEEXT_9 = mainloop-test$(EXEEXT) strlist-test$(EXEEXT) \ $(am__EXEEXT_6) $(am__EXEEXT_7) $(am__EXEEXT_8) @HAVE_SIGXCPU_TRUE@am__EXEEXT_10 = cpulimit-test$(EXEEXT) \ @HAVE_SIGXCPU_TRUE@ cpulimit-test2$(EXEEXT) -@HAVE_GTK30_TRUE@am__EXEEXT_11 = gtk-test$(EXEEXT) +@HAVE_GLIB20_TRUE@@HAVE_GTK30_TRUE@am__EXEEXT_11 = gtk-test$(EXEEXT) @HAVE_ALSA_TRUE@am__EXEEXT_12 = alsa-time-test$(EXEEXT) am__EXEEXT_13 = ipacl-test$(EXEEXT) mcalign-test$(EXEEXT) \ pacat-simple$(EXEEXT) parec-simple$(EXEEXT) \ @@ -2211,19 +2224,19 @@ am__echo_cancel_test_SOURCES_DIST = \ modules/echo-cancel/adrian-aec.c \ modules/echo-cancel/adrian-aec.h modules/echo-cancel/adrian.c \ modules/echo-cancel/adrian.h modules/echo-cancel/speex.c -@HAVE_ADRIAN_EC_TRUE@am__objects_24 = modules/echo-cancel/echo_cancel_test-adrian-aec.$(OBJEXT) \ +@HAVE_ADRIAN_EC_TRUE@am__objects_25 = modules/echo-cancel/echo_cancel_test-adrian-aec.$(OBJEXT) \ @HAVE_ADRIAN_EC_TRUE@ modules/echo-cancel/echo_cancel_test-adrian.$(OBJEXT) -@HAVE_SPEEX_TRUE@am__objects_25 = modules/echo-cancel/echo_cancel_test-speex.$(OBJEXT) -am__objects_26 = modules/echo-cancel/echo_cancel_test-module-echo-cancel.$(OBJEXT) \ +@HAVE_SPEEX_TRUE@am__objects_26 = modules/echo-cancel/echo_cancel_test-speex.$(OBJEXT) +am__objects_27 = modules/echo-cancel/echo_cancel_test-module-echo-cancel.$(OBJEXT) \ modules/echo-cancel/echo_cancel_test-null.$(OBJEXT) \ - $(am__objects_24) $(am__objects_25) -am_echo_cancel_test_OBJECTS = $(am__objects_26) -@HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@am__objects_27 = modules/echo-cancel/echo_cancel_test-adrian-aec-orc-gen.$(OBJEXT) -nodist_echo_cancel_test_OBJECTS = $(am__objects_27) + $(am__objects_25) $(am__objects_26) +am_echo_cancel_test_OBJECTS = $(am__objects_27) +@HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@am__objects_28 = modules/echo-cancel/echo_cancel_test-adrian-aec-orc-gen.$(OBJEXT) +nodist_echo_cancel_test_OBJECTS = $(am__objects_28) echo_cancel_test_OBJECTS = $(am_echo_cancel_test_OBJECTS) \ $(nodist_echo_cancel_test_OBJECTS) am__DEPENDENCIES_14 = $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_13) \ - $(am__DEPENDENCIES_10) $(am__append_139) + $(am__DEPENDENCIES_10) $(am__append_140) echo_cancel_test_DEPENDENCIES = $(am__DEPENDENCIES_14) echo_cancel_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -2342,8 +2355,8 @@ mainloop_test_DEPENDENCIES = $(am__DEPENDENCIES_2) libpulse.la \ mainloop_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(mainloop_test_CFLAGS) \ $(CFLAGS) $(mainloop_test_LDFLAGS) $(LDFLAGS) -o $@ -am__objects_28 = tests/mainloop_test_glib-mainloop-test.$(OBJEXT) -am_mainloop_test_glib_OBJECTS = $(am__objects_28) +am__objects_29 = tests/mainloop_test_glib-mainloop-test.$(OBJEXT) +am_mainloop_test_glib_OBJECTS = $(am__objects_29) mainloop_test_glib_OBJECTS = $(am_mainloop_test_glib_OBJECTS) am__DEPENDENCIES_15 = $(am__DEPENDENCIES_2) libpulse.la \ libpulsecommon-@PA_MAJORMINOR@.la @@ -2469,7 +2482,7 @@ am__pulseaudio_SOURCES_DIST = daemon/caps.c daemon/caps.h \ daemon/dumpmodules.c daemon/dumpmodules.h \ daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h daemon/main.c \ daemon/server-lookup.c daemon/server-lookup.h -@HAVE_DBUS_TRUE@am__objects_29 = \ +@HAVE_DBUS_TRUE@am__objects_30 = \ @HAVE_DBUS_TRUE@ daemon/pulseaudio-server-lookup.$(OBJEXT) am_pulseaudio_OBJECTS = daemon/pulseaudio-caps.$(OBJEXT) \ daemon/pulseaudio-cmdline.$(OBJEXT) \ @@ -2477,7 +2490,7 @@ am_pulseaudio_OBJECTS = daemon/pulseaudio-caps.$(OBJEXT) \ daemon/pulseaudio-daemon-conf.$(OBJEXT) \ daemon/pulseaudio-dumpmodules.$(OBJEXT) \ daemon/pulseaudio-ltdl-bind-now.$(OBJEXT) \ - daemon/pulseaudio-main.$(OBJEXT) $(am__objects_29) + daemon/pulseaudio-main.$(OBJEXT) $(am__objects_30) pulseaudio_OBJECTS = $(am_pulseaudio_OBJECTS) pulseaudio_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(pulseaudio_CFLAGS) \ @@ -3282,8 +3295,10 @@ HAVE_BLUEZ_5 = @HAVE_BLUEZ_5@ HAVE_BLUEZ_5_NATIVE_HEADSET = @HAVE_BLUEZ_5_NATIVE_HEADSET@ HAVE_BLUEZ_5_OFONO_HEADSET = @HAVE_BLUEZ_5_OFONO_HEADSET@ HAVE_COREAUDIO = @HAVE_COREAUDIO@ +HAVE_CXX11 = @HAVE_CXX11@ HAVE_DBUS = @HAVE_DBUS@ HAVE_GLIB20 = @HAVE_GLIB20@ +HAVE_MEMFD = @HAVE_MEMFD@ HAVE_MKFIFO = @HAVE_MKFIFO@ HAVE_NEON = @HAVE_NEON@ HAVE_OSS_OUTPUT = @HAVE_OSS_OUTPUT@ @@ -3519,8 +3534,8 @@ AM_CPPFLAGS = \ -DPA_BUILDDIR=\"$(abs_builddir)\" \ -DPULSE_LOCALEDIR=\"$(localedir)\" -AM_CFLAGS = $(PTHREAD_CFLAGS) $(am__append_1) -AM_CXXFLAGS = $(AM_CFLAGS) $(am__append_2) +AM_CFLAGS = -std=gnu11 $(PTHREAD_CFLAGS) $(am__append_1) +AM_CXXFLAGS = -std=c++11 $(PTHREAD_CFLAGS) $(am__append_2) SERVER_CFLAGS = -D__INCLUDED_FROM_PULSE_AUDIO AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) @@ -3568,7 +3583,7 @@ BUILT_SOURCES = $(am__append_6) $(SYMDEF_FILES) builddirs ################################### # Some minor stuff # ################################### -CLEANFILES = $(am__append_7) $(am__append_49) \ +CLEANFILES = $(am__append_7) $(am__append_50) \ daemon/pulseaudio.desktop @HAVE_ORC_TRUE@ORC_BUILT_SOURCE = $(addsuffix -orc-gen.c,$(ORC_SOURCE)) @HAVE_ORC_TRUE@ORC_BUILT_HEADER = $(addsuffix -orc-gen.h,$(ORC_SOURCE)) @@ -3578,7 +3593,7 @@ CLEANFILES = $(am__append_7) $(am__append_49) \ @HAVE_ORC_TRUE@cp_v_gen = $(cp_v_gen_$(V)) @HAVE_ORC_TRUE@cp_v_gen_ = $(cp_v_gen_$(AM_DEFAULT_VERBOSITY)) @HAVE_ORC_TRUE@cp_v_gen_0 = @echo " CP $@"; -ORC_SOURCE = pulsecore/svolume $(am__append_132) +ORC_SOURCE = pulsecore/svolume $(am__append_133) pulseaudio_SOURCES = daemon/caps.c daemon/caps.h daemon/cmdline.c \ daemon/cmdline.h daemon/cpulimit.c daemon/cpulimit.h \ daemon/daemon-conf.c daemon/daemon-conf.h daemon/dumpmodules.c \ @@ -3605,8 +3620,8 @@ pulseaudio_DEPENDENCIES = libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJ ################################### # Utility programs # ################################### -bin_SCRIPTS = esdcompat $(am__append_16) $(am__append_48) \ - $(am__append_116) +bin_SCRIPTS = esdcompat $(am__append_16) $(am__append_49) \ + $(am__append_117) pacat_SOURCES = utils/pacat.c pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la $(LIBSNDFILE_LIBS) pacat_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) @@ -3637,7 +3652,7 @@ pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) ################################### # We split the foreign code off to not be annoyed by warnings we don't care about -noinst_LTLIBRARIES = liblo-test-util.la $(am__append_50) \ +noinst_LTLIBRARIES = liblo-test-util.la $(am__append_51) \ libpulsecore-foreign.la TESTS_default = mainloop-test strlist-test close-test memblockq-test \ channelmap-test thread-mainloop-test utf8-test format-test \ @@ -3921,25 +3936,26 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = pulse/client-conf.c \ pulsecore/mcalign.c pulsecore/mcalign.h pulsecore/memblock.c \ pulsecore/memblock.h pulsecore/memblockq.c \ pulsecore/memblockq.h pulsecore/memchunk.c \ - pulsecore/memchunk.h pulsecore/native-common.h \ - pulsecore/once.c pulsecore/once.h pulsecore/packet.c \ - pulsecore/packet.h pulsecore/parseaddr.c pulsecore/parseaddr.h \ - pulsecore/pdispatch.c pulsecore/pdispatch.h pulsecore/pid.c \ - pulsecore/pid.h pulsecore/pipe.c pulsecore/pipe.h \ - pulsecore/memtrap.c pulsecore/memtrap.h pulsecore/aupdate.c \ - pulsecore/aupdate.h pulsecore/proplist-util.c \ - pulsecore/proplist-util.h pulsecore/pstream-util.c \ - pulsecore/pstream-util.h pulsecore/pstream.c \ - pulsecore/pstream.h pulsecore/queue.c pulsecore/queue.h \ - pulsecore/random.c pulsecore/random.h pulsecore/refcnt.h \ - pulsecore/srbchannel.c pulsecore/srbchannel.h \ - pulsecore/sample-util.c pulsecore/sample-util.h \ - pulsecore/shm.c pulsecore/shm.h pulsecore/bitset.c \ - pulsecore/bitset.h pulsecore/socket-client.c \ - pulsecore/socket-client.h pulsecore/socket-server.c \ - pulsecore/socket-server.h pulsecore/socket-util.c \ - pulsecore/socket-util.h pulsecore/strbuf.c pulsecore/strbuf.h \ - pulsecore/strlist.c pulsecore/strlist.h pulsecore/svolume_c.c \ + pulsecore/memchunk.h pulsecore/native-common.c \ + pulsecore/native-common.h pulsecore/once.c pulsecore/once.h \ + pulsecore/packet.c pulsecore/packet.h pulsecore/parseaddr.c \ + pulsecore/parseaddr.h pulsecore/pdispatch.c \ + pulsecore/pdispatch.h pulsecore/pid.c pulsecore/pid.h \ + pulsecore/pipe.c pulsecore/pipe.h pulsecore/memtrap.c \ + pulsecore/memtrap.h pulsecore/aupdate.c pulsecore/aupdate.h \ + pulsecore/proplist-util.c pulsecore/proplist-util.h \ + pulsecore/pstream-util.c pulsecore/pstream-util.h \ + pulsecore/pstream.c pulsecore/pstream.h pulsecore/queue.c \ + pulsecore/queue.h pulsecore/random.c pulsecore/random.h \ + pulsecore/refcnt.h pulsecore/srbchannel.c \ + pulsecore/srbchannel.h pulsecore/sample-util.c \ + pulsecore/sample-util.h pulsecore/mem.h pulsecore/shm.c \ + pulsecore/shm.h pulsecore/bitset.c pulsecore/bitset.h \ + pulsecore/socket-client.c pulsecore/socket-client.h \ + pulsecore/socket-server.c pulsecore/socket-server.h \ + pulsecore/socket-util.c pulsecore/socket-util.h \ + pulsecore/strbuf.c pulsecore/strbuf.h pulsecore/strlist.c \ + pulsecore/strlist.h pulsecore/svolume_c.c \ pulsecore/svolume_arm.c pulsecore/svolume_mmx.c \ pulsecore/svolume_sse.c pulsecore/tagstruct.c \ pulsecore/tagstruct.h pulsecore/time-smoother.c \ @@ -3947,22 +3963,22 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = pulse/client-conf.c \ pulsecore/tokenizer.h pulsecore/usergroup.c \ pulsecore/usergroup.h pulsecore/sndfile-util.c \ pulsecore/sndfile-util.h pulsecore/socket.h $(am__append_25) \ - $(am__append_26) $(am__append_27) $(am__append_34) \ - $(am__append_35) $(am__append_37) $(am__append_40) \ - $(am__append_41) + $(am__append_26) $(am__append_27) $(am__append_28) \ + $(am__append_35) $(am__append_36) $(am__append_38) \ + $(am__append_41) $(am__append_42) # proplist-util.h uses these header files, but not the library itself! libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) \ - $(LIBJSON_CFLAGS) $(LIBSNDFILE_CFLAGS) $(am__append_28) \ - $(am__append_30) $(am__append_32) $(GLIB20_CFLAGS) \ - $(GTK30_CFLAGS) $(am__append_36) $(am__append_38) \ - $(am__append_42) + $(LIBJSON_CFLAGS) $(LIBSNDFILE_CFLAGS) $(am__append_29) \ + $(am__append_31) $(am__append_33) $(GLIB20_CFLAGS) \ + $(GTK30_CFLAGS) $(am__append_37) $(am__append_39) \ + $(am__append_43) libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) \ - $(AM_LIBLDFLAGS) -avoid-version $(am__append_29) \ - $(am__append_31) $(am__append_33) + $(AM_LIBLDFLAGS) -avoid-version $(am__append_30) \ + $(am__append_32) $(am__append_34) libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) \ $(LIBJSON_LIBS) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) \ - $(LIBSNDFILE_LIBS) $(am__append_39) $(am__append_43) + $(LIBSNDFILE_LIBS) $(am__append_40) $(am__append_44) ################################### # Client library # @@ -3977,8 +3993,8 @@ pulseinclude_HEADERS = pulse/cdecl.h pulse/channelmap.h \ pulse/sample.h pulse/scache.h pulse/simple.h pulse/stream.h \ pulse/subscribe.h pulse/thread-mainloop.h pulse/timeval.h \ pulse/utf8.h pulse/util.h pulse/version.h pulse/volume.h \ - pulse/xmalloc.h $(am__append_44) -lib_LTLIBRARIES = libpulse.la libpulse-simple.la $(am__append_45) + pulse/xmalloc.h $(am__append_45) +lib_LTLIBRARIES = libpulse.la libpulse-simple.la $(am__append_46) # Public interface libpulse_la_SOURCES = \ @@ -4013,10 +4029,10 @@ libpulse_la_SOURCES = \ pulse/volume.c pulse/volume.h \ pulse/xmalloc.c pulse/xmalloc.h -libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(am__append_46) +libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(am__append_47) libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) \ $(LIBJSON_LIBS) libpulsecommon-@PA_MAJORMINOR@.la \ - $(am__append_47) + $(am__append_48) libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO) libpulse_simple_la_SOURCES = pulse/simple.c pulse/simple.h libpulse_simple_la_CFLAGS = $(AM_CFLAGS) @@ -4088,23 +4104,23 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/source-output.c pulsecore/source-output.h \ pulsecore/source.c pulsecore/source.h pulsecore/start-child.c \ pulsecore/start-child.h pulsecore/thread-mq.c \ - pulsecore/thread-mq.h pulsecore/database.h $(am__append_52) \ - $(am__append_55) $(am__append_58) $(am__append_61) \ - $(am__append_64) $(am__append_67) $(am__append_68) \ - $(am__append_71) $(am__append_74) -libpulsecore_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) \ - $(LIBSNDFILE_CFLAGS) $(WINSOCK_CFLAGS) $(am__append_53) \ + pulsecore/thread-mq.h pulsecore/database.h $(am__append_53) \ $(am__append_56) $(am__append_59) $(am__append_62) \ - $(am__append_65) $(am__append_69) $(am__append_72) \ - $(am__append_75) + $(am__append_65) $(am__append_68) $(am__append_69) \ + $(am__append_72) $(am__append_75) +libpulsecore_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) \ + $(LIBSNDFILE_CFLAGS) $(WINSOCK_CFLAGS) $(am__append_54) \ + $(am__append_57) $(am__append_60) $(am__append_63) \ + $(am__append_66) $(am__append_70) $(am__append_73) \ + $(am__append_76) libpulsecore_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) \ - $(AM_LIBLDFLAGS) -avoid-version $(am__append_57) + $(AM_LIBLDFLAGS) -avoid-version $(am__append_58) libpulsecore_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) \ $(LIBSNDFILE_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) \ libpulsecommon-@PA_MAJORMINOR@.la libpulse.la \ - libpulsecore-foreign.la $(am__append_51) $(am__append_54) \ - $(am__append_60) $(am__append_63) $(am__append_66) \ - $(am__append_70) $(am__append_73) $(am__append_76) + libpulsecore-foreign.la $(am__append_52) $(am__append_55) \ + $(am__append_61) $(am__append_64) $(am__append_67) \ + $(am__append_71) $(am__append_74) $(am__append_77) @HAVE_NEON_TRUE@libpulsecore_sconv_neon_la_SOURCES = pulsecore/sconv_neon.c @HAVE_NEON_TRUE@libpulsecore_sconv_neon_la_CFLAGS = $(AM_CFLAGS) $(NEON_CFLAGS) @HAVE_NEON_TRUE@libpulsecore_mix_neon_la_SOURCES = pulsecore/mix_neon.c @@ -4127,8 +4143,8 @@ libpulsecore_foreign_la_CFLAGS = $(AM_CFLAGS) $(FOREIGN_CFLAGS) ### the head, and not the other way! modlibexec_LTLIBRARIES = libcli.la libprotocol-cli.la \ libprotocol-simple.la libprotocol-http.la \ - libprotocol-native.la $(am__append_77) $(am__append_78) \ - $(am__append_79) $(am__append_80) $(am__append_83) \ + libprotocol-native.la $(am__append_78) $(am__append_79) \ + $(am__append_80) $(am__append_81) $(am__append_84) \ module-cli.la module-cli-protocol-tcp.la \ module-simple-protocol-tcp.la module-null-sink.la \ module-null-source.la module-sine-source.la module-detect.la \ @@ -4149,17 +4165,17 @@ modlibexec_LTLIBRARIES = libcli.la libprotocol-cli.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 module-filter-heuristics.la \ - module-role-ducking.la $(am__append_84) $(am__append_85) \ - $(am__append_86) $(am__append_87) $(am__append_88) \ - $(am__append_89) $(am__append_90) $(am__append_91) \ - $(am__append_92) $(am__append_93) $(am__append_94) \ - $(am__append_95) $(am__append_96) $(am__append_97) \ - $(am__append_98) $(am__append_99) $(am__append_100) \ - $(am__append_101) $(am__append_102) $(am__append_103) \ - $(am__append_105) $(am__append_106) $(am__append_107) \ - $(am__append_108) $(am__append_109) $(am__append_110) \ - $(am__append_111) $(am__append_112) $(am__append_113) \ - $(am__append_114) $(am__append_115) + module-role-ducking.la $(am__append_85) $(am__append_86) \ + $(am__append_87) $(am__append_88) $(am__append_89) \ + $(am__append_90) $(am__append_91) $(am__append_92) \ + $(am__append_93) $(am__append_94) $(am__append_95) \ + $(am__append_96) $(am__append_97) $(am__append_98) \ + $(am__append_99) $(am__append_100) $(am__append_101) \ + $(am__append_102) $(am__append_103) $(am__append_104) \ + $(am__append_106) $(am__append_107) $(am__append_108) \ + $(am__append_109) $(am__append_110) $(am__append_111) \ + $(am__append_112) $(am__append_113) $(am__append_114) \ + $(am__append_115) $(am__append_116) libprotocol_simple_la_SOURCES = pulsecore/protocol-simple.c pulsecore/protocol-simple.h libprotocol_simple_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version libprotocol_simple_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la @@ -4174,11 +4190,11 @@ libprotocol_http_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version libprotocol_http_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la libprotocol_native_la_SOURCES = pulsecore/protocol-native.c pulsecore/protocol-native.h pulsecore/native-common.h libprotocol_native_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) \ - $(am__append_81) + $(am__append_82) libprotocol_native_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version libprotocol_native_la_LIBADD = $(AM_LIBADD) \ libpulsecore-@PA_MAJORMINOR@.la \ - libpulsecommon-@PA_MAJORMINOR@.la libpulse.la $(am__append_82) + libpulsecommon-@PA_MAJORMINOR@.la libpulse.la $(am__append_83) @HAVE_ESOUND_TRUE@libprotocol_esound_la_SOURCES = pulsecore/protocol-esound.c pulsecore/protocol-esound.h pulsecore/esound.h @HAVE_ESOUND_TRUE@libprotocol_esound_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version @HAVE_ESOUND_TRUE@libprotocol_esound_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la @@ -4313,7 +4329,7 @@ SYMDEF_FILES = module-cli-symdef.h module-cli-protocol-tcp-symdef.h \ module-switch-on-connect-symdef.h \ module-switch-on-port-available-symdef.h \ module-filter-apply-symdef.h module-filter-heuristics-symdef.h \ - $(am__append_117) + $(am__append_118) # Simple protocol module_simple_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c @@ -4448,10 +4464,10 @@ module_remap_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_remap_source_la_LIBADD = $(MODULE_LIBADD) module_ladspa_sink_la_SOURCES = modules/module-ladspa-sink.c modules/ladspa.h module_ladspa_sink_la_CFLAGS = -DLADSPA_PATH=\"$(libdir)/ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/local/lib64/ladspa:/usr/lib64/ladspa\" \ - $(AM_CFLAGS) $(SERVER_CFLAGS) $(am__append_118) + $(AM_CFLAGS) $(SERVER_CFLAGS) $(am__append_119) module_ladspa_sink_la_LDFLAGS = $(MODULE_LDFLAGS) module_ladspa_sink_la_LIBADD = $(MODULE_LIBADD) $(LIBLTDL) \ - $(am__append_119) + $(am__append_120) module_equalizer_sink_la_SOURCES = modules/module-equalizer-sink.c module_equalizer_sink_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(DBUS_CFLAGS) $(FFTW_CFLAGS) module_equalizer_sink_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -4535,12 +4551,12 @@ libalsa_util_la_SOURCES = modules/alsa/alsa-util.c \ modules/alsa/alsa-mixer.h modules/alsa/alsa-sink.c \ modules/alsa/alsa-sink.h modules/alsa/alsa-source.c \ modules/alsa/alsa-source.h modules/reserve-wrap.c \ - modules/reserve-wrap.h $(am__append_120) $(am__append_123) + modules/reserve-wrap.h $(am__append_121) $(am__append_124) libalsa_util_la_LDFLAGS = -avoid-version libalsa_util_la_LIBADD = $(MODULE_LIBADD) $(ASOUNDLIB_LIBS) \ - $(am__append_121) $(am__append_124) + $(am__append_122) $(am__append_125) libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) \ - $(ASOUNDLIB_CFLAGS) $(am__append_122) $(am__append_125) + $(ASOUNDLIB_CFLAGS) $(am__append_123) $(am__append_126) module_alsa_sink_la_SOURCES = modules/alsa/module-alsa-sink.c module_alsa_sink_la_LDFLAGS = $(MODULE_LDFLAGS) module_alsa_sink_la_LIBADD = $(MODULE_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la @@ -4619,7 +4635,9 @@ module_position_event_sounds_la_LIBADD = $(MODULE_LIBADD) module_position_event_sounds_la_CFLAGS = $(AM_CFLAGS) # Ducking effect based on stream roles -module_role_ducking_la_SOURCES = modules/module-role-ducking.c +module_role_ducking_la_SOURCES = modules/module-role-ducking.c \ + modules/stream-interaction.c modules/stream-interaction.h + module_role_ducking_la_LDFLAGS = $(MODULE_LDFLAGS) module_role_ducking_la_LIBADD = $(MODULE_LIBADD) module_role_ducking_la_CFLAGS = $(AM_CFLAGS) @@ -4632,7 +4650,9 @@ module_augment_properties_la_LIBADD = $(MODULE_LIBADD) module_augment_properties_la_CFLAGS = $(AM_CFLAGS) -DDESKTOPFILEDIR=\"/usr/share/applications\" # Cork certain streams while others are active (e.g. cork music when phone streams appear) -module_role_cork_la_SOURCES = modules/module-role-cork.c +module_role_cork_la_SOURCES = modules/module-role-cork.c \ + modules/stream-interaction.c modules/stream-interaction.h + module_role_cork_la_LDFLAGS = $(MODULE_LDFLAGS) module_role_cork_la_LIBADD = $(MODULE_LIBADD) module_role_cork_la_CFLAGS = $(AM_CFLAGS) @@ -4647,15 +4667,15 @@ module_device_manager_la_CFLAGS = $(AM_CFLAGS) module_device_restore_la_SOURCES = modules/module-device-restore.c module_device_restore_la_LDFLAGS = $(MODULE_LDFLAGS) module_device_restore_la_LIBADD = $(MODULE_LIBADD) \ - libprotocol-native.la $(am__append_126) -module_device_restore_la_CFLAGS = $(AM_CFLAGS) $(am__append_127) + libprotocol-native.la $(am__append_127) +module_device_restore_la_CFLAGS = $(AM_CFLAGS) $(am__append_128) # Stream volume/muted/device restore module module_stream_restore_la_SOURCES = modules/module-stream-restore.c module_stream_restore_la_LDFLAGS = $(MODULE_LDFLAGS) module_stream_restore_la_LIBADD = $(MODULE_LIBADD) \ - libprotocol-native.la $(am__append_128) -module_stream_restore_la_CFLAGS = $(AM_CFLAGS) $(am__append_129) + libprotocol-native.la $(am__append_129) +module_stream_restore_la_CFLAGS = $(AM_CFLAGS) $(am__append_130) # Card profile restore module module_card_restore_la_SOURCES = modules/module-card-restore.c @@ -4697,13 +4717,13 @@ module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS) module_echo_cancel_la_SOURCES = \ modules/echo-cancel/module-echo-cancel.c \ modules/echo-cancel/null.c modules/echo-cancel/echo-cancel.h \ - $(am__append_130) $(am__append_135) + $(am__append_131) $(am__append_136) module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS) -module_echo_cancel_la_LIBADD = $(MODULE_LIBADD) $(am__append_133) \ - $(am__append_137) $(am__append_139) +module_echo_cancel_la_LIBADD = $(MODULE_LIBADD) $(am__append_134) \ + $(am__append_138) $(am__append_140) module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) \ - $(am__append_131) $(am__append_134) $(am__append_136) \ - $(am__append_138) + $(am__append_132) $(am__append_135) $(am__append_137) \ + $(am__append_139) @HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@nodist_module_echo_cancel_la_SOURCES = \ @HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@ modules/echo-cancel/adrian-aec-orc-gen.c \ @HAVE_ADRIAN_EC_TRUE@@HAVE_ORC_TRUE@ modules/echo-cancel/adrian-aec-orc-gen.h @@ -4799,8 +4819,8 @@ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) # Bluetooth BlueZ 5 sink / source libbluez5_util_la_SOURCES = modules/bluetooth/bluez5-util.c \ modules/bluetooth/bluez5-util.h \ - modules/bluetooth/a2dp-codecs.h $(am__append_140) \ - $(am__append_141) + modules/bluetooth/a2dp-codecs.h $(am__append_141) \ + $(am__append_142) libbluez5_util_la_LDFLAGS = -avoid-version libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) @@ -5348,6 +5368,8 @@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memblockq.lo: \ pulsecore/$(am__dirstamp) pulsecore/$(DEPDIR)/$(am__dirstamp) pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memchunk.lo: \ pulsecore/$(am__dirstamp) pulsecore/$(DEPDIR)/$(am__dirstamp) +pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo: \ + pulsecore/$(am__dirstamp) pulsecore/$(DEPDIR)/$(am__dirstamp) pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-once.lo: \ pulsecore/$(am__dirstamp) pulsecore/$(DEPDIR)/$(am__dirstamp) pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-packet.lo: \ @@ -6074,11 +6096,15 @@ module-rescue-streams.la: $(module_rescue_streams_la_OBJECTS) $(module_rescue_st $(AM_V_CCLD)$(module_rescue_streams_la_LINK) -rpath $(modlibexecdir) $(module_rescue_streams_la_OBJECTS) $(module_rescue_streams_la_LIBADD) $(LIBS) modules/module_role_cork_la-module-role-cork.lo: \ modules/$(am__dirstamp) modules/$(DEPDIR)/$(am__dirstamp) +modules/module_role_cork_la-stream-interaction.lo: \ + modules/$(am__dirstamp) modules/$(DEPDIR)/$(am__dirstamp) module-role-cork.la: $(module_role_cork_la_OBJECTS) $(module_role_cork_la_DEPENDENCIES) $(EXTRA_module_role_cork_la_DEPENDENCIES) $(AM_V_CCLD)$(module_role_cork_la_LINK) -rpath $(modlibexecdir) $(module_role_cork_la_OBJECTS) $(module_role_cork_la_LIBADD) $(LIBS) modules/module_role_ducking_la-module-role-ducking.lo: \ modules/$(am__dirstamp) modules/$(DEPDIR)/$(am__dirstamp) +modules/module_role_ducking_la-stream-interaction.lo: \ + modules/$(am__dirstamp) modules/$(DEPDIR)/$(am__dirstamp) module-role-ducking.la: $(module_role_ducking_la_OBJECTS) $(module_role_ducking_la_DEPENDENCIES) $(EXTRA_module_role_ducking_la_DEPENDENCIES) $(AM_V_CCLD)$(module_role_ducking_la_LINK) -rpath $(modlibexecdir) $(module_role_ducking_la_OBJECTS) $(module_role_ducking_la_LIBADD) $(LIBS) @@ -6915,7 +6941,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_position_event_sounds_la-module-position-event-sounds.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_rescue_streams_la-module-rescue-streams.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_role_cork_la-module-role-cork.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_role_cork_la-stream-interaction.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_role_ducking_la-module-role-ducking.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_role_ducking_la-stream-interaction.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_rygel_media_server_la-module-rygel-media-server.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_simple_protocol_tcp_la-module-protocol-stub.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@modules/$(DEPDIR)/module_simple_protocol_unix_la-module-protocol-stub.Plo@am__quote@ @@ -7072,6 +7100,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-memtrap.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-mutex-posix.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-mutex-win32.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-native-common.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-once.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-packet.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-parseaddr.Plo@am__quote@ @@ -7812,6 +7841,13 @@ pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memchunk.lo: pulsecore/memchunk.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-memchunk.lo `test -f 'pulsecore/memchunk.c' || echo '$(srcdir)/'`pulsecore/memchunk.c +pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo: pulsecore/native-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo -MD -MP -MF pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-native-common.Tpo -c -o pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo `test -f 'pulsecore/native-common.c' || echo '$(srcdir)/'`pulsecore/native-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-native-common.Tpo pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-native-common.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pulsecore/native-common.c' object='pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -c -o pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-native-common.lo `test -f 'pulsecore/native-common.c' || echo '$(srcdir)/'`pulsecore/native-common.c + pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-once.lo: pulsecore/once.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS) $(CFLAGS) -MT pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-once.lo -MD -MP -MF pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-once.Tpo -c -o pulsecore/libpulsecommon_@PA_MAJORMINOR@_la-once.lo `test -f 'pulsecore/once.c' || echo '$(srcdir)/'`pulsecore/once.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-once.Tpo pulsecore/$(DEPDIR)/libpulsecommon_@PA_MAJORMINOR@_la-once.Plo @@ -9023,6 +9059,13 @@ modules/module_role_cork_la-module-role-cork.lo: modules/module-role-cork.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_cork_la_CFLAGS) $(CFLAGS) -c -o modules/module_role_cork_la-module-role-cork.lo `test -f 'modules/module-role-cork.c' || echo '$(srcdir)/'`modules/module-role-cork.c +modules/module_role_cork_la-stream-interaction.lo: modules/stream-interaction.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_cork_la_CFLAGS) $(CFLAGS) -MT modules/module_role_cork_la-stream-interaction.lo -MD -MP -MF modules/$(DEPDIR)/module_role_cork_la-stream-interaction.Tpo -c -o modules/module_role_cork_la-stream-interaction.lo `test -f 'modules/stream-interaction.c' || echo '$(srcdir)/'`modules/stream-interaction.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) modules/$(DEPDIR)/module_role_cork_la-stream-interaction.Tpo modules/$(DEPDIR)/module_role_cork_la-stream-interaction.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='modules/stream-interaction.c' object='modules/module_role_cork_la-stream-interaction.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_cork_la_CFLAGS) $(CFLAGS) -c -o modules/module_role_cork_la-stream-interaction.lo `test -f 'modules/stream-interaction.c' || echo '$(srcdir)/'`modules/stream-interaction.c + modules/module_role_ducking_la-module-role-ducking.lo: modules/module-role-ducking.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_ducking_la_CFLAGS) $(CFLAGS) -MT modules/module_role_ducking_la-module-role-ducking.lo -MD -MP -MF modules/$(DEPDIR)/module_role_ducking_la-module-role-ducking.Tpo -c -o modules/module_role_ducking_la-module-role-ducking.lo `test -f 'modules/module-role-ducking.c' || echo '$(srcdir)/'`modules/module-role-ducking.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) modules/$(DEPDIR)/module_role_ducking_la-module-role-ducking.Tpo modules/$(DEPDIR)/module_role_ducking_la-module-role-ducking.Plo @@ -9030,6 +9073,13 @@ modules/module_role_ducking_la-module-role-ducking.lo: modules/module-role-ducki @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_ducking_la_CFLAGS) $(CFLAGS) -c -o modules/module_role_ducking_la-module-role-ducking.lo `test -f 'modules/module-role-ducking.c' || echo '$(srcdir)/'`modules/module-role-ducking.c +modules/module_role_ducking_la-stream-interaction.lo: modules/stream-interaction.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_ducking_la_CFLAGS) $(CFLAGS) -MT modules/module_role_ducking_la-stream-interaction.lo -MD -MP -MF modules/$(DEPDIR)/module_role_ducking_la-stream-interaction.Tpo -c -o modules/module_role_ducking_la-stream-interaction.lo `test -f 'modules/stream-interaction.c' || echo '$(srcdir)/'`modules/stream-interaction.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) modules/$(DEPDIR)/module_role_ducking_la-stream-interaction.Tpo modules/$(DEPDIR)/module_role_ducking_la-stream-interaction.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='modules/stream-interaction.c' object='modules/module_role_ducking_la-stream-interaction.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_role_ducking_la_CFLAGS) $(CFLAGS) -c -o modules/module_role_ducking_la-stream-interaction.lo `test -f 'modules/stream-interaction.c' || echo '$(srcdir)/'`modules/stream-interaction.c + modules/rtp/module_rtp_recv_la-module-rtp-recv.lo: modules/rtp/module-rtp-recv.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_rtp_recv_la_CFLAGS) $(CFLAGS) -MT modules/rtp/module_rtp_recv_la-module-rtp-recv.lo -MD -MP -MF modules/rtp/$(DEPDIR)/module_rtp_recv_la-module-rtp-recv.Tpo -c -o modules/rtp/module_rtp_recv_la-module-rtp-recv.lo `test -f 'modules/rtp/module-rtp-recv.c' || echo '$(srcdir)/'`modules/rtp/module-rtp-recv.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) modules/rtp/$(DEPDIR)/module_rtp_recv_la-module-rtp-recv.Tpo modules/rtp/$(DEPDIR)/module_rtp_recv_la-module-rtp-recv.Plo diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index 117147d..0454b6d 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -63,6 +63,7 @@ enum { ARG_CHECK, ARG_NO_CPU_LIMIT, ARG_DISABLE_SHM, + ARG_ENABLE_MEMFD, ARG_DUMP_RESAMPLE_METHODS, ARG_SYSTEM, ARG_CLEANUP_SHM, @@ -100,6 +101,7 @@ static const struct option long_options[] = { {"system", 2, 0, ARG_SYSTEM}, {"no-cpu-limit", 2, 0, ARG_NO_CPU_LIMIT}, {"disable-shm", 2, 0, ARG_DISABLE_SHM}, + {"enable-memfd", 2, 0, ARG_ENABLE_MEMFD}, {"dump-resample-methods", 2, 0, ARG_DUMP_RESAMPLE_METHODS}, {"cleanup-shm", 2, 0, ARG_CLEANUP_SHM}, {NULL, 0, 0, 0} @@ -152,7 +154,8 @@ void pa_cmdline_help(const char *argv0) { " --use-pid-file[=BOOL] Create a PID file\n" " --no-cpu-limit[=BOOL] Do not install CPU load limiter on\n" " platforms that support it.\n" - " --disable-shm[=BOOL] Disable shared memory support.\n\n" + " --disable-shm[=BOOL] Disable shared memory support.\n" + " --enable-memfd[=BOOL] Enable memfd shared memory support.\n\n" "STARTUP SCRIPT:\n" " -L, --load=\"MODULE ARGUMENTS\" Load the specified plugin module with\n" @@ -389,6 +392,14 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d conf->disable_shm = !!b; break; + case ARG_ENABLE_MEMFD: + if ((b = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + pa_log(_("--enable-memfd expects boolean argument")); + goto fail; + } + conf->disable_memfd = !b; + break; + default: goto fail; } diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index 288aed2..965a5c8 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -92,6 +92,7 @@ static const pa_daemon_conf default_conf = { #endif .no_cpu_limit = true, .disable_shm = false, + .disable_memfd = true, .lock_memory = false, .deferred_volume = true, .default_n_fragments = 4, @@ -526,6 +527,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { { "cpu-limit", pa_config_parse_not_bool, &c->no_cpu_limit, NULL }, { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, { "enable-shm", pa_config_parse_not_bool, &c->disable_shm, NULL }, + { "enable-memfd", pa_config_parse_not_bool, &c->disable_memfd, NULL }, { "flat-volumes", pa_config_parse_bool, &c->flat_volumes, NULL }, { "lock-memory", pa_config_parse_bool, &c->lock_memory, NULL }, { "enable-deferred-volume", pa_config_parse_bool, &c->deferred_volume, NULL }, diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index 458784c..82b619f 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -66,6 +66,7 @@ typedef struct pa_daemon_conf { system_instance, no_cpu_limit, disable_shm, + disable_memfd, disable_remixing, disable_lfe_remixing, load_default_script_file, diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index 783e326..7a68653 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -19,19 +19,6 @@ # (i.e. not in system mode) changequote(`[', `]')dnl Set up m4 quoting -.nofail - -### Load something into the sample cache -ifelse(@OS_IS_WIN32@, 1, [dnl -load-sample x11-bell %WINDIR%\Media\ding.wav -load-sample-dir-lazy %WINDIR%\Media\*.wav -], [dnl -#load-sample-lazy x11-bell /usr/share/sounds/freedesktop/stereo/bell.oga -#load-sample-lazy pulse-hotplug /usr/share/sounds/freedesktop/stereo/device-added.oga -#load-sample-lazy pulse-coldplug /usr/share/sounds/freedesktop/stereo/device-added.oga -#load-sample-lazy pulse-access /usr/share/sounds/freedesktop/stereo/message.oga -])dnl - .fail ### Automatically restore the volume of streams and devices @@ -174,24 +161,6 @@ load-module module-filter-heuristics load-module module-filter-apply ])dnl -ifelse(@HAVE_X11@, 1, [dnl -# X11 modules should not be started from default.pa so that one daemon -# can be shared by multiple sessions. - -### Load X11 bell module -#load-module module-x11-bell sample=x11-bell - -### Register ourselves in the X11 session manager -#load-module module-x11-xsmp - -### Publish connection data in the X11 root window -#.ifexists module-x11-publish@PA_SOEXT@ -#.nofail -#load-module module-x11-publish -#.fail -#.endif -])dnl - ### Make some devices default #set-default-sink output #set-default-source input diff --git a/src/daemon/main.c b/src/daemon/main.c index c2f47b6..ae1185d 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -1017,7 +1017,9 @@ int main(int argc, char *argv[]) { pa_assert_se(mainloop = pa_mainloop_new()); - if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm, conf->shm_size))) { + if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm, + !conf->disable_shm && !conf->disable_memfd && pa_memfd_is_locally_supported(), + conf->shm_size))) { pa_log(_("pa_core_new() failed.")); goto finish; } diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index 1fe2a02..8079147 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -1518,287 +1518,263 @@ static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) { return 0; } -static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { - snd_mixer_selem_id_t *sid; - snd_mixer_elem_t *me; +static int element_ask_vol_dB(snd_mixer_elem_t *me, pa_alsa_direction_t dir, long value, long *dBvalue) { + if (dir == PA_ALSA_DIRECTION_OUTPUT) + return snd_mixer_selem_ask_playback_vol_dB(me, value, dBvalue); + else + return snd_mixer_selem_ask_capture_vol_dB(me, value, dBvalue); +} - pa_assert(m); - pa_assert(e); - pa_assert(e->path); +static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { - SELEM_INIT(sid, e->alsa_name); + long min_dB = 0, max_dB = 0; + int r; + bool is_mono; + pa_channel_position_t p; - if (!(me = snd_mixer_find_selem(m, sid))) { + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + if (!snd_mixer_selem_has_playback_volume(me)) { + if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)) + e->direction = PA_ALSA_DIRECTION_INPUT; + else + return false; + } + } else { + if (!snd_mixer_selem_has_capture_volume(me)) { + if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)) + e->direction = PA_ALSA_DIRECTION_OUTPUT; + else + return false; + } + } - if (e->required != PA_ALSA_REQUIRED_IGNORE) - return -1; + e->direction_try_other = false; - e->switch_use = PA_ALSA_SWITCH_IGNORE; - e->volume_use = PA_ALSA_VOLUME_IGNORE; - e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume); + else + r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); - return 0; + if (r < 0) { + pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r)); + return false; } - if (e->switch_use != PA_ALSA_SWITCH_IGNORE) { - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { - - if (!snd_mixer_selem_has_playback_switch(me)) { - if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)) - e->direction = PA_ALSA_DIRECTION_INPUT; - else - e->switch_use = PA_ALSA_SWITCH_IGNORE; - } + if (e->min_volume >= e->max_volume) { + pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", + e->min_volume, e->max_volume); + return false; + } + if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { + pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", + e->constant_volume, e->alsa_name, e->min_volume, e->max_volume); + return false; + } - } else { - if (!snd_mixer_selem_has_capture_switch(me)) { - if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)) - e->direction = PA_ALSA_DIRECTION_OUTPUT; - else - e->switch_use = PA_ALSA_SWITCH_IGNORE; - } - } + if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) { + pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " + "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name, + e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); - if (e->switch_use != PA_ALSA_SWITCH_IGNORE) - e->direction_try_other = false; + decibel_fix_free(e->db_fix); + e->db_fix = NULL; } - if (e->volume_use != PA_ALSA_VOLUME_IGNORE) { - - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { - - if (!snd_mixer_selem_has_playback_volume(me)) { - if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)) - e->direction = PA_ALSA_DIRECTION_INPUT; - else - e->volume_use = PA_ALSA_VOLUME_IGNORE; - } + if (e->db_fix) { + e->has_dB = true; + e->min_volume = e->db_fix->min_step; + e->max_volume = e->db_fix->max_step; + min_dB = e->db_fix->db_values[0]; + max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]; + } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0; + else + e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0; - } else { + /* Check that the kernel driver returns consistent limits with + * both _get_*_dB_range() and _ask_*_vol_dB(). */ + if (e->has_dB && !e->db_fix) { + long min_dB_checked = 0; + long max_dB_checked = 0; - if (!snd_mixer_selem_has_capture_volume(me)) { - if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)) - e->direction = PA_ALSA_DIRECTION_OUTPUT; - else - e->volume_use = PA_ALSA_VOLUME_IGNORE; - } + if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) { + pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume); + return false; } - if (e->volume_use != PA_ALSA_VOLUME_IGNORE) { - long min_dB = 0, max_dB = 0; - int r; - - e->direction_try_other = false; + if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) { + pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume); + return false; + } - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume); - else - r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); + if (min_dB != min_dB_checked || max_dB != max_dB_checked) { + pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " + "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " + "%0.2f dB at level %li.", e->alsa_name, min_dB / 100.0, max_dB / 100.0, + min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume); + return false; + } + } - if (r < 0) { - pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r)); - return -1; - } + if (e->has_dB) { + e->min_dB = ((double) min_dB) / 100.0; + e->max_dB = ((double) max_dB) / 100.0; - if (e->min_volume >= e->max_volume) { - pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", e->min_volume, e->max_volume); - e->volume_use = PA_ALSA_VOLUME_IGNORE; - - } else if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && - (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { - pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", - e->constant_volume, e->alsa_name, e->min_volume, e->max_volume); - e->volume_use = PA_ALSA_VOLUME_IGNORE; + if (min_dB >= max_dB) { + pa_assert(!e->db_fix); + pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", + e->min_dB, e->max_dB); + e->has_dB = false; + } + } - } else { - bool is_mono; - pa_channel_position_t p; - - if (e->db_fix && - ((e->min_volume > e->db_fix->min_step) || - (e->max_volume < e->db_fix->max_step))) { - pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " - "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name, - e->db_fix->min_step, e->db_fix->max_step, - e->min_volume, e->max_volume); - - decibel_fix_free(e->db_fix); - e->db_fix = NULL; - } + if (e->volume_limit >= 0) { + if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) + pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " + "%li-%li. The volume limit is ignored.", + e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); + else { + e->max_volume = e->volume_limit; + if (e->has_dB) { if (e->db_fix) { - e->has_dB = true; - e->min_volume = e->db_fix->min_step; - e->max_volume = e->db_fix->max_step; - min_dB = e->db_fix->db_values[0]; - max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]; - } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0; - else - e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0; - - /* Check that the kernel driver returns consistent limits with - * both _get_*_dB_range() and _ask_*_vol_dB(). */ - if (e->has_dB && !e->db_fix) { - long min_dB_checked = 0; - long max_dB_checked = 0; - - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - r = snd_mixer_selem_ask_playback_vol_dB(me, e->min_volume, &min_dB_checked); - else - r = snd_mixer_selem_ask_capture_vol_dB(me, e->min_volume, &min_dB_checked); - - if (r < 0) { - pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume); - return -1; - } - - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB_checked); - else - r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB_checked); - - if (r < 0) { - pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume); - return -1; - } + e->db_fix->max_step = e->max_volume; + e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; + } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) { + pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r)); + e->has_dB = false; + } else + e->max_dB = ((double) max_dB) / 100.0; + } + } + } - if (min_dB != min_dB_checked || max_dB != max_dB_checked) { - pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " - "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " - "%0.2f dB at level %li.", - e->alsa_name, - min_dB / 100.0, max_dB / 100.0, - min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume); - return -1; - } - } + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + is_mono = snd_mixer_selem_is_playback_mono(me) > 0; + else + is_mono = snd_mixer_selem_is_capture_mono(me) > 0; - if (e->has_dB) { - e->min_dB = ((double) min_dB) / 100.0; - e->max_dB = ((double) max_dB) / 100.0; + if (is_mono) { + e->n_channels = 1; - if (min_dB >= max_dB) { - pa_assert(!e->db_fix); - pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB); - e->has_dB = false; - } - } + if (!e->override_map) { + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; + e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0; + } + e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL; + } + e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1]; + return true; + } - if (e->volume_limit >= 0) { - if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) - pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " - "%li-%li. The volume limit is ignored.", - e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); + e->n_channels = 0; + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; - else { - e->max_volume = e->volume_limit; + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; + else + e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; + } - if (e->has_dB) { - if (e->db_fix) { - e->db_fix->max_step = e->max_volume; - e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; + if (e->n_channels <= 0) { + pa_log_warn("Volume element %s with no channels?", e->alsa_name); + return false; + } else if (e->n_channels > 2) { + /* FIXME: In some places code like this is used: + * + * e->masks[alsa_channel_ids[p]][e->n_channels-1] + * + * The definition of e->masks is + * + * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2]; + * + * Since the array size is fixed at 2, we obviously + * don't support elements with more than two + * channels... */ + pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels); + return false; + } - } else { - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB); - else - r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB); - - if (r < 0) { - pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r)); - e->has_dB = false; - } else - e->max_dB = ((double) max_dB) / 100.0; - } - } - } - } + if (!e->override_map) { + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + bool has_channel; - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - is_mono = snd_mixer_selem_is_playback_mono(me) > 0; - else - is_mono = snd_mixer_selem_is_capture_mono(me) > 0; + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; - if (is_mono) { - e->n_channels = 1; + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; + else + has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; - if (!e->override_map) { - for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { - if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) - continue; + e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0; + } + } - e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0; - } + e->merged_mask = 0; + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; - e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL; - } + e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; + } + return true; +} - e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1]; - } else { - e->n_channels = 0; - for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { +static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; - if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) - continue; + pa_assert(m); + pa_assert(e); + pa_assert(e->path); - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; - else - e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; - } + SELEM_INIT(sid, e->alsa_name); - if (e->n_channels <= 0) { - pa_log_warn("Volume element %s with no channels?", e->alsa_name); - e->volume_use = PA_ALSA_VOLUME_IGNORE; - } + if (!(me = snd_mixer_find_selem(m, sid))) { - else if (e->n_channels > 2) { - /* FIXME: In some places code like this is used: - * - * e->masks[alsa_channel_ids[p]][e->n_channels-1] - * - * The definition of e->masks is - * - * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2]; - * - * Since the array size is fixed at 2, we obviously - * don't support elements with more than two - * channels... */ - pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels); - e->volume_use = PA_ALSA_VOLUME_IGNORE; - } + if (e->required != PA_ALSA_REQUIRED_IGNORE) + return -1; - if (!e->override_map) { - for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { - bool has_channel; + e->switch_use = PA_ALSA_SWITCH_IGNORE; + e->volume_use = PA_ALSA_VOLUME_IGNORE; + e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; - if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) - continue; + return 0; + } - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; - else - has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; + if (e->switch_use != PA_ALSA_SWITCH_IGNORE) { + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { - e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0; - } - } + if (!snd_mixer_selem_has_playback_switch(me)) { + if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)) + e->direction = PA_ALSA_DIRECTION_INPUT; + else + e->switch_use = PA_ALSA_SWITCH_IGNORE; + } - e->merged_mask = 0; - for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { - if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) - continue; + } else { - e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; - } - } + if (!snd_mixer_selem_has_capture_switch(me)) { + if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)) + e->direction = PA_ALSA_DIRECTION_OUTPUT; + else + e->switch_use = PA_ALSA_SWITCH_IGNORE; } } + if (e->switch_use != PA_ALSA_SWITCH_IGNORE) + e->direction_try_other = false; } + if (!element_probe_volume(e, me)) + e->volume_use = PA_ALSA_VOLUME_IGNORE; + if (e->switch_use == PA_ALSA_SWITCH_SELECT) { pa_alsa_option *o; diff --git a/src/modules/alsa/mixer/paths/analog-input-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf index 2c94a0b..8163ffb 100644 --- a/src/modules/alsa/mixer/paths/analog-input-linein.conf +++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf @@ -69,6 +69,10 @@ enumeration = select name = analog-input-linein required-any = any +[Option PCM Capture Source:Line In] +name = analog-input-linein +required-any = any + [Element Mic] switch = off volume = off diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf b/src/modules/alsa/mixer/paths/analog-input-mic.conf index 4d64f92..123439b 100644 --- a/src/modules/alsa/mixer/paths/analog-input-mic.conf +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf @@ -76,6 +76,10 @@ enumeration = select name = analog-input-microphone required-any = any +[Option PCM Capture Source:Mic-In/Mic Array] +name = analog-input-microphone +required-any = any + ;;; Some AC'97s have "Mic Select" and "Mic Boost (+20dB)" [Element Mic Select] diff --git a/src/modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf b/src/modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf index e534def..d5922da 100644 --- a/src/modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf +++ b/src/modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf @@ -20,7 +20,8 @@ ; This config supports both cases. ; Also by default there are some non-existing (physically) inputs ; and outputs that are not present here. -; And finally in 2.1 mode LFE is not working, so it is removed also. +; And finally officially supported modes are stereo and 5.1 + stereo S/PDIF, +; so only these modes included. ; ; See default.conf for an explanation on the directives used here. @@ -42,20 +43,6 @@ paths-input = analog-input-mic analog-input-linein channel-map = left,right direction = input -[Mapping analog-surround-41] -device-strings = surround41:%f -channel-map = front-left,front-right,rear-left,rear-right,lfe -paths-output = analog-output -priority = 7 -direction = output - -[Mapping analog-surround-50] -device-strings = surround50:%f -channel-map = front-left,front-right,rear-left,rear-right,front-center -paths-output = analog-output -priority = 8 -direction = output - [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 286cfc9..e5cc4ae 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -369,6 +369,21 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { pa_assert(u); + /* Changing the jack state may cause a port change, and a port change will + * make the sink or source change the mixer settings. If there are multiple + * users having pulseaudio running, the mixer changes done by inactive + * users may mess up the volume settings for the active users, because when + * the inactive users change the mixer settings, those changes are picked + * up by the active user's pulseaudio instance and the changes are + * interpreted as if the active user changed the settings manually e.g. + * with alsamixer. Even single-user systems suffer from this, because gdm + * runs its own pulseaudio instance. + * + * We rerun this function when being unsuspended to catch up on jack state + * changes */ + if (u->card->suspend_cause & PA_SUSPEND_SESSION) + return 0; + if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; @@ -576,6 +591,20 @@ static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *de pa_xfree(t); } +static pa_hook_result_t card_suspend_changed(pa_core *c, pa_card *card, struct userdata *u) { + void *state; + pa_alsa_jack *jack; + + if (card->suspend_cause == 0) { + /* We were unsuspended, update jack state in case it changed while we were suspended */ + PA_HASHMAP_FOREACH(jack, u->jacks, state) { + report_jack_state(jack->melem, 0); + } + } + + return PA_HOOK_OK; +} + static pa_hook_result_t sink_input_put_hook_callback(pa_core *c, pa_sink_input *sink_input, struct userdata *u) { const char *role; pa_sink *sink = sink_input->sink; @@ -782,6 +811,9 @@ int pa__init(pa_module *m) { u->card->userdata = u; u->card->set_profile = card_set_profile; + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_SUSPEND_CHANGED], PA_HOOK_NORMAL, + (pa_hook_cb_t) card_suspend_changed, u); + init_jacks(u); init_profile(u); init_eld_ctls(u); diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c index 60a2b66..3c47fae 100644 --- a/src/modules/echo-cancel/adrian.c +++ b/src/modules/echo-cancel/adrian.c @@ -78,16 +78,16 @@ bool pa_adrian_ec_init(pa_core *c, pa_echo_canceller *ec, rate = out_ss->rate; *nframes = (rate * frame_size_ms) / 1000; - ec->params.priv.adrian.blocksize = (*nframes) * pa_frame_size(out_ss); + ec->params.adrian.blocksize = (*nframes) * pa_frame_size(out_ss); - pa_log_debug ("Using nframes %d, blocksize %u, channels %d, rate %d", *nframes, ec->params.priv.adrian.blocksize, out_ss->channels, out_ss->rate); + pa_log_debug ("Using nframes %d, blocksize %u, channels %d, rate %d", *nframes, ec->params.adrian.blocksize, out_ss->channels, out_ss->rate); /* For now we only support SSE */ if (c->cpu_info.cpu_type == PA_CPU_X86 && (c->cpu_info.flags.x86 & PA_CPU_X86_SSE)) have_vector = 1; - ec->params.priv.adrian.aec = AEC_init(rate, have_vector); - if (!ec->params.priv.adrian.aec) + ec->params.adrian.aec = AEC_init(rate, have_vector); + if (!ec->params.adrian.aec) goto fail; pa_modargs_free(ma); @@ -102,17 +102,17 @@ fail: void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { unsigned int i; - for (i = 0; i < ec->params.priv.adrian.blocksize; i += 2) { + for (i = 0; i < ec->params.adrian.blocksize; i += 2) { /* We know it's S16NE mono data */ int r = *(int16_t *)(rec + i); int p = *(int16_t *)(play + i); - *(int16_t *)(out + i) = (int16_t) AEC_doAEC(ec->params.priv.adrian.aec, r, p); + *(int16_t *)(out + i) = (int16_t) AEC_doAEC(ec->params.adrian.aec, r, p); } } void pa_adrian_ec_done(pa_echo_canceller *ec) { - if (ec->params.priv.adrian.aec) { - AEC_done(ec->params.priv.adrian.aec); - ec->params.priv.adrian.aec = NULL; + if (ec->params.adrian.aec) { + AEC_done(ec->params.adrian.aec); + ec->params.adrian.aec = NULL; } } diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h index 29d1574..ee67949 100644 --- a/src/modules/echo-cancel/echo-cancel.h +++ b/src/modules/echo-cancel/echo-cancel.h @@ -64,13 +64,17 @@ struct pa_echo_canceller_params { /* This is a void* so that we don't have to convert this whole file * to C++ linkage. apm is a pointer to an AudioProcessing object */ void *apm; - uint32_t blocksize; - pa_sample_spec sample_spec; + unsigned int blocksize; /* in frames */ + pa_sample_spec rec_ss, play_ss, out_ss; + float *rec_buffer[PA_CHANNELS_MAX], *play_buffer[PA_CHANNELS_MAX]; /* for deinterleaved buffers */ + void *trace_callback; bool agc; + bool first; + unsigned int agc_start_volume; } webrtc; #endif /* each canceller-specific structure goes here */ - } priv; + }; /* Set this if canceller can do drift compensation. Also see set_drift() * below */ @@ -128,8 +132,8 @@ struct pa_echo_canceller { }; /* Functions to be used by the canceller analog gain control routines */ -void pa_echo_canceller_get_capture_volume(pa_echo_canceller *ec, pa_cvolume *v); -void pa_echo_canceller_set_capture_volume(pa_echo_canceller *ec, pa_cvolume *v); +pa_volume_t pa_echo_canceller_get_capture_volume(pa_echo_canceller *ec); +void pa_echo_canceller_set_capture_volume(pa_echo_canceller *ec, pa_volume_t volume); /* Computes EC block size in frames (rounded down to nearest power-of-2) based * on sample rate and milliseconds. */ diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 18fe5dc..c873648 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -75,6 +75,7 @@ PA_MODULE_USAGE( "save_aec=<save AEC data in /tmp> " "autoloaded=<set if this module is being loaded automatically> " "use_volume_sharing=<yes or no> " + "use_master_format=<yes or no> " )); /* NOTE: Make sure the enum and ec_table are maintained in the correct order */ @@ -140,6 +141,7 @@ static const pa_echo_canceller ec_table[] = { #define DEFAULT_ADJUST_TOLERANCE (5*PA_USEC_PER_MSEC) #define DEFAULT_SAVE_AEC false #define DEFAULT_AUTOLOADED false +#define DEFAULT_USE_MASTER_FORMAT false #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) @@ -204,7 +206,6 @@ struct userdata { pa_core *core; pa_module *module; - bool autoloaded; bool dead; bool save_aec; @@ -275,6 +276,7 @@ static const char* const valid_modargs[] = { "save_aec", "autoloaded", "use_volume_sharing", + "use_master_format", NULL }; @@ -1433,12 +1435,6 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { pa_assert_ctl_context(); pa_assert_se(u = o->userdata); - if (u->autoloaded) { - /* We were autoloaded, and don't support moving. Let's unload ourselves. */ - pa_log_debug("Can't move autoloaded streams, unloading"); - pa_module_unload_request(u->module, true); - } - if (dest) { pa_source_set_asyncmsgq(u->source, dest->asyncmsgq); pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); @@ -1470,12 +1466,6 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (u->autoloaded) { - /* We were autoloaded, and don't support moving. Let's unload ourselves. */ - pa_log_debug("Can't move autoloaded streams, unloading"); - pa_module_unload_request(u->module, true); - } - if (dest) { pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); @@ -1532,12 +1522,16 @@ static int canceller_process_msg_cb(pa_msgobject *o, int code, void *userdata, i switch (code) { case ECHO_CANCELLER_MESSAGE_SET_VOLUME: { - pa_cvolume *v = (pa_cvolume *) userdata; - - if (u->use_volume_sharing) - pa_source_set_volume(u->source, v, true, false); - else - pa_source_output_set_volume(u->source_output, v, false, true); + pa_volume_t v = PA_PTR_TO_UINT(userdata); + pa_cvolume vol; + + if (u->use_volume_sharing) { + pa_cvolume_set(&vol, u->source->sample_spec.channels, v); + pa_source_set_volume(u->source, &vol, true, false); + } else { + pa_cvolume_set(&vol, u->source_output->sample_spec.channels, v); + pa_source_output_set_volume(u->source_output, &vol, false, true); + } break; } @@ -1551,22 +1545,20 @@ static int canceller_process_msg_cb(pa_msgobject *o, int code, void *userdata, i } /* Called by the canceller, so source I/O thread context. */ -void pa_echo_canceller_get_capture_volume(pa_echo_canceller *ec, pa_cvolume *v) { +pa_volume_t pa_echo_canceller_get_capture_volume(pa_echo_canceller *ec) { #ifndef ECHO_CANCEL_TEST - *v = ec->msg->userdata->thread_info.current_volume; + return pa_cvolume_avg(&ec->msg->userdata->thread_info.current_volume); #else - pa_cvolume_set(v, 1, PA_VOLUME_NORM); + return PA_VOLUME_NORM; #endif } /* Called by the canceller, so source I/O thread context. */ -void pa_echo_canceller_set_capture_volume(pa_echo_canceller *ec, pa_cvolume *v) { +void pa_echo_canceller_set_capture_volume(pa_echo_canceller *ec, pa_volume_t v) { #ifndef ECHO_CANCEL_TEST - if (!pa_cvolume_equal(&ec->msg->userdata->thread_info.current_volume, v)) { - pa_cvolume *vol = pa_xnewdup(pa_cvolume, v, 1); - - pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(ec->msg), ECHO_CANCELLER_MESSAGE_SET_VOLUME, vol, 0, NULL, - pa_xfree); + if (pa_cvolume_avg(&ec->msg->userdata->thread_info.current_volume) != v) { + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(ec->msg), ECHO_CANCELLER_MESSAGE_SET_VOLUME, PA_UINT_TO_PTR(v), + 0, NULL, NULL); } #endif } @@ -1652,6 +1644,7 @@ int pa__init(pa_module*m) { pa_modargs *ma; pa_source *source_master=NULL; pa_sink *sink_master=NULL; + bool autoloaded; pa_source_output_new_data source_output_data; pa_sink_input_new_data sink_input_data; pa_source_new_data source_data; @@ -1659,6 +1652,7 @@ int pa__init(pa_module*m) { pa_memchunk silence; uint32_t temp; uint32_t nframes = 0; + bool use_master_format; pa_assert(m); @@ -1684,13 +1678,30 @@ int pa__init(pa_module*m) { goto fail; } - source_ss = source_master->sample_spec; - source_ss.rate = DEFAULT_RATE; - source_ss.channels = DEFAULT_CHANNELS; - pa_channel_map_init_auto(&source_map, source_ss.channels, PA_CHANNEL_MAP_DEFAULT); + /* Set to true if we just want to inherit sample spec and channel map from the sink and source master */ + use_master_format = DEFAULT_USE_MASTER_FORMAT; + if (pa_modargs_get_value_boolean(ma, "use_master_format", &use_master_format) < 0) { + pa_log("use_master_format= expects a boolean argument"); + goto fail; + } + source_ss = source_master->sample_spec; sink_ss = sink_master->sample_spec; - sink_map = sink_master->channel_map; + + if (use_master_format) { + source_map = source_master->channel_map; + sink_map = sink_master->channel_map; + } else { + source_ss = source_master->sample_spec; + source_ss.rate = DEFAULT_RATE; + source_ss.channels = DEFAULT_CHANNELS; + pa_channel_map_init_auto(&source_map, source_ss.channels, PA_CHANNEL_MAP_DEFAULT); + + sink_ss = sink_master->sample_spec; + sink_ss.rate = DEFAULT_RATE; + sink_ss.channels = DEFAULT_CHANNELS; + pa_channel_map_init_auto(&sink_map, sink_ss.channels, PA_CHANNEL_MAP_DEFAULT); + } u = pa_xnew0(struct userdata, 1); if (!u) { @@ -1736,8 +1747,8 @@ int pa__init(pa_module*m) { goto fail; } - u->autoloaded = DEFAULT_AUTOLOADED; - if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { + autoloaded = DEFAULT_AUTOLOADED; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &autoloaded) < 0) { pa_log("Failed to parse autoloaded value"); goto fail; } @@ -1782,7 +1793,7 @@ int pa__init(pa_module*m) { pa_source_new_data_set_channel_map(&source_data, &source_map); pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name); pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - if (!u->autoloaded) + if (!autoloaded) pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { @@ -1832,7 +1843,7 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_channel_map(&sink_data, &sink_map); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - if (!u->autoloaded) + if (!autoloaded) pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { @@ -1884,6 +1895,9 @@ int pa__init(pa_module*m) { pa_source_output_new_data_set_sample_spec(&source_output_data, &source_output_ss); pa_source_output_new_data_set_channel_map(&source_output_data, &source_output_map); + if (autoloaded) + source_output_data.flags |= PA_SOURCE_OUTPUT_DONT_MOVE; + pa_source_output_new(&u->source_output, m->core, &source_output_data); pa_source_output_new_data_done(&source_output_data); @@ -1919,6 +1933,9 @@ int pa__init(pa_module*m) { pa_sink_input_new_data_set_channel_map(&sink_input_data, &sink_map); sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE; + if (autoloaded) + sink_input_data.flags |= PA_SINK_INPUT_DONT_MOVE; + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); pa_sink_input_new_data_done(&sink_input_data); @@ -2137,12 +2154,12 @@ int main(int argc, char* argv[]) { goto fail; } - source_ss.format = PA_SAMPLE_S16LE; + source_ss.format = PA_SAMPLE_FLOAT32LE; source_ss.rate = DEFAULT_RATE; source_ss.channels = DEFAULT_CHANNELS; pa_channel_map_init_auto(&source_map, source_ss.channels, PA_CHANNEL_MAP_DEFAULT); - sink_ss.format = PA_SAMPLE_S16LE; + sink_ss.format = PA_SAMPLE_FLOAT32LE; sink_ss.rate = DEFAULT_RATE; sink_ss.channels = DEFAULT_CHANNELS; pa_channel_map_init_auto(&sink_map, sink_ss.channels, PA_CHANNEL_MAP_DEFAULT); diff --git a/src/modules/echo-cancel/null.c b/src/modules/echo-cancel/null.c index 673b14f..c8ecf27 100644 --- a/src/modules/echo-cancel/null.c +++ b/src/modules/echo-cancel/null.c @@ -34,7 +34,7 @@ bool pa_null_ec_init(pa_core *c, pa_echo_canceller *ec, char strss_sink[PA_SAMPLE_SPEC_SNPRINT_MAX]; *nframes = 256; - ec->params.priv.null.out_ss = *out_ss; + ec->params.null.out_ss = *out_ss; *rec_ss = *out_ss; *rec_map = *out_map; @@ -49,7 +49,7 @@ bool pa_null_ec_init(pa_core *c, pa_echo_canceller *ec, void pa_null_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { /* The null implementation simply copies the recorded buffer to the output buffer and ignores the play buffer. */ - memcpy(out, rec, 256 * pa_frame_size(&ec->params.priv.null.out_ss)); + memcpy(out, rec, 256 * pa_frame_size(&ec->params.null.out_ss)); } void pa_null_ec_done(pa_echo_canceller *ec) { diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c index 11e53b3..a3ae646 100644 --- a/src/modules/echo-cancel/speex.c +++ b/src/modules/echo-cancel/speex.c @@ -47,9 +47,9 @@ static const char* const valid_modargs[] = { NULL }; -static void pa_speex_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map, - pa_sample_spec *play_ss, pa_channel_map *play_map, - pa_sample_spec *out_ss, pa_channel_map *out_map) { +static void speex_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map, + pa_sample_spec *play_ss, pa_channel_map *play_map, + pa_sample_spec *out_ss, pa_channel_map *out_map) { out_ss->format = PA_SAMPLE_S16NE; *play_ss = *out_ss; @@ -111,26 +111,26 @@ static bool pa_speex_ec_preprocessor_init(pa_echo_canceller *ec, pa_sample_spec goto fail; } - ec->params.priv.speex.pp_state = speex_preprocess_state_init(nframes, out_ss->rate); + ec->params.speex.pp_state = speex_preprocess_state_init(nframes, out_ss->rate); tmp = agc; - speex_preprocess_ctl(ec->params.priv.speex.pp_state, SPEEX_PREPROCESS_SET_AGC, &tmp); + speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_AGC, &tmp); tmp = denoise; - speex_preprocess_ctl(ec->params.priv.speex.pp_state, SPEEX_PREPROCESS_SET_DENOISE, &tmp); + speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_DENOISE, &tmp); if (echo_suppress) { if (echo_suppress_attenuation) - speex_preprocess_ctl(ec->params.priv.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, + speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, &echo_suppress_attenuation); if (echo_suppress_attenuation_active) { - speex_preprocess_ctl(ec->params.priv.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE, + speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE, &echo_suppress_attenuation_active); } - speex_preprocess_ctl(ec->params.priv.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_STATE, - ec->params.priv.speex.state); + speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_STATE, + ec->params.speex.state); } pa_log_info("Loaded speex preprocessor with params: agc=%s, denoise=%s, echo_suppress=%s", pa_yes_no(agc), @@ -170,18 +170,18 @@ bool pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec, goto fail; } - pa_speex_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map); + speex_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map); rate = out_ss->rate; *nframes = pa_echo_canceller_blocksize_power2(rate, frame_size_ms); pa_log_debug ("Using nframes %d, channels %d, rate %d", *nframes, out_ss->channels, out_ss->rate); - ec->params.priv.speex.state = speex_echo_state_init_mc(*nframes, (rate * filter_size_ms) / 1000, out_ss->channels, out_ss->channels); + ec->params.speex.state = speex_echo_state_init_mc(*nframes, (rate * filter_size_ms) / 1000, out_ss->channels, out_ss->channels); - if (!ec->params.priv.speex.state) + if (!ec->params.speex.state) goto fail; - speex_echo_ctl(ec->params.priv.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate); + speex_echo_ctl(ec->params.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate); if (!pa_speex_ec_preprocessor_init(ec, out_ss, *nframes, ma)) goto fail; @@ -192,34 +192,34 @@ bool pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec, fail: if (ma) pa_modargs_free(ma); - if (ec->params.priv.speex.pp_state) { - speex_preprocess_state_destroy(ec->params.priv.speex.pp_state); - ec->params.priv.speex.pp_state = NULL; + if (ec->params.speex.pp_state) { + speex_preprocess_state_destroy(ec->params.speex.pp_state); + ec->params.speex.pp_state = NULL; } - if (ec->params.priv.speex.state) { - speex_echo_state_destroy(ec->params.priv.speex.state); - ec->params.priv.speex.state = NULL; + if (ec->params.speex.state) { + speex_echo_state_destroy(ec->params.speex.state); + ec->params.speex.state = NULL; } return false; } void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { - speex_echo_cancellation(ec->params.priv.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play, + speex_echo_cancellation(ec->params.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play, (spx_int16_t *) out); /* preprecessor is run after AEC. This is not a mistake! */ - if (ec->params.priv.speex.pp_state) - speex_preprocess_run(ec->params.priv.speex.pp_state, (spx_int16_t *) out); + if (ec->params.speex.pp_state) + speex_preprocess_run(ec->params.speex.pp_state, (spx_int16_t *) out); } void pa_speex_ec_done(pa_echo_canceller *ec) { - if (ec->params.priv.speex.pp_state) { - speex_preprocess_state_destroy(ec->params.priv.speex.pp_state); - ec->params.priv.speex.pp_state = NULL; + if (ec->params.speex.pp_state) { + speex_preprocess_state_destroy(ec->params.speex.pp_state); + ec->params.speex.pp_state = NULL; } - if (ec->params.priv.speex.state) { - speex_echo_state_destroy(ec->params.priv.speex.state); - ec->params.priv.speex.state = NULL; + if (ec->params.speex.state) { + speex_echo_state_destroy(ec->params.speex.state); + ec->params.speex.state = NULL; } } diff --git a/src/modules/echo-cancel/webrtc.cc b/src/modules/echo-cancel/webrtc.cc index 511c7ee..db6873d 100644 --- a/src/modules/echo-cancel/webrtc.cc +++ b/src/modules/echo-cancel/webrtc.cc @@ -2,8 +2,9 @@ This file is part of PulseAudio. Copyright 2011 Collabora Ltd. + 2015 Aldebaran SoftBank Group - Contributor: Arun Raghavan <arun.raghavan@collabora.co.uk> + Contributor: Arun Raghavan <mail@arunraghavan.net> PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -33,8 +34,9 @@ PA_C_DECL_BEGIN #include "echo-cancel.h" PA_C_DECL_END -#include <audio_processing.h> -#include <module_common_types.h> +#include <webrtc/modules/audio_processing/include/audio_processing.h> +#include <webrtc/modules/interface/module_common_types.h> +#include <webrtc/system_wrappers/include/trace.h> #define BLOCK_SIZE_US 10000 @@ -46,6 +48,15 @@ PA_C_DECL_END #define DEFAULT_ROUTING_MODE "speakerphone" #define DEFAULT_COMFORT_NOISE true #define DEFAULT_DRIFT_COMPENSATION false +#define DEFAULT_VAD true +#define DEFAULT_EXTENDED_FILTER false +#define DEFAULT_INTELLIGIBILITY_ENHANCER false +#define DEFAULT_EXPERIMENTAL_AGC false +#define DEFAULT_AGC_START_VOLUME 85 +#define DEFAULT_BEAMFORMING false +#define DEFAULT_TRACE false + +#define WEBRTC_AGC_MAX_VOLUME 255 static const char* const valid_modargs[] = { "high_pass_filter", @@ -56,6 +67,15 @@ static const char* const valid_modargs[] = { "routing_mode", "comfort_noise", "drift_compensation", + "voice_detection", + "extended_filter", + "intelligibility_enhancer", + "experimental_agc", + "agc_start_volume", + "beamforming", + "mic_geometry", /* documented in parse_mic_geometry() */ + "target_direction", /* documented in parse_mic_geometry() */ + "trace", NULL }; @@ -74,15 +94,155 @@ static int routing_mode_from_string(const char *rmode) { return -1; } +class PaWebrtcTraceCallback : public webrtc::TraceCallback { + void Print(webrtc::TraceLevel level, const char *message, int length) + { + if (level & webrtc::kTraceError || level & webrtc::kTraceCritical) + pa_log(message); + else if (level & webrtc::kTraceWarning) + pa_log_warn(message); + else if (level & webrtc::kTraceInfo) + pa_log_info(message); + else + pa_log_debug(message); + } +}; + +static int webrtc_volume_from_pa(pa_volume_t v) +{ + return (v * WEBRTC_AGC_MAX_VOLUME) / PA_VOLUME_NORM; +} + +static pa_volume_t webrtc_volume_to_pa(int v) +{ + return (v * PA_VOLUME_NORM) / WEBRTC_AGC_MAX_VOLUME; +} + +static void webrtc_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map, + pa_sample_spec *play_ss, pa_channel_map *play_map, + pa_sample_spec *out_ss, pa_channel_map *out_map, + bool beamforming) +{ + rec_ss->format = PA_SAMPLE_FLOAT32NE; + play_ss->format = PA_SAMPLE_FLOAT32NE; + + /* AudioProcessing expects one of the following rates */ + if (rec_ss->rate >= 48000) + rec_ss->rate = 48000; + else if (rec_ss->rate >= 32000) + rec_ss->rate = 32000; + else if (rec_ss->rate >= 16000) + rec_ss->rate = 16000; + else + rec_ss->rate = 8000; + + *out_ss = *rec_ss; + *out_map = *rec_map; + + if (beamforming) { + /* The beamformer gives us a single channel */ + out_ss->channels = 1; + pa_channel_map_init_mono(out_map); + } + + /* Playback stream rate needs to be the same as capture */ + play_ss->rate = rec_ss->rate; +} + +static bool parse_point(const char **point, float (&f)[3]) { + int ret, length; + + ret = sscanf(*point, "%g,%g,%g%n", &f[0], &f[1], &f[2], &length); + if (ret != 3) + return false; + + /* Consume the bytes we've read so far */ + *point += length; + + return true; +} + +static bool parse_mic_geometry(const char **mic_geometry, std::vector<webrtc::Point>& geometry) { + /* The microphone geometry is expressed as cartesian point form: + * x1,y1,z1,x2,y2,z2,... + * + * Where x1,y1,z1 is the position of the first microphone with regards to + * the array's "center", x2,y2,z2 the position of the second, and so on. + * + * 'x' is the horizontal coordinate, with positive values being to the + * right from the mic array's perspective. + * + * 'y' is the depth coordinate, with positive values being in front of the + * array. + * + * 'z' is the vertical coordinate, with positive values being above the + * array. + * + * All distances are in meters. + */ + + /* The target direction is expected to be in spherical point form: + * a,e,r + * + * Where 'a is the azimuth of the first mic channel, 'e' its elevation, + * and 'r' the radius. + * + * 0 radians azimuth is to the right of the array, and positive angles + * move in a counter-clockwise direction. + * + * 0 radians elevation is horizontal w.r.t. the array, and positive + * angles go upwards. + * + * radius is distance from the array center in meters. + */ + + int i; + float f[3]; + + for (i = 0; i < geometry.size(); i++) { + if (!parse_point(mic_geometry, f)) { + pa_log("Failed to parse channel %d in mic_geometry", i); + return false; + } + + /* Except for the last point, we should have a trailing comma */ + if (i != geometry.size() - 1) { + if (**mic_geometry != ',') { + pa_log("Failed to parse channel %d in mic_geometry", i); + return false; + } + + (*mic_geometry)++; + } + + pa_log_debug("Got mic #%d position: (%g, %g, %g)", i, f[0], f[1], f[2]); + + geometry[i].c[0] = f[0]; + geometry[i].c[1] = f[1]; + geometry[i].c[2] = f[2]; + } + + if (**mic_geometry != '\0') { + pa_log("Failed to parse mic_geometry value: more parameters than expected"); + return false; + } + + return true; +} + bool pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec, pa_sample_spec *rec_ss, pa_channel_map *rec_map, pa_sample_spec *play_ss, pa_channel_map *play_map, pa_sample_spec *out_ss, pa_channel_map *out_map, uint32_t *nframes, const char *args) { webrtc::AudioProcessing *apm = NULL; - bool hpf, ns, agc, dgc, mobile, cn; - int rm = -1; + webrtc::ProcessingConfig pconfig; + webrtc::Config config; + bool hpf, ns, agc, dgc, mobile, cn, vad, ext_filter, intelligibility, experimental_agc, beamforming; + int rm = -1, i; + uint32_t agc_start_volume; pa_modargs *ma; + bool trace = false; if (!(ma = pa_modargs_new(args, valid_modargs))) { pa_log("Failed to parse submodule arguments."); @@ -153,32 +313,134 @@ bool pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec, } } - apm = webrtc::AudioProcessing::Create(0); + vad = DEFAULT_VAD; + if (pa_modargs_get_value_boolean(ma, "voice_detection", &vad) < 0) { + pa_log("Failed to parse voice_detection value"); + goto fail; + } + + ext_filter = DEFAULT_EXTENDED_FILTER; + if (pa_modargs_get_value_boolean(ma, "extended_filter", &ext_filter) < 0) { + pa_log("Failed to parse extended_filter value"); + goto fail; + } + + intelligibility = DEFAULT_INTELLIGIBILITY_ENHANCER; + if (pa_modargs_get_value_boolean(ma, "intelligibility_enhancer", &intelligibility) < 0) { + pa_log("Failed to parse intelligibility_enhancer value"); + goto fail; + } + + experimental_agc = DEFAULT_EXPERIMENTAL_AGC; + if (pa_modargs_get_value_boolean(ma, "experimental_agc", &experimental_agc) < 0) { + pa_log("Failed to parse experimental_agc value"); + goto fail; + } + + agc_start_volume = DEFAULT_AGC_START_VOLUME; + if (pa_modargs_get_value_u32(ma, "agc_start_volume", &agc_start_volume) < 0) { + pa_log("Failed to parse agc_start_volume value"); + goto fail; + } + if (agc_start_volume > WEBRTC_AGC_MAX_VOLUME) { + pa_log("AGC start volume must not exceed %u", WEBRTC_AGC_MAX_VOLUME); + goto fail; + } + ec->params.webrtc.agc_start_volume = agc_start_volume; + + beamforming = DEFAULT_BEAMFORMING; + if (pa_modargs_get_value_boolean(ma, "beamforming", &beamforming) < 0) { + pa_log("Failed to parse beamforming value"); + goto fail; + } + + if (ext_filter) + config.Set<webrtc::ExtendedFilter>(new webrtc::ExtendedFilter(true)); + if (intelligibility) + pa_log_warn("The intelligibility enhancer is not currently supported"); + if (experimental_agc) + config.Set<webrtc::ExperimentalAgc>(new webrtc::ExperimentalAgc(true, ec->params.webrtc.agc_start_volume)); + + trace = DEFAULT_TRACE; + if (pa_modargs_get_value_boolean(ma, "trace", &trace) < 0) { + pa_log("Failed to parse trace value"); + goto fail; + } + + if (trace) { + webrtc::Trace::CreateTrace(); + webrtc::Trace::set_level_filter(webrtc::kTraceAll); + ec->params.webrtc.trace_callback = new PaWebrtcTraceCallback(); + webrtc::Trace::SetTraceCallback((PaWebrtcTraceCallback *) ec->params.webrtc.trace_callback); + } + + webrtc_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map, beamforming); + + /* We do this after fixate because we need the capture channel count */ + if (beamforming) { + std::vector<webrtc::Point> geometry(rec_ss->channels); + webrtc::SphericalPointf direction(0.0f, 0.0f, 0.0f); + const char *mic_geometry, *target_direction; + + if (!(mic_geometry = pa_modargs_get_value(ma, "mic_geometry", NULL))) { + pa_log("mic_geometry must be set if beamforming is enabled"); + goto fail; + } + + if (!parse_mic_geometry(&mic_geometry, geometry)) { + pa_log("Failed to parse mic_geometry value"); + goto fail; + } + + if ((target_direction = pa_modargs_get_value(ma, "target_direction", NULL))) { + float f[3]; + + if (!parse_point(&target_direction, f)) { + pa_log("Failed to parse target_direction value"); + goto fail; + } + + if (*target_direction != '\0') { + pa_log("Failed to parse target_direction value: more parameters than expected"); + goto fail; + } + +#define IS_ZERO(f) ((f) < 0.000001 && (f) > -0.000001) + + if (!IS_ZERO(f[1]) || !IS_ZERO(f[2])) { + pa_log("The beamformer currently only supports targeting along the azimuth"); + goto fail; + } + + direction.s[0] = f[0]; + direction.s[1] = f[1]; + direction.s[2] = f[2]; + } - out_ss->format = PA_SAMPLE_S16NE; - *play_ss = *out_ss; - /* FIXME: the implementation actually allows a different number of - * source/sink channels. Do we want to support that? */ - *play_map = *out_map; - *rec_ss = *out_ss; - *rec_map = *out_map; + if (!target_direction) + config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry)); + else + config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry, direction)); + } - apm->set_sample_rate_hz(out_ss->rate); + apm = webrtc::AudioProcessing::Create(config); - apm->set_num_channels(out_ss->channels, out_ss->channels); - apm->set_num_reverse_channels(play_ss->channels); + pconfig = { + webrtc::StreamConfig(rec_ss->rate, rec_ss->channels, false), /* input stream */ + webrtc::StreamConfig(out_ss->rate, out_ss->channels, false), /* output stream */ + webrtc::StreamConfig(play_ss->rate, play_ss->channels, false), /* reverse input stream */ + webrtc::StreamConfig(play_ss->rate, play_ss->channels, false), /* reverse output stream */ + }; + if (apm->Initialize(pconfig) != webrtc::AudioProcessing::kNoError) { + pa_log("Error initialising audio processing module"); + goto fail; + } if (hpf) apm->high_pass_filter()->Enable(true); if (!mobile) { - if (ec->params.drift_compensation) { - apm->echo_cancellation()->set_device_sample_rate_hz(out_ss->rate); - apm->echo_cancellation()->enable_drift_compensation(true); - } else { - apm->echo_cancellation()->enable_drift_compensation(false); - } - + apm->echo_cancellation()->enable_drift_compensation(ec->params.drift_compensation); apm->echo_cancellation()->Enable(true); } else { apm->echo_control_mobile()->set_routing_mode(static_cast<webrtc::EchoControlMobile::RoutingMode>(rm)); @@ -195,28 +457,38 @@ bool pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec, if (mobile && rm <= webrtc::EchoControlMobile::kEarpiece) { /* Maybe this should be a knob, but we've got a lot of knobs already */ apm->gain_control()->set_mode(webrtc::GainControl::kFixedDigital); - ec->params.priv.webrtc.agc = false; + ec->params.webrtc.agc = false; } else if (dgc) { apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital); - ec->params.priv.webrtc.agc = false; + ec->params.webrtc.agc = false; } else { apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveAnalog); - if (apm->gain_control()->set_analog_level_limits(0, PA_VOLUME_NORM-1) != apm->kNoError) { + if (apm->gain_control()->set_analog_level_limits(0, WEBRTC_AGC_MAX_VOLUME) != + webrtc::AudioProcessing::kNoError) { pa_log("Failed to initialise AGC"); goto fail; } - ec->params.priv.webrtc.agc = true; + ec->params.webrtc.agc = true; } apm->gain_control()->Enable(true); } - apm->voice_detection()->Enable(true); + if (vad) + apm->voice_detection()->Enable(true); + + ec->params.webrtc.apm = apm; + ec->params.webrtc.rec_ss = *rec_ss; + ec->params.webrtc.play_ss = *play_ss; + ec->params.webrtc.out_ss = *out_ss; + ec->params.webrtc.blocksize = (uint64_t) out_ss->rate * BLOCK_SIZE_US / PA_USEC_PER_SEC; + *nframes = ec->params.webrtc.blocksize; + ec->params.webrtc.first = true; - ec->params.priv.webrtc.apm = apm; - ec->params.priv.webrtc.sample_spec = *out_ss; - ec->params.priv.webrtc.blocksize = (uint64_t)pa_bytes_per_second(out_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC; - *nframes = ec->params.priv.webrtc.blocksize / pa_frame_size(out_ss); + for (i = 0; i < rec_ss->channels; i++) + ec->params.webrtc.rec_buffer[i] = pa_xnew(float, *nframes); + for (i = 0; i < play_ss->channels; i++) + ec->params.webrtc.play_buffer[i] = pa_xnew(float, *nframes); pa_modargs_free(ma); return true; @@ -224,58 +496,77 @@ bool pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec, fail: if (ma) pa_modargs_free(ma); - if (apm) - webrtc::AudioProcessing::Destroy(apm); + if (ec->params.webrtc.trace_callback) { + webrtc::Trace::ReturnTrace(); + delete ((PaWebrtcTraceCallback *) ec->params.webrtc.trace_callback); + } if (apm) + delete apm; return false; } void pa_webrtc_ec_play(pa_echo_canceller *ec, const uint8_t *play) { - webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm; - webrtc::AudioFrame play_frame; - const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec; + webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.webrtc.apm; + const pa_sample_spec *ss = &ec->params.webrtc.play_ss; + int n = ec->params.webrtc.blocksize; + float **buf = ec->params.webrtc.play_buffer; + webrtc::StreamConfig config(ss->rate, ss->channels, false); + + pa_deinterleave(play, (void **) buf, ss->channels, pa_sample_size(ss), n); - play_frame._audioChannel = ss->channels; - play_frame._frequencyInHz = ss->rate; - play_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss); - memcpy(play_frame._payloadData, play, ec->params.priv.webrtc.blocksize); + pa_assert_se(apm->ProcessReverseStream(buf, config, config, buf) == webrtc::AudioProcessing::kNoError); - apm->AnalyzeReverseStream(&play_frame); + /* FIXME: If ProcessReverseStream() makes any changes to the audio, such as + * applying intelligibility enhancement, those changes don't have any + * effect. This function is called at the source side, but the processing + * would have to be done in the sink to be able to feed the processed audio + * to speakers. */ } void pa_webrtc_ec_record(pa_echo_canceller *ec, const uint8_t *rec, uint8_t *out) { - webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm; - webrtc::AudioFrame out_frame; - const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec; - pa_cvolume v; - - out_frame._audioChannel = ss->channels; - out_frame._frequencyInHz = ss->rate; - out_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss); - memcpy(out_frame._payloadData, rec, ec->params.priv.webrtc.blocksize); - - if (ec->params.priv.webrtc.agc) { - pa_cvolume_init(&v); - pa_echo_canceller_get_capture_volume(ec, &v); - apm->gain_control()->set_stream_analog_level(pa_cvolume_avg(&v)); + webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.webrtc.apm; + const pa_sample_spec *rec_ss = &ec->params.webrtc.rec_ss; + const pa_sample_spec *out_ss = &ec->params.webrtc.out_ss; + float **buf = ec->params.webrtc.rec_buffer; + int n = ec->params.webrtc.blocksize; + int old_volume, new_volume; + webrtc::StreamConfig rec_config(rec_ss->rate, rec_ss->channels, false); + webrtc::StreamConfig out_config(out_ss->rate, out_ss->channels, false); + + pa_deinterleave(rec, (void **) buf, rec_ss->channels, pa_sample_size(rec_ss), n); + + if (ec->params.webrtc.agc) { + pa_volume_t v = pa_echo_canceller_get_capture_volume(ec); + old_volume = webrtc_volume_from_pa(v); + apm->gain_control()->set_stream_analog_level(old_volume); } apm->set_stream_delay_ms(0); - apm->ProcessStream(&out_frame); + pa_assert_se(apm->ProcessStream(buf, rec_config, out_config, buf) == webrtc::AudioProcessing::kNoError); + + if (ec->params.webrtc.agc) { + if (PA_UNLIKELY(ec->params.webrtc.first)) { + /* We start at a sane default volume (taken from the Chromium + * condition on the experimental AGC in audio_processing.h). This is + * needed to make sure that there's enough energy in the capture + * signal for the AGC to work */ + ec->params.webrtc.first = false; + new_volume = ec->params.webrtc.agc_start_volume; + } else { + new_volume = apm->gain_control()->stream_analog_level(); + } - if (ec->params.priv.webrtc.agc) { - pa_cvolume_set(&v, ss->channels, apm->gain_control()->stream_analog_level()); - pa_echo_canceller_set_capture_volume(ec, &v); + if (old_volume != new_volume) + pa_echo_canceller_set_capture_volume(ec, webrtc_volume_to_pa(new_volume)); } - memcpy(out, out_frame._payloadData, ec->params.priv.webrtc.blocksize); + pa_interleave((const void **) buf, out_ss->channels, out, pa_sample_size(out_ss), n); } void pa_webrtc_ec_set_drift(pa_echo_canceller *ec, float drift) { - webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm; - const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec; + webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.webrtc.apm; - apm->echo_cancellation()->set_stream_drift_samples(drift * ec->params.priv.webrtc.blocksize / pa_frame_size(ss)); + apm->echo_cancellation()->set_stream_drift_samples(drift * ec->params.webrtc.blocksize); } void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { @@ -284,8 +575,20 @@ void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t * } void pa_webrtc_ec_done(pa_echo_canceller *ec) { - if (ec->params.priv.webrtc.apm) { - webrtc::AudioProcessing::Destroy((webrtc::AudioProcessing*)ec->params.priv.webrtc.apm); - ec->params.priv.webrtc.apm = NULL; + int i; + + if (ec->params.webrtc.trace_callback) { + webrtc::Trace::ReturnTrace(); + delete ((PaWebrtcTraceCallback *) ec->params.webrtc.trace_callback); } + + if (ec->params.webrtc.apm) { + delete (webrtc::AudioProcessing*)ec->params.webrtc.apm; + ec->params.webrtc.apm = NULL; + } + + for (i = 0; i < ec->params.webrtc.rec_ss.channels; i++) + pa_xfree(ec->params.webrtc.rec_buffer[i]); + for (i = 0; i < ec->params.webrtc.play_ss.channels; i++) + pa_xfree(ec->params.webrtc.play_buffer[i]); } diff --git a/src/modules/macosx/module-coreaudio-device.c b/src/modules/macosx/module-coreaudio-device.c index 0c92d42..6c9e55d 100644 --- a/src/modules/macosx/module-coreaudio-device.c +++ b/src/modules/macosx/module-coreaudio-device.c @@ -376,23 +376,25 @@ static int ca_sink_set_state(pa_sink *s, pa_sink_state_t state) { } /* Caveat: The caller is responsible to get rid of the CFString(Ref). */ -static bool CFString_to_cstr_n(CFStringRef cfstr, char *buf, long n) { - bool ret; - - pa_assert (buf); +static char * CFString_to_cstr(CFStringRef cfstr) { + char *ret = NULL; ret = false; if (cfstr != NULL) { const char *tmp = CFStringGetCStringPtr(cfstr, kCFStringEncodingUTF8); + CFIndex n = CFStringGetLength(cfstr) + 1 /* for the terminating NULL */; + + ret = pa_xmalloc(n); if (tmp == NULL) { - if (CFStringGetCString(cfstr, buf, n, kCFStringEncodingUTF8)) - ret = true; + if (!CFStringGetCString(cfstr, ret, n, kCFStringEncodingUTF8)) { + pa_xfree(ret); + ret = NULL; + } } else { - strncpy(buf, tmp, n); - buf[n - 1] = 0; - ret = true; + strncpy(ret, tmp, n - 1); + ret[n - 1] = '\0'; } } @@ -408,11 +410,16 @@ static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx coreaudio_sink *ca_sink; pa_sink *sink; unsigned int i; - char tmp[255]; + char *tmp; pa_strbuf *strbuf; AudioObjectPropertyAddress property_address; CFStringRef tmp_cfstr = NULL; + if (buf->mNumberChannels > PA_CHANNELS_MAX) { + pa_log("Skipping device with more channels than we support (%u)", (unsigned int) buf->mNumberChannels); + return -1; + } + ca_sink = pa_xnew0(coreaudio_sink, 1); ca_sink->map.channels = buf->mNumberChannels; ca_sink->ss.channels = buf->mNumberChannels; @@ -425,23 +432,24 @@ static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx property_address.mSelector = kAudioObjectPropertyElementName; property_address.mScope = kAudioDevicePropertyScopeOutput; property_address.mElement = channel_idx + i + 1; - size = sizeof(tmp); + size = sizeof(tmp_cfstr); err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &tmp_cfstr); if (err == 0) { - err = !(CFString_to_cstr_n(tmp_cfstr, tmp, sizeof(tmp))); + tmp = CFString_to_cstr(tmp_cfstr); - if (tmp_cfstr) { + if (tmp_cfstr) CFRelease(tmp_cfstr); - } } - if (err || !strlen(tmp)) - snprintf(tmp, sizeof(tmp), "Channel %d", (int) property_address.mElement); - if (i > 0) pa_strbuf_puts(strbuf, ", "); - pa_strbuf_puts(strbuf, tmp); + if (err || !tmp || !strlen(tmp)) + pa_strbuf_printf(strbuf, "Channel %d", (int) property_address.mElement); + else + pa_strbuf_puts(strbuf, tmp); + + pa_xfree(tmp); } ca_sink->name = pa_strbuf_to_string_free(strbuf); @@ -535,11 +543,16 @@ static int ca_device_create_source(pa_module *m, AudioBuffer *buf, int channel_i coreaudio_source *ca_source; pa_source *source; unsigned int i; - char tmp[255]; + char *tmp; pa_strbuf *strbuf; AudioObjectPropertyAddress property_address; CFStringRef tmp_cfstr = NULL; + if (buf->mNumberChannels > PA_CHANNELS_MAX) { + pa_log("Skipping device with more channels than we support (%u)", (unsigned int) buf->mNumberChannels); + return -1; + } + ca_source = pa_xnew0(coreaudio_source, 1); ca_source->map.channels = buf->mNumberChannels; ca_source->ss.channels = buf->mNumberChannels; @@ -552,23 +565,24 @@ static int ca_device_create_source(pa_module *m, AudioBuffer *buf, int channel_i property_address.mSelector = kAudioObjectPropertyElementName; property_address.mScope = kAudioDevicePropertyScopeInput; property_address.mElement = channel_idx + i + 1; - size = sizeof(tmp); + size = sizeof(tmp_cfstr); err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &tmp_cfstr); if (err == 0) { - err = !(CFString_to_cstr_n(tmp_cfstr, tmp, sizeof(tmp))); + tmp = CFString_to_cstr(tmp_cfstr); - if (tmp_cfstr) { + if (tmp_cfstr) CFRelease(tmp_cfstr); - } } - if (err || !strlen(tmp)) - snprintf(tmp, sizeof(tmp), "Channel %d", (int) property_address.mElement); - if (i > 0) pa_strbuf_puts(strbuf, ", "); - pa_strbuf_puts(strbuf, tmp); + if (err || !tmp || !strlen(tmp)) + pa_strbuf_printf(strbuf, "Channel %d", (int) property_address.mElement); + else + pa_strbuf_puts(strbuf, tmp); + + pa_xfree(tmp); } ca_source->name = pa_strbuf_to_string_free(strbuf); diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index f906843..7545aa5 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -64,7 +64,7 @@ struct userdata { pa_database *database; }; -#define ENTRY_VERSION 3 +#define ENTRY_VERSION 4 struct port_info { char *name; @@ -75,6 +75,8 @@ struct port_info { struct entry { char *profile; pa_hashmap *ports; /* Port name -> struct port_info */ + char *preferred_input_port; + char *preferred_output_port; }; static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { @@ -131,6 +133,8 @@ static struct port_info *port_info_new(pa_device_port *port) { static void entry_free(struct entry* e) { pa_assert(e); + pa_xfree(e->preferred_output_port); + pa_xfree(e->preferred_input_port); pa_xfree(e->profile); pa_hashmap_free(e->ports); @@ -176,6 +180,12 @@ static bool entrys_equal(struct entry *a, struct entry *b) { return false; } + if (!pa_safe_streq(a->preferred_input_port, b->preferred_input_port)) + return false; + + if (!pa_safe_streq(a->preferred_output_port, b->preferred_output_port)) + return false; + return true; } @@ -201,6 +211,9 @@ static bool entry_write(struct userdata *u, const char *name, const struct entry pa_tagstruct_puts(t, p_info->profile); } + pa_tagstruct_puts(t, e->preferred_input_port); + pa_tagstruct_puts(t, e->preferred_output_port); + key.data = (char *) name; key.size = strlen(name); @@ -314,6 +327,18 @@ static struct entry* entry_read(struct userdata *u, const char *name) { } } + if (version >= 4) { + const char *preferred_input_port; + const char *preferred_output_port; + + if (pa_tagstruct_gets(t, &preferred_input_port) < 0 + || pa_tagstruct_gets(t, &preferred_output_port) < 0) + goto fail; + + e->preferred_input_port = pa_xstrdup(preferred_input_port); + e->preferred_output_port = pa_xstrdup(preferred_output_port); + } + if (!pa_tagstruct_eof(t)) goto fail; @@ -547,6 +572,46 @@ static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new if (!p->preferred_profile && p_info->profile) pa_device_port_set_preferred_profile(p, p_info->profile); } + + if (e->preferred_input_port) { + p = pa_hashmap_get(new_data->ports, e->preferred_input_port); + if (p) + pa_card_new_data_set_preferred_port(new_data, PA_DIRECTION_INPUT, p); + } + + if (e->preferred_output_port) { + p = pa_hashmap_get(new_data->ports, e->preferred_output_port); + if (p) + pa_card_new_data_set_preferred_port(new_data, PA_DIRECTION_OUTPUT, p); + } + + entry_free(e); + + return PA_HOOK_OK; +} + +static pa_hook_result_t card_preferred_port_changed_callback(pa_core *core, pa_card_preferred_port_changed_hook_data *data, + struct userdata *u) { + struct entry *e; + pa_card *card; + + card = data->card; + + e = entry_read(u, card->name); + if (!e) + e = entry_from_card(card); + + if (data->direction == PA_DIRECTION_INPUT) { + pa_xfree(e->preferred_input_port); + e->preferred_input_port = pa_xstrdup(card->preferred_input_port ? card->preferred_input_port->name : NULL); + } else { + pa_xfree(e->preferred_output_port); + e->preferred_output_port = pa_xstrdup(card->preferred_output_port ? card->preferred_output_port->name : NULL); + } + + if (entry_write(u, card->name, e)) + trigger_save(u); + entry_free(e); return PA_HOOK_OK; @@ -570,6 +635,7 @@ int pa__init(pa_module*m) { pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) card_new_hook_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) card_put_hook_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_preferred_port_changed_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_changed_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_added_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], @@ -608,8 +674,10 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->save_time_event) + if (u->save_time_event) { u->core->mainloop->time_free(u->save_time_event); + pa_database_sync(u->database); + } if (u->database) pa_database_close(u->database); diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index a9a2de0..d6c6b76 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -46,7 +46,10 @@ PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE("just-one=<boolean>"); + +#ifdef __linux__ PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-detect!"); +#endif static const char* const valid_modargs[] = { "just-one", diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c index f125bdd..2a0a67f 100644 --- a/src/modules/module-device-manager.c +++ b/src/modules/module-device-manager.c @@ -292,8 +292,10 @@ static struct entry* entry_read(struct userdata *u, const char *name) { pa_zero(data); - if (!pa_database_get(u->database, &key, &data)) - goto fail; + if (!pa_database_get(u->database, &key, &data)) { + pa_log_debug("Database contains no data for key: %s", name); + return NULL; + } t = pa_tagstruct_new_fixed(data.data, data.size); e = entry_new(); @@ -647,8 +649,10 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha } static void route_sink_input(struct userdata *u, pa_sink_input *si) { + const char *auto_filtered_prop; const char *role; uint32_t role_index, device_index; + bool auto_filtered = false; pa_sink *sink; pa_assert(u); @@ -661,6 +665,10 @@ static void route_sink_input(struct userdata *u, pa_sink_input *si) { if (!si->sink) return; + auto_filtered_prop = pa_proplist_gets(si->proplist, "module-device-manager.auto_filtered"); + if (auto_filtered_prop) + auto_filtered = (pa_parse_boolean(auto_filtered_prop) == 1); + /* It might happen that a stream and a sink are set up at the same time, in which case we want to make sure we don't interfere with that */ @@ -682,6 +690,13 @@ static void route_sink_input(struct userdata *u, pa_sink_input *si) { if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index))) return; + if (auto_filtered) { + /* For streams for which a filter has been loaded by another module, we + * do not try to execute moves within the same filter hierarchy */ + if (pa_sink_get_master(si->sink) == pa_sink_get_master(sink)) + return; + } + if (si->sink != sink) pa_sink_input_move_to(si, sink, false); } @@ -705,8 +720,10 @@ static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_si } static void route_source_output(struct userdata *u, pa_source_output *so) { + const char *auto_filtered_prop; const char *role; uint32_t role_index, device_index; + bool auto_filtered = false; pa_source *source; pa_assert(u); @@ -722,6 +739,10 @@ static void route_source_output(struct userdata *u, pa_source_output *so) { if (!so->source) return; + auto_filtered_prop = pa_proplist_gets(so->proplist, "module-device-manager.auto_filtered"); + if (auto_filtered_prop) + auto_filtered = (pa_parse_boolean(auto_filtered_prop) == 1); + /* It might happen that a stream and a source are set up at the same time, in which case we want to make sure we don't interfere with that */ @@ -743,6 +764,13 @@ static void route_source_output(struct userdata *u, pa_source_output *so) { if (!(source = pa_idxset_get_by_index(u->core->sources, device_index))) return; + if (auto_filtered) { + /* For streams for which a filter has been loaded by another module, we + * do not try to execute moves within the same filter hierarchy */ + if (pa_source_get_master(so->source) == pa_source_get_master(source)) + return; + } + if (so->source != source) pa_source_output_move_to(so, source, false); } diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index c3f83ce..364d68b 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -38,6 +38,7 @@ #include "module-filter-apply-symdef.h" #define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving" +#define PA_PROP_MDM_AUTO_FILTERED "module-device-manager.auto_filtered" PA_MODULE_AUTHOR("Colin Guthrie"); PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed"); @@ -65,6 +66,10 @@ struct filter { struct userdata { pa_core *core; pa_hashmap *filters; + /* Keep track of streams we're managing PA_PROP_MDM_AUTO_FILTERED on, we're + * only maintaining membership, so key and value are just the + * pa_sink_input/pa_source_output. */ + pa_hashmap *mdm_ignored_inputs, *mdm_ignored_outputs; bool autoclean; pa_time_event *housekeeping_time_event; }; @@ -124,7 +129,7 @@ static const char* should_filter(pa_object *o, bool is_sink_input) { else pl = PA_SOURCE_OUTPUT(o)->proplist; - /* If the stream doesn't what any filter, then let it be. */ + /* If the stream doesn't want any filter, then let it be. */ if ((apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) { const char* suppress = pa_proplist_gets(pl, PA_PROP_FILTER_SUPPRESS); @@ -270,14 +275,20 @@ static void trigger_housekeeping(struct userdata *u) { u->housekeeping_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + HOUSEKEEPING_INTERVAL, housekeeping_time_callback, u); } -static int do_move(pa_object *obj, pa_object *parent, bool restore, bool is_input) { - if (is_input) - return pa_sink_input_move_to(PA_SINK_INPUT(obj), PA_SINK(parent), restore); - else - return pa_source_output_move_to(PA_SOURCE_OUTPUT(obj), PA_SOURCE(parent), restore); +static int do_move(struct userdata *u, pa_object *obj, pa_object *parent, bool is_input) { + /* Keep track of objects that we've marked for module-device-manager to ignore */ + pa_hashmap_put(is_input ? u->mdm_ignored_inputs : u->mdm_ignored_outputs, obj, obj); + + if (is_input) { + pa_sink_input_set_property(PA_SINK_INPUT(obj), PA_PROP_MDM_AUTO_FILTERED, "1"); + return pa_sink_input_move_to(PA_SINK_INPUT(obj), PA_SINK(parent), false); + } else { + pa_source_output_set_property(PA_SOURCE_OUTPUT(obj), PA_PROP_MDM_AUTO_FILTERED, "1"); + return pa_source_output_move_to(PA_SOURCE_OUTPUT(obj), PA_SOURCE(parent), false); + } } -static void move_object_for_filter(pa_object *o, struct filter* filter, bool restore, bool is_sink_input) { +static void move_object_for_filter(struct userdata *u, pa_object *o, struct filter* filter, bool restore, bool is_sink_input) { pa_object *parent; pa_proplist *pl; const char *name; @@ -301,7 +312,7 @@ static void move_object_for_filter(pa_object *o, struct filter* filter, bool res pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_MOVING, "1"); - if (do_move(o, parent, false, is_sink_input) < 0) + if (do_move(u, o, parent, is_sink_input) < 0) pa_log_info("Failed to move %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output", pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name); else @@ -315,7 +326,7 @@ static void move_objects_for_filter(struct userdata *u, pa_object *o, struct fil bool is_sink_input) { if (!should_group_filter(filter)) - move_object_for_filter(o, filter, restore, is_sink_input); + move_object_for_filter(u, o, filter, restore, is_sink_input); else { pa_source_output *so; pa_sink_input *si; @@ -328,7 +339,7 @@ static void move_objects_for_filter(struct userdata *u, pa_object *o, struct fil g = get_group(PA_OBJECT(so), false); if (pa_streq(g, group)) - move_object_for_filter(PA_OBJECT(so), filter, restore, false); + move_object_for_filter(u, PA_OBJECT(so), filter, restore, false); pa_xfree(g); } @@ -337,7 +348,7 @@ static void move_objects_for_filter(struct userdata *u, pa_object *o, struct fil g = get_group(PA_OBJECT(si), true); if (pa_streq(g, group)) - move_object_for_filter(PA_OBJECT(si), filter, restore, true); + move_object_for_filter(u, PA_OBJECT(si), filter, restore, true); pa_xfree(g); } @@ -405,6 +416,8 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i pa_sink *sink = NULL; pa_source *source = NULL; pa_module *module = NULL; + char *module_name = NULL; + struct filter *fltr = NULL, *filter = NULL; if (is_sink_input) { sink = PA_SINK_INPUT(o)->sink; @@ -420,31 +433,27 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i /* If there is no sink/source yet, we can't do much */ if ((is_sink_input && !sink) || (!is_sink_input && !source)) - return PA_HOOK_OK; + goto done; /* If the stream doesn't what any filter, then let it be. */ if ((want = should_filter(o, is_sink_input))) { - char *module_name; - struct filter *fltr, *filter; - /* We need to ensure the SI is playing on a sink of this type * attached to the sink it's "officially" playing on */ if (!module) - return PA_HOOK_OK; + goto done; module_name = pa_sprintf_malloc("module-%s", want); if (pa_streq(module->name, module_name)) { pa_log_debug("Stream appears to be playing on an appropriate sink already. Ignoring."); - pa_xfree(module_name); - return PA_HOOK_OK; + goto done; } fltr = filter_new(want, sink, source); if (should_group_filter(fltr) && !find_paired_master(u, fltr, o, is_sink_input)) { pa_log_debug("Want group filtering but don't have enough streams."); - return PA_HOOK_OK; + goto done; } if (!(filter = pa_hashmap_get(u->filters, fltr))) { @@ -467,14 +476,10 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i pa_xfree(args); } - pa_xfree(fltr); - if (!filter) { pa_log("Unable to load %s", module_name); - pa_xfree(module_name); - return PA_HOOK_OK; + goto done; } - pa_xfree(module_name); /* We can move the stream now as we know the destination. If this * isn't true, we will do it later when the sink appears. */ @@ -484,7 +489,6 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i } } else { void *state; - struct filter *filter = NULL; /* We do not want to filter... but are we already filtered? * This can happen if an input's proplist changes */ @@ -500,6 +504,10 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i if (done_something) trigger_housekeeping(u); +done: + pa_xfree(module_name); + pa_xfree(fltr); + return PA_HOOK_OK; } @@ -517,6 +525,9 @@ static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input * if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING)) return PA_HOOK_OK; + /* If we're managing m-d-m.auto_filtered on this, remove and re-add if we're continuing to manage it */ + pa_hashmap_remove(u->mdm_ignored_inputs, i); + return process(u, PA_OBJECT(i), true); } @@ -536,6 +547,8 @@ static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, st if (pa_hashmap_size(u->filters) > 0) trigger_housekeeping(u); + pa_hashmap_remove(u->mdm_ignored_inputs, i); + return PA_HOOK_OK; } @@ -589,6 +602,9 @@ static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_ou if (pa_proplist_gets(o->proplist, PA_PROP_FILTER_APPLY_MOVING)) return PA_HOOK_OK; + /* If we're managing m-d-m.auto_filtered on this, remove and re-add if we're continuing to manage it */ + pa_hashmap_remove(u->mdm_ignored_outputs, o); + return process(u, PA_OBJECT(o), false); } @@ -608,6 +624,8 @@ static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output if (pa_hashmap_size(u->filters) > 0) trigger_housekeeping(u); + pa_hashmap_remove(u->mdm_ignored_outputs, o); + return PA_HOOK_OK; } @@ -647,6 +665,16 @@ static pa_hook_result_t source_unlink_cb(pa_core *core, pa_source *source, struc return PA_HOOK_OK; } +static void unset_mdm_ignore_input(pa_sink_input *i) +{ + pa_sink_input_set_property(i, PA_PROP_MDM_AUTO_FILTERED, NULL); +} + +static void unset_mdm_ignore_output(pa_source_output *o) +{ + pa_source_output_set_property(o, PA_PROP_MDM_AUTO_FILTERED, NULL); +} + int pa__init(pa_module *m) { pa_modargs *ma = NULL; struct userdata *u; @@ -669,6 +697,8 @@ int pa__init(pa_module *m) { } u->filters = pa_hashmap_new(filter_hash, filter_compare); + u->mdm_ignored_inputs = pa_hashmap_new_full(NULL, NULL, (pa_free_cb_t) unset_mdm_ignore_input, NULL); + u->mdm_ignored_outputs = pa_hashmap_new_full(NULL, NULL, (pa_free_cb_t) unset_mdm_ignore_output, NULL); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); @@ -716,5 +746,11 @@ void pa__done(pa_module *m) { pa_hashmap_free(u->filters); } + if (u->mdm_ignored_inputs) + pa_hashmap_free(u->mdm_ignored_inputs); + + if (u->mdm_ignored_outputs) + pa_hashmap_free(u->mdm_ignored_outputs); + pa_xfree(u); } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 37bf7b1..f4d0761 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -398,9 +398,9 @@ static bool source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) { /* Called from main thread */ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { - pa_proplist *p; - const char *n; struct userdata *u; + char *input_description; + const char *n; if (!dest) return; @@ -409,14 +409,13 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { pa_assert_ctl_context(); pa_assert_se(u = o->userdata); - p = pa_proplist_new(); - pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback of %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION))); + input_description = pa_sprintf_malloc("Loopback of %s", + pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_sink_input_set_property(u->sink_input, PA_PROP_MEDIA_NAME, input_description); + pa_xfree(input_description); if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME))) - pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n); - - pa_sink_input_update_proplist(u->sink_input, PA_UPDATE_REPLACE, p); - pa_proplist_free(p); + pa_sink_input_set_property(u->sink_input, PA_PROP_DEVICE_ICON_NAME, n); if (pa_source_get_state(dest) == PA_SOURCE_SUSPENDED) pa_sink_input_cork(u->sink_input, true); @@ -671,7 +670,7 @@ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t s /* Called from main thread */ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { struct userdata *u; - pa_proplist *p; + char *output_description; const char *n; if (!dest) @@ -681,14 +680,13 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { pa_assert_ctl_context(); pa_assert_se(u = i->userdata); - p = pa_proplist_new(); - pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback to %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION))); + output_description = pa_sprintf_malloc("Loopback to %s", + pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_source_output_set_property(u->source_output, PA_PROP_MEDIA_NAME, output_description); + pa_xfree(output_description); if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME))) - pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n); - - pa_source_output_update_proplist(u->source_output, PA_UPDATE_REPLACE, p); - pa_proplist_free(p); + pa_source_output_set_property(u->source_output, PA_PROP_MEDIA_ICON_NAME, n); if (pa_sink_get_state(dest) == PA_SINK_SUSPENDED) pa_source_output_cork(u->source_output, true); diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 901dabd..60ac1c4 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -309,7 +309,7 @@ static pa_hook_result_t source_output_move_fail_hook_callback(pa_core *c, pa_sou return PA_HOOK_OK; } else { - pa_log_info("Successfully moved output input %u \"%s\" to %s.", i->index, + pa_log_info("Successfully moved source output %u \"%s\" to %s.", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); return PA_HOOK_STOP; } diff --git a/src/modules/module-role-cork.c b/src/modules/module-role-cork.c index 7c0f59f..d71cc38 100644 --- a/src/modules/module-role-cork.c +++ b/src/modules/module-role-cork.c @@ -21,15 +21,9 @@ #include <config.h> #endif -#include <pulse/xmalloc.h> - #include <pulsecore/macro.h> -#include <pulsecore/hashmap.h> -#include <pulsecore/hook-list.h> #include <pulsecore/core.h> -#include <pulsecore/core-util.h> -#include <pulsecore/sink-input.h> -#include <pulsecore/modargs.h> +#include <stream-interaction.h> #include "module-role-cork-symdef.h" @@ -49,260 +43,16 @@ static const char* const valid_modargs[] = { NULL }; -struct userdata { - pa_core *core; - pa_hashmap *cork_state; - pa_idxset *trigger_roles; - pa_idxset *cork_roles; - bool global:1; - pa_hook_slot - *sink_input_put_slot, - *sink_input_unlink_slot, - *sink_input_move_start_slot, - *sink_input_move_finish_slot; -}; - -static bool shall_cork(struct userdata *u, pa_sink *s, pa_sink_input *ignore) { - pa_sink_input *j; - uint32_t idx, role_idx; - const char *trigger_role; - - pa_assert(u); - pa_sink_assert_ref(s); - - for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - const char *role; - - if (j == ignore) - continue; - - if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) - continue; - - PA_IDXSET_FOREACH(trigger_role, u->trigger_roles, role_idx) { - if (pa_streq(role, trigger_role)) { - pa_log_debug("Found a '%s' stream that will trigger the auto-cork.", trigger_role); - return true; - } - } - } - - return false; -} - -static inline void apply_cork_to_sink(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool cork) { - pa_sink_input *j; - uint32_t idx, role_idx; - const char *cork_role; - bool trigger = false; - - pa_assert(u); - pa_sink_assert_ref(s); - - for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - bool corked, corked_here; - const char *role; - - if (j == ignore) - continue; - - if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) - continue; - - PA_IDXSET_FOREACH(cork_role, u->cork_roles, role_idx) { - if ((trigger = pa_streq(role, cork_role))) - break; - } - if (!trigger) - continue; - - corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED); - corked_here = !!pa_hashmap_get(u->cork_state, j); - - if (cork && !corked && !j->muted) { - pa_log_debug("Found a '%s' stream that should be corked/muted.", cork_role); - if (!corked_here) - pa_hashmap_put(u->cork_state, j, PA_INT_TO_PTR(1)); - pa_sink_input_set_mute(j, true, false); - pa_sink_input_send_event(j, PA_STREAM_EVENT_REQUEST_CORK, NULL); - } else if (!cork) { - pa_hashmap_remove(u->cork_state, j); - - if (corked_here && (corked || j->muted)) { - pa_log_debug("Found a '%s' stream that should be uncorked/unmuted.", cork_role); - if (j->muted) - pa_sink_input_set_mute(j, false, false); - if (corked) - pa_sink_input_send_event(j, PA_STREAM_EVENT_REQUEST_UNCORK, NULL); - } - } - } -} - -static void apply_cork(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool cork) { - pa_assert(u); - - if (u->global) { - uint32_t idx; - PA_IDXSET_FOREACH(s, u->core->sinks, idx) - apply_cork_to_sink(u, s, ignore, cork); - } else - apply_cork_to_sink(u, s, ignore, cork); -} - -static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool create) { - bool cork = false; - const char *role; - - pa_assert(u); - pa_sink_input_assert_ref(i); - - if (!create) - pa_hashmap_remove(u->cork_state, i); - - if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE))) - return PA_HOOK_OK; - - if (!i->sink) - return PA_HOOK_OK; - - cork = shall_cork(u, i->sink, create ? NULL : i); - apply_cork(u, i->sink, create ? NULL : i, cork); - - return PA_HOOK_OK; -} - -static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_core_assert_ref(core); - pa_sink_input_assert_ref(i); - - return process(u, i, true); -} - -static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_sink_input_assert_ref(i); - - return process(u, i, false); -} - -static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_core_assert_ref(core); - pa_sink_input_assert_ref(i); - - return process(u, i, false); -} - -static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_core_assert_ref(core); - pa_sink_input_assert_ref(i); - - return process(u, i, true); -} - int pa__init(pa_module *m) { - pa_modargs *ma = NULL; - struct userdata *u; - const char *roles; - bool global = false; pa_assert(m); - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments"); - goto fail; - } - - m->userdata = u = pa_xnew(struct userdata, 1); - - u->core = m->core; - u->cork_state = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - - u->trigger_roles = pa_idxset_new(NULL, NULL); - roles = pa_modargs_get_value(ma, "trigger_roles", NULL); - if (roles) { - const char *split_state = NULL; - char *n = NULL; - while ((n = pa_split(roles, ",", &split_state))) { - if (n[0] != '\0') - pa_idxset_put(u->trigger_roles, n, NULL); - else - pa_xfree(n); - } - } - if (pa_idxset_isempty(u->trigger_roles)) { - pa_log_debug("Using role 'phone' as trigger role."); - pa_idxset_put(u->trigger_roles, pa_xstrdup("phone"), NULL); - } - - u->cork_roles = pa_idxset_new(NULL, NULL); - roles = pa_modargs_get_value(ma, "cork_roles", NULL); - if (roles) { - const char *split_state = NULL; - char *n = NULL; - while ((n = pa_split(roles, ",", &split_state))) { - if (n[0] != '\0') - pa_idxset_put(u->cork_roles, n, NULL); - else - pa_xfree(n); - } - } - if (pa_idxset_isempty(u->cork_roles)) { - pa_log_debug("Using roles 'music' and 'video' as cork roles."); - pa_idxset_put(u->cork_roles, pa_xstrdup("music"), NULL); - pa_idxset_put(u->cork_roles, pa_xstrdup("video"), NULL); - } - - if (pa_modargs_get_value_boolean(ma, "global", &global) < 0) { - pa_log("Invalid boolean parameter: global"); - goto fail; - } - u->global = global; - - u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u); - u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u); - u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u); - u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); - - pa_modargs_free(ma); - - return 0; - -fail: - pa__done(m); - - if (ma) - pa_modargs_free(ma); - - return -1; - + return pa_stream_interaction_init(m, valid_modargs); } void pa__done(pa_module *m) { - struct userdata* u; pa_assert(m); - if (!(u = m->userdata)) - return; - - if (u->trigger_roles) - pa_idxset_free(u->trigger_roles, pa_xfree); - - if (u->cork_roles) - pa_idxset_free(u->cork_roles, pa_xfree); - - if (u->sink_input_put_slot) - pa_hook_slot_free(u->sink_input_put_slot); - if (u->sink_input_unlink_slot) - pa_hook_slot_free(u->sink_input_unlink_slot); - if (u->sink_input_move_start_slot) - pa_hook_slot_free(u->sink_input_move_start_slot); - if (u->sink_input_move_finish_slot) - pa_hook_slot_free(u->sink_input_move_finish_slot); - - if (u->cork_state) - pa_hashmap_free(u->cork_state); - - pa_xfree(u); - + pa_stream_interaction_done(m); } diff --git a/src/modules/module-role-ducking.c b/src/modules/module-role-ducking.c index 4f9be10..add2d36 100644 --- a/src/modules/module-role-ducking.c +++ b/src/modules/module-role-ducking.c @@ -21,16 +21,9 @@ #include <config.h> #endif -#include <pulse/volume.h> -#include <pulse/xmalloc.h> - #include <pulsecore/macro.h> -#include <pulsecore/hashmap.h> -#include <pulsecore/hook-list.h> #include <pulsecore/core.h> -#include <pulsecore/core-util.h> -#include <pulsecore/sink-input.h> -#include <pulsecore/modargs.h> +#include <stream-interaction.h> #include "module-role-ducking-symdef.h" @@ -39,10 +32,10 @@ PA_MODULE_DESCRIPTION("Apply a ducking effect based on streams roles"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( - "trigger_roles=<Comma separated list of roles which will trigger a ducking> " - "ducking_roles=<Comma separated list of roles which will be ducked> " + "trigger_roles=<Comma(and slash) separated list of roles which will trigger a ducking. Slash can divide the roles into groups>" + "ducking_roles=<Comma(and slash) separated list of roles which will be ducked. Slash can divide the roles into groups>" "global=<Should we operate globally or only inside the same device?>" - "volume=<Volume for the attenuated streams. Default: -20dB" + "volume=<Volume for the attenuated streams. Default: -20dB. If trigger_roles and ducking_roles are separated by slash, use slash for dividing volume group>" ); static const char* const valid_modargs[] = { @@ -53,264 +46,16 @@ static const char* const valid_modargs[] = { NULL }; -struct userdata { - pa_core *core; - const char *name; - pa_idxset *trigger_roles; - pa_idxset *ducking_roles; - pa_idxset *ducked_inputs; - bool global; - pa_volume_t volume; - pa_hook_slot - *sink_input_put_slot, - *sink_input_unlink_slot, - *sink_input_move_start_slot, - *sink_input_move_finish_slot; -}; - -static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore) { - pa_sink_input *j; - uint32_t idx, role_idx; - const char *trigger_role; - - pa_assert(u); - pa_sink_assert_ref(s); - - PA_IDXSET_FOREACH(j, s->inputs, idx) { - const char *role; - - if (j == ignore) - continue; - - if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) - continue; - - PA_IDXSET_FOREACH(trigger_role, u->trigger_roles, role_idx) { - if (pa_streq(role, trigger_role)) { - pa_log_debug("Found a '%s' stream that will trigger the ducking.", trigger_role); - return true; - } - } - } - - return false; -} - -static void apply_ducking_to_sink(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool duck) { - pa_sink_input *j; - uint32_t idx, role_idx; - const char *ducking_role; - bool trigger = false; - - pa_assert(u); - pa_sink_assert_ref(s); - - PA_IDXSET_FOREACH(j, s->inputs, idx) { - const char *role; - pa_sink_input *i; - - if (j == ignore) - continue; - - if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) - continue; - - PA_IDXSET_FOREACH(ducking_role, u->ducking_roles, role_idx) { - if ((trigger = pa_streq(role, ducking_role))) - break; - } - if (!trigger) - continue; - - i = pa_idxset_get_by_data(u->ducked_inputs, j, NULL); - if (duck && !i) { - pa_cvolume vol; - vol.channels = 1; - vol.values[0] = u->volume; - - pa_log_debug("Found a '%s' stream that should be ducked.", ducking_role); - pa_sink_input_add_volume_factor(j, u->name, &vol); - pa_idxset_put(u->ducked_inputs, j, NULL); - } else if (!duck && i) { /* This stream should not longer be ducked */ - pa_log_debug("Found a '%s' stream that should be unducked", ducking_role); - pa_idxset_remove_by_data(u->ducked_inputs, j, NULL); - pa_sink_input_remove_volume_factor(j, u->name); - } - } -} - -static void apply_ducking(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool duck) { - pa_assert(u); - - if (u->global) { - uint32_t idx; - PA_IDXSET_FOREACH(s, u->core->sinks, idx) - apply_ducking_to_sink(u, s, ignore, duck); - } else - apply_ducking_to_sink(u, s, ignore, duck); -} - -static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool duck) { - bool should_duck = false; - const char *role; - - pa_assert(u); - pa_sink_input_assert_ref(i); - - if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE))) - return PA_HOOK_OK; - - if (!i->sink) - return PA_HOOK_OK; - - should_duck = sink_has_trigger_streams(u, i->sink, duck ? NULL : i); - apply_ducking(u, i->sink, duck ? NULL : i, should_duck); - - return PA_HOOK_OK; -} - -static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_core_assert_ref(core); - pa_sink_input_assert_ref(i); - - return process(u, i, true); -} - -static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_sink_input_assert_ref(i); - - pa_idxset_remove_by_data(u->ducked_inputs, i, NULL); - return process(u, i, false); -} - -static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_core_assert_ref(core); - pa_sink_input_assert_ref(i); - - return process(u, i, false); -} - -static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - pa_core_assert_ref(core); - pa_sink_input_assert_ref(i); - - return process(u, i, true); -} - int pa__init(pa_module *m) { - pa_modargs *ma = NULL; - struct userdata *u; - const char *roles; pa_assert(m); - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments"); - goto fail; - } - - m->userdata = u = pa_xnew0(struct userdata, 1); - - u->core = m->core; - u->name = m->name; - - u->ducked_inputs = pa_idxset_new(NULL, NULL); - - u->trigger_roles = pa_idxset_new(NULL, NULL); - roles = pa_modargs_get_value(ma, "trigger_roles", NULL); - if (roles) { - const char *split_state = NULL; - char *n = NULL; - while ((n = pa_split(roles, ",", &split_state))) { - if (n[0] != '\0') - pa_idxset_put(u->trigger_roles, n, NULL); - else - pa_xfree(n); - } - } - if (pa_idxset_isempty(u->trigger_roles)) { - pa_log_debug("Using role 'phone' as trigger role."); - pa_idxset_put(u->trigger_roles, pa_xstrdup("phone"), NULL); - } - - u->ducking_roles = pa_idxset_new(NULL, NULL); - roles = pa_modargs_get_value(ma, "ducking_roles", NULL); - if (roles) { - const char *split_state = NULL; - char *n = NULL; - while ((n = pa_split(roles, ",", &split_state))) { - if (n[0] != '\0') - pa_idxset_put(u->ducking_roles, n, NULL); - else - pa_xfree(n); - } - } - if (pa_idxset_isempty(u->ducking_roles)) { - pa_log_debug("Using roles 'music' and 'video' as ducking roles."); - pa_idxset_put(u->ducking_roles, pa_xstrdup("music"), NULL); - pa_idxset_put(u->ducking_roles, pa_xstrdup("video"), NULL); - } - - u->global = false; - if (pa_modargs_get_value_boolean(ma, "global", &u->global) < 0) { - pa_log("Failed to parse a boolean parameter: global"); - goto fail; - } - - u->volume = pa_sw_volume_from_dB(-20); - if (pa_modargs_get_value_volume(ma, "volume", &u->volume) < 0) { - pa_log("Failed to parse a volume parameter: volume"); - goto fail; - } - - u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u); - u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u); - u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u); - u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); - - pa_modargs_free(ma); - - return 0; - -fail: - pa__done(m); - - if (ma) - pa_modargs_free(ma); - - return -1; + return pa_stream_interaction_init(m, valid_modargs); } void pa__done(pa_module *m) { - struct userdata* u; - pa_sink_input *i; pa_assert(m); - if (!(u = m->userdata)) - return; - - if (u->trigger_roles) - pa_idxset_free(u->trigger_roles, pa_xfree); - - if (u->ducking_roles) - pa_idxset_free(u->ducking_roles, pa_xfree); - - if (u->ducked_inputs) { - while ((i = pa_idxset_steal_first(u->ducked_inputs, NULL))) - pa_sink_input_remove_volume_factor(i, u->name); - - pa_idxset_free(u->ducked_inputs, NULL); - } - - if (u->sink_input_put_slot) - pa_hook_slot_free(u->sink_input_put_slot); - if (u->sink_input_unlink_slot) - pa_hook_slot_free(u->sink_input_unlink_slot); - if (u->sink_input_move_start_slot) - pa_hook_slot_free(u->sink_input_move_start_slot); - if (u->sink_input_move_finish_slot) - pa_hook_slot_free(u->sink_input_move_finish_slot); - - pa_xfree(u); + pa_stream_interaction_done(m); } diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index c79918a..2fa0bff 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -412,10 +412,12 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_smoother_resume(u->smoother, pa_rtclock_now(), true); if (!u->source || u->source_suspended) { + bool mute; if (unsuspend(u) < 0) return -1; u->sink->get_volume(u->sink); - u->sink->get_mute(u->sink); + if (u->sink->get_mute(u->sink, &mute) >= 0) + pa_sink_set_mute(u->sink, mute, false); } u->sink_suspended = false; } @@ -1033,8 +1035,12 @@ int pa__init(pa_module *m) { if (sink_new_data.muted_is_set) u->sink->set_mute(u->sink); - else - u->sink->get_mute(u->sink); + else { + bool mute; + + if (u->sink->get_mute(u->sink, &mute) >= 0) + pa_sink_set_mute(u->sink, mute, false); + } pa_sink_put(u->sink); } diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index ac51ff3..d1c2597 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -645,7 +645,7 @@ static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userd e->channel_map = map; e->volume_valid = !!map.channels; - device_updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); + device_updated = (e->device_valid != !!device[0]) || !pa_safe_streq(e->device, device); pa_xfree(e->device); e->device = pa_xstrdup(device); e->device_valid = !!device[0]; @@ -757,7 +757,7 @@ static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBus pa_assert_se(e = entry_read(de->userdata, de->entry_name)); - updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); + updated = (e->device_valid != !!device[0]) || !pa_safe_streq(e->device, device); if (updated) { pa_xfree(e->device); @@ -2341,7 +2341,7 @@ static void clean_up_db(struct userdata *u) { PA_LLIST_FOREACH_SAFE(item, next, to_be_converted) { pa_log_debug("Upgrading a legacy entry to the current format: %s", item->entry_name); - pa_assert_se(entry_write(u, item->entry_name, item->entry, true) >= 0); + pa_assert_se(entry_write(u, item->entry_name, item->entry, true)); trigger_save(u); PA_LLIST_REMOVE(struct clean_up_item, to_be_converted, item); diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c index 5dd9786..b9a0f3b 100644 --- a/src/modules/module-switch-on-port-available.c +++ b/src/modules/module-switch-on-port-available.c @@ -29,45 +29,97 @@ #include "module-switch-on-port-available-symdef.h" -static bool profile_good_for_output(pa_card_profile *profile) { +struct card_info { + struct userdata *userdata; + pa_card *card; + + /* We need to cache the active profile, because we want to compare the old + * and new profiles in the PROFILE_CHANGED hook. Without this we'd only + * have access to the new profile. */ + pa_card_profile *active_profile; +}; + +struct userdata { + pa_hashmap *card_infos; /* pa_card -> struct card_info */ +}; + +static void card_info_new(struct userdata *u, pa_card *card) { + struct card_info *info; + + info = pa_xnew0(struct card_info, 1); + info->userdata = u; + info->card = card; + info->active_profile = card->active_profile; + + pa_hashmap_put(u->card_infos, card, info); +} + +static void card_info_free(struct card_info *info) { + pa_hashmap_remove(info->userdata->card_infos, info->card); + pa_xfree(info); +} + +static bool profile_good_for_output(pa_card_profile *profile, pa_device_port *port) { + pa_card *card; pa_sink *sink; uint32_t idx; pa_assert(profile); - if (!pa_safe_streq(profile->card->active_profile->input_name, profile->input_name)) + card = profile->card; + + if (!pa_safe_streq(card->active_profile->input_name, profile->input_name)) return false; - if (profile->card->active_profile->n_sources != profile->n_sources) + if (card->active_profile->n_sources != profile->n_sources) return false; - if (profile->card->active_profile->max_source_channels != profile->max_source_channels) + if (card->active_profile->max_source_channels != profile->max_source_channels) return false; - /* Try not to switch to HDMI sinks from analog when HDMI is becoming available */ - PA_IDXSET_FOREACH(sink, profile->card->sinks, idx) { + if (port == card->preferred_output_port) + return true; + + PA_IDXSET_FOREACH(sink, card->sinks, idx) { if (!sink->active_port) continue; - if (sink->active_port->available != PA_AVAILABLE_NO) + if ((sink->active_port->available != PA_AVAILABLE_NO) && (sink->active_port->priority >= port->priority)) return false; } return true; } -static bool profile_good_for_input(pa_card_profile *profile) { +static bool profile_good_for_input(pa_card_profile *profile, pa_device_port *port) { + pa_card *card; + pa_source *source; + uint32_t idx; + pa_assert(profile); - if (!pa_safe_streq(profile->card->active_profile->output_name, profile->output_name)) + card = profile->card; + + if (!pa_safe_streq(card->active_profile->output_name, profile->output_name)) return false; - if (profile->card->active_profile->n_sinks != profile->n_sinks) + if (card->active_profile->n_sinks != profile->n_sinks) return false; - if (profile->card->active_profile->max_sink_channels != profile->max_sink_channels) + if (card->active_profile->max_sink_channels != profile->max_sink_channels) return false; + if (port == card->preferred_input_port) + return true; + + PA_IDXSET_FOREACH(source, card->sources, idx) { + if (!source->active_port) + continue; + + if ((source->active_port->available != PA_AVAILABLE_NO) && (source->active_port->priority >= port->priority)) + return false; + } + return true; } @@ -88,12 +140,12 @@ static int try_to_switch_profile(pa_device_port *port) { switch (port->direction) { case PA_DIRECTION_OUTPUT: name = profile->output_name; - good = profile_good_for_output(profile); + good = profile_good_for_output(profile, port); break; case PA_DIRECTION_INPUT: name = profile->input_name; - good = profile_good_for_input(profile); + good = profile_good_for_input(profile, port); break; } @@ -182,7 +234,7 @@ static bool switch_to_port(pa_device_port *port) { pa_log_debug("Trying to switch to port %s", port->name); if (!pp.is_preferred_profile_active) { if (try_to_switch_profile(port) < 0) { - if (pp.is_possible_profile_active) + if (!pp.is_possible_profile_active) return false; } else @@ -307,9 +359,142 @@ static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data return PA_HOOK_OK; } +static pa_hook_result_t card_put_hook_callback(pa_core *core, pa_card *card, struct userdata *u) { + card_info_new(u, card); + + return PA_HOOK_OK; +} + +static pa_hook_result_t card_unlink_hook_callback(pa_core *core, pa_card *card, struct userdata *u) { + card_info_free(pa_hashmap_get(u->card_infos, card)); + + return PA_HOOK_OK; +} + +static void update_preferred_input_port(pa_card *card, pa_card_profile *old_profile, pa_card_profile *new_profile) { + pa_source *source; + + /* If the profile change didn't affect input, it doesn't indicate change in + * the user's input port preference. */ + if (pa_safe_streq(old_profile->input_name, new_profile->input_name)) + return; + + /* If there are more than one source, we don't know which of those the user + * prefers. If there are no sources, then the user doesn't seem to care + * about input at all. */ + if (pa_idxset_size(card->sources) != 1) { + pa_card_set_preferred_port(card, PA_DIRECTION_INPUT, NULL); + return; + } + + /* If the profile change modified the set of sinks, then it's unclear + * whether the user wanted to activate some specific input port, or was the + * input change only a side effect of activating some output. If the new + * profile contains no sinks, though, then we know the user only cares + * about input. */ + if (pa_idxset_size(card->sinks) > 0 && !pa_safe_streq(old_profile->output_name, new_profile->output_name)) { + pa_card_set_preferred_port(card, PA_DIRECTION_INPUT, NULL); + return; + } + + source = pa_idxset_first(card->sources, NULL); + + /* We know the user wanted to activate this source. The user might not have + * wanted to activate the port that was selected by default, but if that's + * the case, the user will change the port manually, and we'll update the + * port preference at that time. If no port change occurs, we can assume + * that the user likes the port that is now active. */ + pa_card_set_preferred_port(card, PA_DIRECTION_INPUT, source->active_port); +} + +static void update_preferred_output_port(pa_card *card, pa_card_profile *old_profile, pa_card_profile *new_profile) { + pa_sink *sink; + + /* If the profile change didn't affect output, it doesn't indicate change in + * the user's output port preference. */ + if (pa_safe_streq(old_profile->output_name, new_profile->output_name)) + return; + + /* If there are more than one sink, we don't know which of those the user + * prefers. If there are no sinks, then the user doesn't seem to care about + * output at all. */ + if (pa_idxset_size(card->sinks) != 1) { + pa_card_set_preferred_port(card, PA_DIRECTION_OUTPUT, NULL); + return; + } + + /* If the profile change modified the set of sources, then it's unclear + * whether the user wanted to activate some specific output port, or was + * the output change only a side effect of activating some input. If the + * new profile contains no sources, though, then we know the user only + * cares about output. */ + if (pa_idxset_size(card->sources) > 0 && !pa_safe_streq(old_profile->input_name, new_profile->input_name)) { + pa_card_set_preferred_port(card, PA_DIRECTION_OUTPUT, NULL); + return; + } + + sink = pa_idxset_first(card->sinks, NULL); + + /* We know the user wanted to activate this sink. The user might not have + * wanted to activate the port that was selected by default, but if that's + * the case, the user will change the port manually, and we'll update the + * port preference at that time. If no port change occurs, we can assume + * that the user likes the port that is now active. */ + pa_card_set_preferred_port(card, PA_DIRECTION_OUTPUT, sink->active_port); +} + +static pa_hook_result_t card_profile_changed_callback(pa_core *core, pa_card *card, struct userdata *u) { + struct card_info *info; + pa_card_profile *old_profile; + pa_card_profile *new_profile; + + info = pa_hashmap_get(u->card_infos, card); + old_profile = info->active_profile; + new_profile = card->active_profile; + info->active_profile = new_profile; + + /* This profile change wasn't initiated by the user, so it doesn't signal + * a change in the user's port preferences. */ + if (!card->save_profile) + return PA_HOOK_OK; + + update_preferred_input_port(card, old_profile, new_profile); + update_preferred_output_port(card, old_profile, new_profile); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_port_changed_callback(pa_core *core, pa_source *source, void *userdata) { + if (!source->save_port) + return PA_HOOK_OK; + + pa_card_set_preferred_port(source->card, PA_DIRECTION_INPUT, source->active_port); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_port_changed_callback(pa_core *core, pa_sink *sink, void *userdata) { + if (!sink->save_port) + return PA_HOOK_OK; + + pa_card_set_preferred_port(sink->card, PA_DIRECTION_OUTPUT, sink->active_port); + + return PA_HOOK_OK; +} + int pa__init(pa_module*m) { + struct userdata *u; + pa_card *card; + uint32_t idx; + pa_assert(m); + u = m->userdata = pa_xnew0(struct userdata, 1); + u->card_infos = pa_hashmap_new(NULL, NULL); + + PA_IDXSET_FOREACH(card, m->core->cards, idx) + card_info_new(u, card); + /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */ pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, NULL); @@ -317,8 +502,35 @@ int pa__init(pa_module*m) { PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, NULL); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, NULL); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PUT], + PA_HOOK_NORMAL, (pa_hook_cb_t) card_put_hook_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], + PA_HOOK_NORMAL, (pa_hook_cb_t) card_unlink_hook_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], + PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_changed_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], + PA_HOOK_NORMAL, (pa_hook_cb_t) source_port_changed_callback, NULL); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], + PA_HOOK_NORMAL, (pa_hook_cb_t) sink_port_changed_callback, NULL); handle_all_unavailable(m->core); return 0; } + +void pa__done(pa_module *module) { + struct userdata *u; + struct card_info *info; + + pa_assert(module); + + if (!(u = module->userdata)) + return; + + while ((info = pa_hashmap_last(u->card_infos))) + card_info_free(info); + + pa_hashmap_free(u->card_infos); + + pa_xfree(u); +} diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 53c4402..5c8b84a 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -1778,7 +1778,7 @@ static void pstream_die_callback(pa_pstream *p, void *userdata) { } /* Called from main context */ -static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data, void *userdata) { +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { struct userdata *u = userdata; pa_assert(p); diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index dc42f7c..5977500 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -106,6 +106,7 @@ struct session { pa_usec_t intended_latency; pa_usec_t sink_latency; + unsigned int base_rate; pa_usec_t last_rate_update; pa_usec_t last_latency; double estimated_rate; @@ -284,7 +285,6 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { pa_usec_t wi, ri, render_delay, sink_delay = 0, latency; - uint32_t base_rate = s->sink_input->sink->sample_spec.rate; uint32_t current_rate = s->sink_input->sample_spec.rate; uint32_t new_rate; double estimated_rate, alpha = 0.02; @@ -351,12 +351,12 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { new_rate = (uint32_t) ((double) (RATE_UPDATE_INTERVAL + latency/4 - s->intended_latency/4) / (double) RATE_UPDATE_INTERVAL * s->avg_estimated_rate); s->last_latency = latency; - if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) (base_rate*1.25)) { - pa_log_warn("Sample rates too different, not adjusting (%u vs. %u).", base_rate, new_rate); - new_rate = base_rate; + if (new_rate < (uint32_t) (s->base_rate*0.8) || new_rate > (uint32_t) (s->base_rate*1.25)) { + pa_log_warn("Sample rates too different, not adjusting (%u vs. %u).", s->base_rate, new_rate); + new_rate = s->base_rate; } else { - if (base_rate < new_rate + 20 && new_rate < base_rate + 20) - new_rate = base_rate; + if (s->base_rate < new_rate + 20 && new_rate < s->base_rate + 20) + new_rate = s->base_rate; /* Do the adjustment in small steps; 2‰ can be considered inaudible */ if (new_rate < (uint32_t) (current_rate*0.998) || new_rate > (uint32_t) (current_rate*1.002)) { pa_log_info("New rate of %u Hz not within 2‰ of %u Hz, forcing smaller adjustment", new_rate, current_rate); @@ -521,8 +521,6 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in s->intended_latency = u->latency; s->last_rate_update = pa_timeval_load(&now); s->last_latency = u->latency; - s->estimated_rate = (double) sink->sample_spec.rate; - s->avg_estimated_rate = (double) sink->sample_spec.rate; pa_atomic_store(&s->timestamp, (int) now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) @@ -554,6 +552,10 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in goto fail; } + s->base_rate = (double) s->sink_input->sample_spec.rate; + s->estimated_rate = (double) s->sink_input->sample_spec.rate; + s->avg_estimated_rate = (double) s->sink_input->sample_spec.rate; + s->sink_input->userdata = s; s->sink_input->parent.process_msg = sink_input_process_msg; diff --git a/src/modules/stream-interaction.c b/src/modules/stream-interaction.c new file mode 100644 index 0000000..7a476c3 --- /dev/null +++ b/src/modules/stream-interaction.c @@ -0,0 +1,550 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + 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 <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> + +#include <pulsecore/macro.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/modargs.h> + +#include "stream-interaction.h" + +struct group { + char *name; + pa_idxset *trigger_roles; + pa_idxset *interaction_roles; + pa_hashmap *interaction_state; + pa_volume_t volume; +}; + +struct userdata { + pa_core *core; + uint32_t n_groups; + struct group **groups; + bool global:1; + bool duck:1; + pa_hook_slot + *sink_input_put_slot, + *sink_input_unlink_slot, + *sink_input_move_start_slot, + *sink_input_move_finish_slot, + *sink_input_state_changed_slot, + *sink_input_mute_changed_slot, + *sink_input_proplist_changed_slot; +}; + +static const char *get_trigger_role(struct userdata *u, pa_sink_input *i, struct group *g) { + const char *role, *trigger_role; + uint32_t role_idx; + + if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE))) + role = "no_role"; + + if (g == NULL) { + /* get it from all groups */ + uint32_t j; + for (j = 0; j < u->n_groups; j++) { + PA_IDXSET_FOREACH(trigger_role, u->groups[j]->trigger_roles, role_idx) { + if (pa_streq(role, trigger_role)) + return trigger_role; + } + } + } else { + PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx) { + if (pa_streq(role, trigger_role)) + return trigger_role; + } + } + + return NULL; +} + +static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) { + pa_sink_input *j; + uint32_t idx; + const char *trigger_role; + + pa_assert(u); + pa_sink_assert_ref(s); + + for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { + + if (j == ignore) + continue; + + trigger_role = get_trigger_role(u, j, g); + if (trigger_role && !j->muted && pa_sink_input_get_state(j) != PA_SINK_INPUT_CORKED) + return trigger_role; + } + + return NULL; +} + +static const char *find_global_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) { + const char *trigger_role = NULL; + + pa_assert(u); + + if (u->global) { + uint32_t idx; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) + if ((trigger_role = find_trigger_stream(u, s, ignore, g))) + break; + } else + trigger_role = find_trigger_stream(u, s, ignore, g); + + return trigger_role; +} + +static void cork_or_duck(struct userdata *u, pa_sink_input *i, const char *interaction_role, const char *trigger_role, bool interaction_applied, struct group *g) { + + if (u->duck && !interaction_applied) { + pa_cvolume vol; + vol.channels = 1; + vol.values[0] = g->volume; + + pa_log_debug("Found a '%s' stream of '%s' that ducks a '%s' stream.", trigger_role, g->name, interaction_role); + pa_sink_input_add_volume_factor(i, g->name, &vol); + + } else if (!u->duck) { + pa_log_debug("Found a '%s' stream that corks/mutes a '%s' stream.", trigger_role, interaction_role); + pa_sink_input_set_mute(i, true, false); + pa_sink_input_send_event(i, PA_STREAM_EVENT_REQUEST_CORK, NULL); + } +} + +static void uncork_or_unduck(struct userdata *u, pa_sink_input *i, const char *interaction_role, bool corked, struct group *g) { + + if (u->duck) { + pa_log_debug("In '%s', found a '%s' stream that should be unducked", g->name, interaction_role); + pa_sink_input_remove_volume_factor(i, g->name); + } + else if (corked || i->muted) { + pa_log_debug("Found a '%s' stream that should be uncorked/unmuted.", interaction_role); + if (i->muted) + pa_sink_input_set_mute(i, false, false); + if (corked) + pa_sink_input_send_event(i, PA_STREAM_EVENT_REQUEST_UNCORK, NULL); + } +} + +static inline void apply_interaction_to_sink(struct userdata *u, pa_sink *s, const char *new_trigger, pa_sink_input *ignore, bool new_stream, struct group *g) { + pa_sink_input *j; + uint32_t idx, role_idx; + const char *interaction_role; + bool trigger = false; + + pa_assert(u); + pa_sink_assert_ref(s); + + for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { + bool corked, interaction_applied; + const char *role; + + if (j == ignore) + continue; + + if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) + role = "no_role"; + + PA_IDXSET_FOREACH(interaction_role, g->interaction_roles, role_idx) { + if ((trigger = pa_streq(role, interaction_role))) + break; + if ((trigger = (pa_streq(interaction_role, "any_role") && !get_trigger_role(u, j, g)))) + break; + } + if (!trigger) + continue; + + /* Some applications start their streams corked, so the stream is uncorked by */ + /* the application only after sink_input_put() was called. If a new stream turns */ + /* up, act as if it was not corked. In the case of module-role-cork this will */ + /* only mute the stream because corking is reverted later by the application */ + corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED); + if (new_stream && corked) + corked = false; + interaction_applied = !!pa_hashmap_get(g->interaction_state, j); + + if (new_trigger && ((!corked && !j->muted) || u->duck)) { + if (!interaction_applied) + pa_hashmap_put(g->interaction_state, j, PA_INT_TO_PTR(1)); + + cork_or_duck(u, j, role, new_trigger, interaction_applied, g); + + } else if (!new_trigger && interaction_applied) { + pa_hashmap_remove(g->interaction_state, j); + + uncork_or_unduck(u, j, role, corked, g); + } + } +} + +static void apply_interaction(struct userdata *u, pa_sink *s, const char *trigger_role, pa_sink_input *ignore, bool new_stream, struct group *g) { + pa_assert(u); + + if (u->global) { + uint32_t idx; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) + apply_interaction_to_sink(u, s, trigger_role, ignore, new_stream, g); + } else + apply_interaction_to_sink(u, s, trigger_role, ignore, new_stream, g); +} + +static void remove_interactions(struct userdata *u, struct group *g) { + uint32_t idx, idx_input; + pa_sink *s; + pa_sink_input *j; + bool corked; + const char *role; + + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + + for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx_input)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx_input))) { + + if(!!pa_hashmap_get(g->interaction_state, j)) { + corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED); + if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) + role = "no_role"; + uncork_or_unduck(u, j, role, corked, g); + } + } + } +} + +static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool create, bool new_stream) { + const char *trigger_role; + uint32_t j; + + pa_assert(u); + pa_sink_input_assert_ref(i); + + if (!create) + for (j = 0; j < u->n_groups; j++) + pa_hashmap_remove(u->groups[j]->interaction_state, i); + + if (!i->sink) + return PA_HOOK_OK; + + for (j = 0; j < u->n_groups; j++) { + trigger_role = find_global_trigger_stream(u, i->sink, create ? NULL : i, u->groups[j]); + apply_interaction(u, i->sink, trigger_role, create ? NULL : i, new_stream, u->groups[j]); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + return process(u, i, true, true); +} + +static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_sink_input_assert_ref(i); + + return process(u, i, false, false); +} + +static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + return process(u, i, false, false); +} + +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + return process(u, i, true, false); +} + +static pa_hook_result_t sink_input_state_changed_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role(u, i, NULL)) + return process(u, i, true, false); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_mute_changed_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role(u, i, NULL)) + return process(u, i, true, false); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_proplist_changed_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i))) + return process(u, i, true, false); + + return PA_HOOK_OK; +} + +int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) { + pa_modargs *ma = NULL; + struct userdata *u; + const char *roles; + bool global = false; + uint32_t i = 0; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, v_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + + u->core = m->core; + + u->duck = false; + if (pa_streq(m->name, "module-role-ducking")) + u->duck = true; + + u->n_groups = 1; + + if (u->duck) { + const char *volumes; + uint32_t group_count_tr = 0; + uint32_t group_count_du = 0; + uint32_t group_count_vol = 0; + + roles = pa_modargs_get_value(ma, "trigger_roles", NULL); + if (roles) { + const char *split_state = NULL; + char *n = NULL; + while ((n = pa_split(roles, "/", &split_state))) { + group_count_tr++; + pa_xfree(n); + } + } + roles = pa_modargs_get_value(ma, "ducking_roles", NULL); + if (roles) { + const char *split_state = NULL; + char *n = NULL; + while ((n = pa_split(roles, "/", &split_state))) { + group_count_du++; + pa_xfree(n); + } + } + volumes = pa_modargs_get_value(ma, "volume", NULL); + if (volumes) { + const char *split_state = NULL; + char *n = NULL; + while ((n = pa_split(volumes, "/", &split_state))) { + group_count_vol++; + pa_xfree(n); + } + } + + if ((group_count_tr > 1 || group_count_du > 1 || group_count_vol > 1) && + ((group_count_tr != group_count_du) || (group_count_tr != group_count_vol))) { + pa_log("Invalid number of groups"); + goto fail; + } + + if (group_count_tr > 0) + u->n_groups = group_count_tr; + } + + u->groups = pa_xnew0(struct group*, u->n_groups); + for (i = 0; i < u->n_groups; i++) { + u->groups[i] = pa_xnew0(struct group, 1); + u->groups[i]->trigger_roles = pa_idxset_new(NULL, NULL); + u->groups[i]->interaction_roles = pa_idxset_new(NULL, NULL); + u->groups[i]->interaction_state = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (u->duck) + u->groups[i]->name = pa_sprintf_malloc("ducking_group_%u", i); + } + + roles = pa_modargs_get_value(ma, "trigger_roles", NULL); + if (roles) { + const char *group_split_state = NULL; + char *roles_in_group = NULL; + i = 0; + while ((roles_in_group = pa_split(roles, "/", &group_split_state))) { + if (roles_in_group[0] != '\0') { + const char *split_state = NULL; + char *n = NULL; + while ((n = pa_split(roles_in_group, ",", &split_state))) { + if (n[0] != '\0') + pa_idxset_put(u->groups[i]->trigger_roles, n, NULL); + else { + pa_log("empty trigger role"); + pa_xfree(n); + goto fail; + } + } + i++; + } else { + pa_log("empty trigger roles"); + pa_xfree(roles_in_group); + goto fail; + } + } + } + if (pa_idxset_isempty(u->groups[0]->trigger_roles)) { + pa_log_debug("Using role 'phone' as trigger role."); + pa_idxset_put(u->groups[0]->trigger_roles, pa_xstrdup("phone"), NULL); + } + + roles = pa_modargs_get_value(ma, u->duck ? "ducking_roles" : "cork_roles", NULL); + if (roles) { + const char *group_split_state = NULL; + char *roles_in_group = NULL; + i = 0; + while ((roles_in_group = pa_split(roles, "/", &group_split_state))) { + if (roles_in_group[0] != '\0') { + const char *split_state = NULL; + char *n = NULL; + while ((n = pa_split(roles_in_group, ",", &split_state))) { + if (n[0] != '\0') + pa_idxset_put(u->groups[i]->interaction_roles, n, NULL); + else { + pa_log("empty ducking role"); + pa_xfree(n); + goto fail; + } + } + i++; + } else { + pa_log("empty ducking roles"); + pa_xfree(roles_in_group); + goto fail; + } + } + } + if (pa_idxset_isempty(u->groups[0]->interaction_roles)) { + pa_log_debug("Using roles 'music' and 'video' as %s roles.", u->duck ? "ducking" : "cork"); + pa_idxset_put(u->groups[0]->interaction_roles, pa_xstrdup("music"), NULL); + pa_idxset_put(u->groups[0]->interaction_roles, pa_xstrdup("video"), NULL); + } + + if (u->duck) { + const char *volumes; + u->groups[0]->volume = pa_sw_volume_from_dB(-20); + if ((volumes = pa_modargs_get_value(ma, "volume", NULL))) { + const char *group_split_state = NULL; + char *n = NULL; + i = 0; + while ((n = pa_split(volumes, "/", &group_split_state))) { + if (n[0] != '\0') { + if (pa_parse_volume(n, &(u->groups[i++]->volume)) < 0) { + pa_log("Failed to parse volume"); + pa_xfree(n); + goto fail; + } + } else { + pa_log("empty volume"); + pa_xfree(n); + goto fail; + } + pa_xfree(n); + } + } + } + + if (pa_modargs_get_value_boolean(ma, "global", &global) < 0) { + pa_log("Invalid boolean parameter: global"); + goto fail; + } + u->global = global; + + u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u); + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u); + u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u); + u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); + u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_state_changed_cb, u); + u->sink_input_mute_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_mute_changed_cb, u); + u->sink_input_proplist_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_changed_cb, u); + + pa_modargs_free(ma); + + return 0; + +fail: + pa_stream_interaction_done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; + +} + +void pa_stream_interaction_done(pa_module *m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->groups) { + uint32_t j; + for (j = 0; j < u->n_groups; j++) { + remove_interactions(u, u->groups[j]); + pa_idxset_free(u->groups[j]->trigger_roles, pa_xfree); + pa_idxset_free(u->groups[j]->interaction_roles, pa_xfree); + pa_hashmap_free(u->groups[j]->interaction_state); + if (u->duck) + pa_xfree(u->groups[j]->name); + pa_xfree(u->groups[j]); + } + pa_xfree(u->groups); + } + + if (u->sink_input_put_slot) + pa_hook_slot_free(u->sink_input_put_slot); + if (u->sink_input_unlink_slot) + pa_hook_slot_free(u->sink_input_unlink_slot); + if (u->sink_input_move_start_slot) + pa_hook_slot_free(u->sink_input_move_start_slot); + if (u->sink_input_move_finish_slot) + pa_hook_slot_free(u->sink_input_move_finish_slot); + if (u->sink_input_state_changed_slot) + pa_hook_slot_free(u->sink_input_state_changed_slot); + if (u->sink_input_mute_changed_slot) + pa_hook_slot_free(u->sink_input_mute_changed_slot); + if (u->sink_input_proplist_changed_slot) + pa_hook_slot_free(u->sink_input_proplist_changed_slot); + + pa_xfree(u); + +} diff --git a/src/modules/stream-interaction.h b/src/modules/stream-interaction.h new file mode 100644 index 0000000..d5bc626 --- /dev/null +++ b/src/modules/stream-interaction.h @@ -0,0 +1,21 @@ +/*** + This file is part of PulseAudio. + + Copyright 2015 Georg Chini + + 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 <http://www.gnu.org/licenses/>. +***/ + +int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]); +void pa_stream_interaction_done(pa_module *m); diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index c23aa6b..a3c9486 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -141,6 +141,7 @@ void pa_client_conf_load(pa_client_conf *c, bool load_from_x11, bool load_from_e { "cookie-file", pa_config_parse_string, &c->cookie_file_from_client_conf, NULL }, { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, { "enable-shm", pa_config_parse_not_bool, &c->disable_shm, NULL }, + { "enable-memfd", pa_config_parse_not_bool, &c->disable_memfd, NULL }, { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, { "auto-connect-localhost", pa_config_parse_bool, &c->auto_connect_localhost, NULL }, { "auto-connect-display", pa_config_parse_bool, &c->auto_connect_display, NULL }, diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h index eac705a..7691ec7 100644 --- a/src/pulse/client-conf.h +++ b/src/pulse/client-conf.h @@ -37,7 +37,7 @@ typedef struct pa_client_conf { bool cookie_from_x11_valid; char *cookie_file_from_application; char *cookie_file_from_client_conf; - bool autospawn, disable_shm, auto_connect_localhost, auto_connect_display; + bool autospawn, disable_shm, disable_memfd, auto_connect_localhost, auto_connect_display; size_t shm_size; } pa_client_conf; diff --git a/src/pulse/context.c b/src/pulse/context.c index 4f084e8..69be5f4 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -69,6 +69,7 @@ void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void pa_command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void pa_command_disable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void pa_command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_REQUEST] = pa_command_request, @@ -90,6 +91,7 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = pa_command_stream_buffer_attr, [PA_COMMAND_ENABLE_SRBCHANNEL] = pa_command_enable_srbchannel, [PA_COMMAND_DISABLE_SRBCHANNEL] = pa_command_disable_srbchannel, + [PA_COMMAND_REGISTER_MEMFD_SHMID] = pa_command_register_memfd_shmid, }; static void context_free(pa_context *c); @@ -125,6 +127,7 @@ static void reset_callbacks(pa_context *c) { pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, pa_proplist *p) { pa_context *c; + pa_mem_type_t type; pa_assert(mainloop); @@ -170,10 +173,18 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char * c->srb_template.readfd = -1; c->srb_template.writefd = -1; - if (!(c->mempool = pa_mempool_new(!c->conf->disable_shm, c->conf->shm_size))) { + c->memfd_on_local = (!c->conf->disable_memfd && pa_memfd_is_locally_supported()); - if (!c->conf->disable_shm) - c->mempool = pa_mempool_new(false, c->conf->shm_size); + type = (c->conf->disable_shm) ? PA_MEM_TYPE_PRIVATE : + ((!c->memfd_on_local) ? + PA_MEM_TYPE_SHARED_POSIX : PA_MEM_TYPE_SHARED_MEMFD); + + if (!(c->mempool = pa_mempool_new(type, c->conf->shm_size, true))) { + + if (!c->conf->disable_shm) { + pa_log_warn("Failed to allocate shared memory pool. Falling back to a normal private one."); + c->mempool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, c->conf->shm_size, true); + } if (!c->mempool) { context_free(c); @@ -249,7 +260,7 @@ static void context_free(pa_context *c) { pa_hashmap_free(c->playback_streams); if (c->mempool) - pa_mempool_free(c->mempool); + pa_mempool_unref(c->mempool); if (c->conf) pa_client_conf_free(c->conf); @@ -326,7 +337,7 @@ static void pstream_die_callback(pa_pstream *p, void *userdata) { pa_context_fail(c, PA_ERR_CONNECTIONTERMINATED); } -static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data, void *userdata) { +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { pa_context *c = userdata; pa_assert(p); @@ -476,6 +487,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t case PA_CONTEXT_AUTHORIZING: { pa_tagstruct *reply; bool shm_on_remote = false; + bool memfd_on_remote = false; if (pa_tagstruct_getu32(t, &c->version) < 0 || !pa_tagstruct_eof(t)) { @@ -494,7 +506,15 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t not. */ if (c->version >= 13) { shm_on_remote = !!(c->version & 0x80000000U); - c->version &= 0x7FFFFFFFU; + + /* Starting with protocol version 31, the second MSB of the version + * tag reflects whether memfd is supported on the other PA end. */ + if (c->version >= 31) + memfd_on_remote = !!(c->version & 0x40000000U); + + /* Reserve the two most-significant _bytes_ of the version tag + * for flags. */ + c->version &= 0x0000FFFFU; } pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION); @@ -520,6 +540,26 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t pa_log_debug("Negotiated SHM: %s", pa_yes_no(c->do_shm)); pa_pstream_enable_shm(c->pstream, c->do_shm); + c->shm_type = PA_MEM_TYPE_PRIVATE; + if (c->do_shm) { + if (c->version >= 31 && memfd_on_remote && c->memfd_on_local) { + const char *reason; + + pa_pstream_enable_memfd(c->pstream); + if (pa_mempool_is_memfd_backed(c->mempool)) + if (pa_pstream_register_memfd_mempool(c->pstream, c->mempool, &reason)) + pa_log("Failed to regester memfd mempool. Reason: %s", reason); + + /* Even if memfd pool registration fails, the negotiated SHM type + * shall remain memfd as both endpoints claim to support it. */ + c->shm_type = PA_MEM_TYPE_SHARED_MEMFD; + } else + c->shm_type = PA_MEM_TYPE_SHARED_POSIX; + } + + pa_log_debug("Memfd possible: %s", pa_yes_no(c->memfd_on_local)); + pa_log_debug("Negotiated SHM type: %s", pa_mem_type_to_string(c->shm_type)); + reply = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); if (c->version >= 13) { @@ -587,8 +627,10 @@ static void setup_context(pa_context *c, pa_iochannel *io) { pa_log_debug("SHM possible: %s", pa_yes_no(c->do_shm)); /* Starting with protocol version 13 we use the MSB of the version - * tag for informing the other side if we could do SHM or not */ - pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION | (c->do_shm ? 0x80000000U : 0)); + * tag for informing the other side if we could do SHM or not. + * Starting from version 31, second MSB is used to flag memfd support. */ + pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION | (c->do_shm ? 0x80000000U : 0) | + (c->memfd_on_local ? 0x40000000 : 0)); pa_tagstruct_put_arbitrary(t, cookie, sizeof(cookie)); #ifdef HAVE_CREDS @@ -1428,8 +1470,7 @@ static void pa_command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uin pa_context *c = userdata; #ifdef HAVE_CREDS - const int *fds; - int nfd; + pa_cmsg_ancil_data *ancil = NULL; pa_assert(pd); pa_assert(command == PA_COMMAND_ENABLE_SRBCHANNEL); @@ -1437,26 +1478,34 @@ static void pa_command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uin pa_assert(c); pa_assert(PA_REFCNT_VALUE(c) >= 1); + ancil = pa_pdispatch_take_ancil_data(pd); + if (!ancil) + goto fail; + /* Currently only one srb channel is supported, might change in future versions */ - if (c->srb_template.readfd != -1) { - pa_context_fail(c, PA_ERR_PROTOCOL); - return; - } + if (c->srb_template.readfd != -1) + goto fail; - fds = pa_pdispatch_fds(pd, &nfd); - if (nfd != 2 || !fds || fds[0] == -1 || fds[1] == -1) { - pa_context_fail(c, PA_ERR_PROTOCOL); - return; - } + if (ancil->nfd != 2 || ancil->fds[0] == -1 || ancil->fds[1] == -1) + goto fail; pa_context_ref(c); - c->srb_template.readfd = fds[0]; - c->srb_template.writefd = fds[1]; + c->srb_template.readfd = ancil->fds[0]; + c->srb_template.writefd = ancil->fds[1]; c->srb_setup_tag = tag; pa_context_unref(c); + ancil->close_fds_on_cleanup = false; + return; + +fail: + if (ancil) + pa_cmsg_ancil_data_close_fds(ancil); + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; #else pa_assert(c); pa_context_fail(c, PA_ERR_PROTOCOL); @@ -1489,6 +1538,18 @@ static void pa_command_disable_srbchannel(pa_pdispatch *pd, uint32_t command, ui pa_pstream_send_tagstruct(c->pstream, t2); } +static void pa_command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REGISTER_MEMFD_SHMID); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_common_command_register_memfd_shmid(c->pstream, pd, c->version, command, t)) + pa_context_fail(c, PA_ERR_PROTOCOL); +} void pa_command_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_context *c = userdata; diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c index 268894a..1ce3cd3 100644 --- a/src/pulse/glib-mainloop.c +++ b/src/pulse/glib-mainloop.c @@ -595,7 +595,7 @@ static const pa_mainloop_api vtable = { .io_new = glib_io_new, .io_enable = glib_io_enable, .io_free = glib_io_free, - .io_set_destroy= glib_io_set_destroy, + .io_set_destroy = glib_io_set_destroy, .time_new = glib_time_new, .time_restart = glib_time_restart, diff --git a/src/pulse/internal.h b/src/pulse/internal.h index eefd181..9bbb903 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -88,6 +88,7 @@ struct pa_context { bool is_local:1; bool do_shm:1; + bool memfd_on_local:1; bool server_specified:1; bool no_fail:1; bool do_autospawn:1; @@ -95,6 +96,8 @@ struct pa_context { bool filter_added:1; pa_spawn_api spawn_api; + pa_mem_type_t shm_type; + pa_strlist *server_list; char *server; diff --git a/src/pulse/sample.h b/src/pulse/sample.h index 7cf50d6..4299eec 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -128,7 +128,7 @@ PA_C_DECL_BEGIN #define PA_CHANNELS_MAX 32U /** Maximum allowed sample rate */ -#define PA_RATE_MAX (48000U*4U) +#define PA_RATE_MAX (48000U*8U) /** Sample format */ typedef enum pa_sample_format { diff --git a/src/pulse/stream.h b/src/pulse/stream.h index ab233d5..5dfdee1 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -547,9 +547,9 @@ int pa_stream_cancel_write( int pa_stream_write( pa_stream *p /**< The stream to use */, const void *data /**< The data to write */, - size_t nbytes /**< The length of the data to write in bytes */, + size_t nbytes /**< The length of the data to write in bytes, must be in multiples of the stream's sample spec frame size */, pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */, - int64_t offset, /**< Offset for seeking, must be 0 for upload streams */ + int64_t offset /**< Offset for seeking, must be 0 for upload streams, must be in multiples of the stream's sample spec frame size */, pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */); /** Function does exactly the same as pa_stream_write() with the difference diff --git a/src/pulse/version.h b/src/pulse/version.h index c59c550..f717c5e 100644 --- a/src/pulse/version.h +++ b/src/pulse/version.h @@ -33,7 +33,7 @@ PA_C_DECL_BEGIN /** Return the version of the header files. Keep in mind that this is a macro and not a function, so it is impossible to get the pointer of it. */ -#define pa_get_headers_version() ("8.0.0") +#define pa_get_headers_version() ("8.99.0") /** Return the version of the library the current application is * linked to. */ @@ -47,13 +47,13 @@ const char* pa_get_library_version(void); /** The current protocol version. Version 8 relates to Polypaudio * 0.8/PulseAudio 0.9. */ -#define PA_PROTOCOL_VERSION 30 +#define PA_PROTOCOL_VERSION 31 /** The major version of PA. \since 0.9.15 */ #define PA_MAJOR 8 /** The minor version of PA. \since 0.9.15 */ -#define PA_MINOR 0 +#define PA_MINOR 99 /** The micro version of PA (will always be 0 from v1.0 onwards). \since 0.9.15 */ #define PA_MICRO 0 diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index b6cbbf7..410746b 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -103,6 +103,15 @@ void pa_card_new_data_set_profile(pa_card_new_data *data, const char *profile) { data->active_profile = pa_xstrdup(profile); } +void pa_card_new_data_set_preferred_port(pa_card_new_data *data, pa_direction_t direction, pa_device_port *port) { + pa_assert(data); + + if (direction == PA_DIRECTION_INPUT) + data->preferred_input_port = port; + else + data->preferred_output_port = port; +} + void pa_card_new_data_done(pa_card_new_data *data) { pa_assert(data); @@ -169,6 +178,9 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) { PA_HASHMAP_FOREACH(port, c->ports, state) port->card = c; + c->preferred_input_port = data->preferred_input_port; + c->preferred_output_port = data->preferred_output_port; + if (data->active_profile) if ((c->active_profile = pa_hashmap_get(c->profiles, data->active_profile))) c->save_profile = data->save_profile; @@ -309,15 +321,63 @@ int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) { return 0; } +void pa_card_set_preferred_port(pa_card *c, pa_direction_t direction, pa_device_port *port) { + pa_device_port *old_port; + const char *old_port_str; + const char *new_port_str; + pa_card_preferred_port_changed_hook_data data; + + pa_assert(c); + + if (direction == PA_DIRECTION_INPUT) { + old_port = c->preferred_input_port; + old_port_str = c->preferred_input_port ? c->preferred_input_port->name : "(unset)"; + } else { + old_port = c->preferred_output_port; + old_port_str = c->preferred_output_port ? c->preferred_output_port->name : "(unset)"; + } + + if (port == old_port) + return; + + new_port_str = port ? port->name : "(unset)"; + + if (direction == PA_DIRECTION_INPUT) { + c->preferred_input_port = port; + pa_log_debug("%s: preferred_input_port: %s -> %s", c->name, old_port_str, new_port_str); + } else { + c->preferred_output_port = port; + pa_log_debug("%s: preferred_output_port: %s -> %s", c->name, old_port_str, new_port_str); + } + + data.card = c; + data.direction = direction; + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED], &data); +} + int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause) { pa_sink *sink; pa_source *source; + pa_suspend_cause_t suspend_cause; uint32_t idx; int ret = 0; pa_assert(c); pa_assert(cause != 0); + suspend_cause = c->suspend_cause; + + if (suspend) + suspend_cause |= cause; + else + suspend_cause &= ~cause; + + if (c->suspend_cause != suspend_cause) { + pa_log_debug("Card suspend causes/state changed"); + c->suspend_cause = suspend_cause; + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_SUSPEND_CHANGED], c); + } + PA_IDXSET_FOREACH(sink, c->sinks, idx) { int r; diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index 30bfc0e..d4970e3 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -79,9 +79,13 @@ struct pa_card { pa_card_profile *active_profile; pa_hashmap *ports; + pa_device_port *preferred_input_port; + pa_device_port *preferred_output_port; bool save_profile:1; + pa_suspend_cause_t suspend_cause; + void *userdata; int (*set_profile)(pa_card *c, pa_card_profile *profile); @@ -98,12 +102,19 @@ typedef struct pa_card_new_data { char *active_profile; pa_hashmap *ports; + pa_device_port *preferred_input_port; + pa_device_port *preferred_output_port; bool namereg_fail:1; bool save_profile:1; } pa_card_new_data; +typedef struct { + pa_card *card; + pa_direction_t direction; +} pa_card_preferred_port_changed_hook_data; + pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra); void pa_card_profile_free(pa_card_profile *c); @@ -113,6 +124,7 @@ void pa_card_profile_set_available(pa_card_profile *c, pa_available_t available) pa_card_new_data *pa_card_new_data_init(pa_card_new_data *data); void pa_card_new_data_set_name(pa_card_new_data *data, const char *name); void pa_card_new_data_set_profile(pa_card_new_data *data, const char *profile); +void pa_card_new_data_set_preferred_port(pa_card_new_data *data, pa_direction_t direction, pa_device_port *port); void pa_card_new_data_done(pa_card_new_data *data); pa_card *pa_card_new(pa_core *c, pa_card_new_data *data); @@ -122,6 +134,8 @@ void pa_card_add_profile(pa_card *c, pa_card_profile *profile); int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save); +void pa_card_set_preferred_port(pa_card *c, pa_direction_t direction, pa_device_port *port); + int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause); #endif diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 19c89a9..b6eb85a 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -2535,8 +2535,10 @@ char *pa_getcwd(void) { if (getcwd(p, l)) return p; - if (errno != ERANGE) + if (errno != ERANGE) { + pa_xfree(p); return NULL; + } pa_xfree(p); l *= 2; diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 43fd30e..6d102f5 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -61,22 +61,25 @@ static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t o static void core_free(pa_object *o); -pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) { +pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) { pa_core* c; pa_mempool *pool; + pa_mem_type_t type; int j; pa_assert(m); if (shared) { - if (!(pool = pa_mempool_new(shared, shm_size))) { - pa_log_warn("Failed to allocate shared memory pool. Falling back to a normal memory pool."); + type = (enable_memfd) ? PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX; + if (!(pool = pa_mempool_new(type, shm_size, false))) { + pa_log_warn("Failed to allocate %s memory pool. Falling back to a normal memory pool.", + pa_mem_type_to_string(type)); shared = false; } } if (!shared) { - if (!(pool = pa_mempool_new(shared, shm_size))) { + if (!(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, shm_size, false))) { pa_log("pa_mempool_new() failed."); return NULL; } @@ -123,13 +126,9 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) { c->subscription_event_last = NULL; c->mempool = pool; + c->shm_size = shm_size; pa_silence_cache_init(&c->silence_cache); - if (shared && !(c->rw_mempool = pa_mempool_new(shared, shm_size))) - pa_log_warn("Failed to allocate shared writable memory pool."); - if (c->rw_mempool) - pa_mempool_set_is_remote_writable(c->rw_mempool, true); - c->exit_event = NULL; c->scache_auto_unload_event = NULL; @@ -216,9 +215,7 @@ static void core_free(pa_object *o) { pa_assert(!c->default_sink); pa_silence_cache_done(&c->silence_cache); - if (c->rw_mempool) - pa_mempool_free(c->rw_mempool); - pa_mempool_free(c->mempool); + pa_mempool_unref(c->mempool); for (j = 0; j < PA_CORE_HOOK_MAX; j++) pa_hook_done(&c->hooks[j]); @@ -283,9 +280,6 @@ void pa_core_maybe_vacuum(pa_core *c) { } pa_mempool_vacuum(c->mempool); - - if (c->rw_mempool) - pa_mempool_vacuum(c->rw_mempool); } pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) { diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index aefc1eb..00d7f2f 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -120,9 +120,11 @@ typedef enum pa_core_hook { PA_CORE_HOOK_CARD_NEW, PA_CORE_HOOK_CARD_PUT, PA_CORE_HOOK_CARD_UNLINK, + PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED, PA_CORE_HOOK_CARD_PROFILE_CHANGED, PA_CORE_HOOK_CARD_PROFILE_ADDED, PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED, + PA_CORE_HOOK_CARD_SUSPEND_CHANGED, PA_CORE_HOOK_PORT_AVAILABLE_CHANGED, PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED, PA_CORE_HOOK_DEFAULT_SINK_CHANGED, @@ -177,10 +179,13 @@ struct pa_core { PA_LLIST_HEAD(pa_subscription_event, subscription_event_queue); pa_subscription_event *subscription_event_last; - /* The mempool is used for data we write to, it's readonly for the client. - The rw_mempool is used for data writable by both server and client (and - can be NULL in some cases). */ - pa_mempool *mempool, *rw_mempool; + /* The mempool is used for data we write to, it's readonly for the client. */ + pa_mempool *mempool; + + /* Shared memory size, as specified either by daemon configuration + * or PA daemon defaults (~ 64 MiB). */ + size_t shm_size; + pa_silence_cache silence_cache; pa_time_event *exit_event; @@ -215,7 +220,7 @@ enum { PA_CORE_MESSAGE_MAX }; -pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size); +pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size); /* Check whether no one is connected to this core */ void pa_core_check_idle(pa_core *c); diff --git a/src/pulsecore/creds.h b/src/pulsecore/creds.h index 64d8387..9fdbb4f 100644 --- a/src/pulsecore/creds.h +++ b/src/pulsecore/creds.h @@ -49,9 +49,14 @@ struct pa_cmsg_ancil_data { pa_creds creds; bool creds_valid; int nfd; + + /* Don't close these fds by your own. Check pa_cmsg_ancil_data_close_fds() */ int fds[MAX_ANCIL_DATA_FDS]; + bool close_fds_on_cleanup; }; +void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil); + #else #undef HAVE_CREDS #endif diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c index 5f5ace2..c0b1eb0 100644 --- a/src/pulsecore/filter/lfe-filter.c +++ b/src/pulsecore/filter/lfe-filter.c @@ -110,6 +110,7 @@ static void process_block(pa_lfe_filter_t *f, pa_memchunk *buf, bool store_resul } pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) { + pa_mempool *pool; struct saved_state *s, *s2; void *data; @@ -129,10 +130,12 @@ pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) { /* TODO: This actually memcpys the entire chunk into a new allocation, because we need to retain the original in case of rewinding. Investigate whether this can be avoided. */ data = pa_memblock_acquire_chunk(buf); - s->chunk.memblock = pa_memblock_new_malloced(pa_memblock_get_pool(buf->memblock), pa_xmemdup(data, buf->length), buf->length); + pool = pa_memblock_get_pool(buf->memblock); + s->chunk.memblock = pa_memblock_new_malloced(pool, pa_xmemdup(data, buf->length), buf->length); s->chunk.length = buf->length; s->chunk.index = 0; pa_memblock_release(buf->memblock); + pa_mempool_unref(pool), pool = NULL; s->index = f->index; memcpy(s->lr4, f->lr4, sizeof(struct lr4) * f->cm.channels); diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c index d1bb109..e62750b 100644 --- a/src/pulsecore/iochannel.c +++ b/src/pulsecore/iochannel.c @@ -450,6 +450,7 @@ ssize_t pa_iochannel_read_with_ancil_data(pa_iochannel*io, void*data, size_t l, } memcpy(ancil_data->fds, CMSG_DATA(cmh), nfd * sizeof(int)); ancil_data->nfd = nfd; + ancil_data->close_fds_on_cleanup = true; } } diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c index 3c876f6..b5db818 100644 --- a/src/pulsecore/log.c +++ b/src/pulsecore/log.c @@ -39,6 +39,17 @@ #endif #ifdef HAVE_SYSTEMD_JOURNAL + +/* sd_journal_send() implicitly add fields for the source file, + * function name and code line from where it's invoked. As the + * correct code location fields CODE_FILE, CODE_LINE and + * CODE_FUNC are already handled by this module, we do not want + * the automatic values supplied by the systemd journal API. + * + * Without suppressing these, both the actual log event source + * and the call to sd_journal_send() will be logged. */ +#define SD_JOURNAL_SUPPRESS_LOCATION + #include <systemd/sd-journal.h> #endif diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h index 1716f81..1e15f4f 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -298,14 +298,16 @@ static inline size_t PA_PAGE_ALIGN(size_t l) { #define PA_INT_TYPE_SIGNED(type) (!!((type) 0 > (type) -1)) +#define PA_INT_TYPE_HALF(type) ((type) 1 << (sizeof(type)*8 - 2)) + #define PA_INT_TYPE_MAX(type) \ ((type) (PA_INT_TYPE_SIGNED(type) \ - ? ~(~(type) 0 << (8*sizeof(type)-1)) \ + ? (PA_INT_TYPE_HALF(type) - 1 + PA_INT_TYPE_HALF(type)) \ : (type) -1)) #define PA_INT_TYPE_MIN(type) \ ((type) (PA_INT_TYPE_SIGNED(type) \ - ? (~(type) 0 << (8*sizeof(type)-1)) \ + ? (-1 - PA_INT_TYPE_MAX(type)) \ : (type) 0)) /* We include this at the very last place */ diff --git a/src/pulsecore/mem.h b/src/pulsecore/mem.h new file mode 100644 index 0000000..cba1410 --- /dev/null +++ b/src/pulsecore/mem.h @@ -0,0 +1,60 @@ +#ifndef foopulsememhfoo +#define foopulsememhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> + +typedef enum pa_mem_type { + PA_MEM_TYPE_SHARED_POSIX, /* Data is shared and created using POSIX shm_open() */ + PA_MEM_TYPE_SHARED_MEMFD, /* Data is shared and created using Linux memfd_create() */ + PA_MEM_TYPE_PRIVATE, /* Data is private and created using classic memory allocation + (posix_memalign(), malloc() or anonymous mmap()) */ +} pa_mem_type_t; + +static inline const char *pa_mem_type_to_string(pa_mem_type_t type) { + switch (type) { + case PA_MEM_TYPE_SHARED_POSIX: + return "shared posix-shm"; + case PA_MEM_TYPE_SHARED_MEMFD: + return "shared memfd"; + case PA_MEM_TYPE_PRIVATE: + return "private"; + } + + pa_assert_not_reached(); +} + +static inline bool pa_mem_type_is_shared(pa_mem_type_t t) { + return (t == PA_MEM_TYPE_SHARED_POSIX) || (t == PA_MEM_TYPE_SHARED_MEMFD); +} + +static inline bool pa_memfd_is_locally_supported() { +#if defined(HAVE_CREDS) && defined(HAVE_MEMFD) + return true; +#else + return false; +#endif +} + +#endif diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c index 9b6810d..17520ed 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -100,6 +100,28 @@ struct pa_memimport_segment { bool writable; }; +/* + * If true, this segment's lifetime will not be limited by the + * number of active blocks (seg->n_blocks) using its shared memory. + * Rather, it will exist for the full lifetime of the memimport it + * is attached to. + * + * This is done to support memfd blocks transport. + * + * To transfer memfd-backed blocks without passing their fd every + * time, thus minimizing overhead and avoiding fd leaks, a command + * is sent with the memfd fd as ancil data very early on. + * + * This command has an ID that identifies the memfd region. Further + * block references are then exclusively done using this ID. On the + * receiving end, such logic is enabled by the memimport's segment + * hash and 'permanent' segments below. + */ +static bool segment_is_permanent(pa_memimport_segment *seg) { + pa_assert(seg); + return seg->memory.type == PA_MEM_TYPE_SHARED_MEMFD; +} + /* A collection of multiple segments */ struct pa_memimport { pa_mutex *mutex; @@ -142,10 +164,30 @@ struct pa_memexport { }; struct pa_mempool { + /* Reference count the mempool + * + * Any block allocation from the pool itself, or even just imported from + * another process through SHM and attached to it (PA_MEMBLOCK_IMPORTED), + * shall increase the refcount. + * + * This is done for per-client mempools: global references to blocks in + * the pool, or just to attached ones, can still be lingering around when + * the client connection dies and all per-client objects are to be freed. + * That is, current PulseAudio design does not guarantee that the client + * mempool blocks are referenced only by client-specific objects. + * + * For further details, please check: + * https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-February/025587.html + */ + PA_REFCNT_DECLARE; + pa_semaphore *semaphore; pa_mutex *mutex; pa_shm memory; + + bool global; + size_t block_size; unsigned n_blocks; bool is_remote_writable; @@ -237,6 +279,7 @@ static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) { b = pa_xmalloc(PA_ALIGN(sizeof(pa_memblock)) + length); PA_REFCNT_INIT(b); b->pool = p; + pa_mempool_ref(b->pool); b->type = PA_MEMBLOCK_APPENDED; b->read_only = b->is_silence = false; pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); @@ -367,6 +410,7 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) { PA_REFCNT_INIT(b); b->pool = p; + pa_mempool_ref(b->pool); b->read_only = b->is_silence = false; b->length = length; pa_atomic_store(&b->n_acquired, 0); @@ -390,6 +434,7 @@ pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, bool r PA_REFCNT_INIT(b); b->pool = p; + pa_mempool_ref(b->pool); b->type = PA_MEMBLOCK_FIXED; b->read_only = read_only; b->is_silence = false; @@ -423,6 +468,7 @@ pa_memblock *pa_memblock_new_user( PA_REFCNT_INIT(b); b->pool = p; + pa_mempool_ref(b->pool); b->type = PA_MEMBLOCK_USER; b->read_only = read_only; b->is_silence = false; @@ -518,10 +564,13 @@ size_t pa_memblock_get_length(pa_memblock *b) { return b->length; } +/* Note! Always unref the returned pool after use */ pa_mempool* pa_memblock_get_pool(pa_memblock *b) { pa_assert(b); pa_assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b->pool); + pa_mempool_ref(b->pool); return b->pool; } @@ -535,10 +584,13 @@ pa_memblock* pa_memblock_ref(pa_memblock*b) { } static void memblock_free(pa_memblock *b) { - pa_assert(b); + pa_mempool *pool; + pa_assert(b); + pa_assert(b->pool); pa_assert(pa_atomic_load(&b->n_acquired) == 0); + pool = b->pool; stat_remove(b); switch (b->type) { @@ -620,6 +672,8 @@ static void memblock_free(pa_memblock *b) { default: pa_assert_not_reached(); } + + pa_mempool_unref(pool); } /* No lock necessary */ @@ -744,11 +798,39 @@ static void memblock_replace_import(pa_memblock *b) { pa_mutex_unlock(import->mutex); } -pa_mempool* pa_mempool_new(bool shared, size_t size) { +/*@per_client: This is a security measure. By default this should + * be set to true where the created mempool is never shared with more + * than one client in the system. Set this to false if a global + * mempool, shared with all existing and future clients, is required. + * + * NOTE-1: Do not create any further global mempools! They allow data + * leaks between clients and thus conflict with the xdg-app containers + * model. They also complicate the handling of memfd-based pools. + * + * NOTE-2: Almost all mempools are now created on a per client basis. + * The only exception is the pa_core's mempool which is still shared + * between all clients of the system. + * + * Beside security issues, special marking for global mempools is + * required for memfd communication. To avoid fd leaks, memfd pools + * are registered with the connection pstream to create an ID<->memfd + * mapping on both PA endpoints. Such memory regions are then always + * referenced by their IDs and never by their fds and thus their fds + * can be quickly closed later. + * + * Unfortunately this scheme cannot work with global pools since the + * ID registration mechanism needs to happen for each newly connected + * client, and thus the need for a more special handling. That is, + * for the pool's fd to be always open :-( + * + * TODO-1: Transform the global core mempool to a per-client one + * TODO-2: Remove global mempools support */ +pa_mempool *pa_mempool_new(pa_mem_type_t type, size_t size, bool per_client) { pa_mempool *p; char t1[PA_BYTES_SNPRINT_MAX], t2[PA_BYTES_SNPRINT_MAX]; p = pa_xnew0(pa_mempool, 1); + PA_REFCNT_INIT(p); p->block_size = PA_PAGE_ALIGN(PA_MEMPOOL_SLOT_SIZE); if (p->block_size < PA_PAGE_SIZE) @@ -763,18 +845,20 @@ pa_mempool* pa_mempool_new(bool shared, size_t size) { p->n_blocks = 2; } - if (pa_shm_create_rw(&p->memory, p->n_blocks * p->block_size, shared, 0700) < 0) { + if (pa_shm_create_rw(&p->memory, type, p->n_blocks * p->block_size, 0700) < 0) { pa_xfree(p); return NULL; } pa_log_debug("Using %s memory pool with %u slots of size %s each, total size is %s, maximum usable slot size is %lu", - p->memory.shared ? "shared" : "private", + pa_mem_type_to_string(type), p->n_blocks, pa_bytes_snprint(t1, sizeof(t1), (unsigned) p->block_size), pa_bytes_snprint(t2, sizeof(t2), (unsigned) (p->n_blocks * p->block_size)), (unsigned long) pa_mempool_block_size_max(p)); + p->global = !per_client; + pa_atomic_store(&p->n_init, 0); PA_LLIST_HEAD_INIT(pa_memimport, p->imports); @@ -788,7 +872,7 @@ pa_mempool* pa_mempool_new(bool shared, size_t size) { return p; } -void pa_mempool_free(pa_mempool *p) { +static void mempool_free(pa_mempool *p) { pa_assert(p); pa_mutex_lock(p->mutex); @@ -893,10 +977,24 @@ void pa_mempool_vacuum(pa_mempool *p) { } /* No lock necessary */ +bool pa_mempool_is_shared(pa_mempool *p) { + pa_assert(p); + + return pa_mem_type_is_shared(p->memory.type); +} + +/* No lock necessary */ +bool pa_mempool_is_memfd_backed(const pa_mempool *p) { + pa_assert(p); + + return (p->memory.type == PA_MEM_TYPE_SHARED_MEMFD); +} + +/* No lock necessary */ int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) { pa_assert(p); - if (!p->memory.shared) + if (!pa_mempool_is_shared(p)) return -1; *id = p->memory.id; @@ -904,11 +1002,84 @@ int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) { return 0; } -/* No lock necessary */ -bool pa_mempool_is_shared(pa_mempool *p) { +pa_mempool* pa_mempool_ref(pa_mempool *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + PA_REFCNT_INC(p); + return p; +} + +void pa_mempool_unref(pa_mempool *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (PA_REFCNT_DEC(p) <= 0) + mempool_free(p); +} + +/* No lock necessary + * Check pa_mempool_new() for per-client vs. global mempools */ +bool pa_mempool_is_global(pa_mempool *p) { + pa_assert(p); + + return p->global; +} + +/* No lock necessary + * Check pa_mempool_new() for per-client vs. global mempools */ +bool pa_mempool_is_per_client(pa_mempool *p) { + return !pa_mempool_is_global(p); +} + +/* Self-locked + * + * This is only for per-client mempools! + * + * After this method's return, the caller owns the file descriptor + * and is responsible for closing it in the appropriate time. This + * should only be called once during during a mempool's lifetime. + * + * Check pa_shm->fd and pa_mempool_new() for further context. */ +int pa_mempool_take_memfd_fd(pa_mempool *p) { + int memfd_fd; + pa_assert(p); + pa_assert(pa_mempool_is_shared(p)); + pa_assert(pa_mempool_is_memfd_backed(p)); + pa_assert(pa_mempool_is_per_client(p)); + + pa_mutex_lock(p->mutex); + + memfd_fd = p->memory.fd; + p->memory.fd = -1; - return p->memory.shared; + pa_mutex_unlock(p->mutex); + + pa_assert(memfd_fd != -1); + return memfd_fd; +} + +/* No lock necessary + * + * This is only for global mempools! + * + * Global mempools have their memfd descriptor always open. DO NOT + * close the returned descriptor by your own. + * + * Check pa_mempool_new() for further context. */ +int pa_mempool_get_memfd_fd(pa_mempool *p) { + int memfd_fd; + + pa_assert(p); + pa_assert(pa_mempool_is_shared(p)); + pa_assert(pa_mempool_is_memfd_backed(p)); + pa_assert(pa_mempool_is_global(p)); + + memfd_fd = p->memory.fd; + pa_assert(memfd_fd != -1); + + return memfd_fd; } /* For receiving blocks from other nodes */ @@ -921,6 +1092,7 @@ pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void i = pa_xnew(pa_memimport, 1); i->mutex = pa_mutex_new(true, true); i->pool = p; + pa_mempool_ref(i->pool); i->segments = pa_hashmap_new(NULL, NULL); i->blocks = pa_hashmap_new(NULL, NULL); i->release_cb = cb; @@ -935,16 +1107,19 @@ pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i); -/* Should be called locked */ -static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id, bool writable) { +/* Should be called locked + * Caller owns passed @memfd_fd and must close it down when appropriate. */ +static pa_memimport_segment* segment_attach(pa_memimport *i, pa_mem_type_t type, uint32_t shm_id, + int memfd_fd, bool writable) { pa_memimport_segment* seg; + pa_assert(pa_mem_type_is_shared(type)); if (pa_hashmap_size(i->segments) >= PA_MEMIMPORT_SEGMENTS_MAX) return NULL; seg = pa_xnew0(pa_memimport_segment, 1); - if (pa_shm_attach(&seg->memory, shm_id, writable) < 0) { + if (pa_shm_attach(&seg->memory, type, shm_id, memfd_fd, writable) < 0) { pa_xfree(seg); return NULL; } @@ -960,6 +1135,7 @@ static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id, bo /* Should be called locked */ static void segment_detach(pa_memimport_segment *seg) { pa_assert(seg); + pa_assert(seg->n_blocks == (segment_is_permanent(seg) ? 1u : 0u)); pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id)); pa_shm_free(&seg->memory); @@ -974,6 +1150,8 @@ static void segment_detach(pa_memimport_segment *seg) { void pa_memimport_free(pa_memimport *i) { pa_memexport *e; pa_memblock *b; + pa_memimport_segment *seg; + void *state = NULL; pa_assert(i); @@ -982,6 +1160,15 @@ void pa_memimport_free(pa_memimport *i) { while ((b = pa_hashmap_first(i->blocks))) memblock_replace_import(b); + /* Permanent segments exist for the lifetime of the memimport. Now + * that we're freeing the memimport itself, clear them all up. + * + * Careful! segment_detach() internally removes itself from the + * memimport's hash; the same hash we're now using for iteration. */ + PA_HASHMAP_FOREACH(seg, i->segments, state) { + if (segment_is_permanent(seg)) + segment_detach(seg); + } pa_assert(pa_hashmap_size(i->segments) == 0); pa_mutex_unlock(i->mutex); @@ -996,6 +1183,7 @@ void pa_memimport_free(pa_memimport *i) { pa_mutex_unlock(i->pool->mutex); + pa_mempool_unref(i->pool); pa_hashmap_free(i->blocks); pa_hashmap_free(i->segments); @@ -1004,13 +1192,47 @@ void pa_memimport_free(pa_memimport *i) { pa_xfree(i); } +/* Create a new memimport's memfd segment entry, with passed SHM ID + * as key and the newly-created segment (with its mmap()-ed memfd + * memory region) as its value. + * + * Note! check comments at 'pa_shm->fd', 'segment_is_permanent()', + * and 'pa_pstream_register_memfd_mempool()' for further details. + * + * Caller owns passed @memfd_fd and must close it down when appropriate. */ +int pa_memimport_attach_memfd(pa_memimport *i, uint32_t shm_id, int memfd_fd, bool writable) { + pa_memimport_segment *seg; + int ret = -1; + + pa_assert(i); + pa_assert(memfd_fd != -1); + + pa_mutex_lock(i->mutex); + + if (!(seg = segment_attach(i, PA_MEM_TYPE_SHARED_MEMFD, shm_id, memfd_fd, writable))) + goto finish; + + /* n_blocks acts as a segment reference count. To avoid the segment + * being deleted when receiving silent memchunks, etc., mark our + * permanent presence by incrementing that refcount. */ + seg->n_blocks++; + + pa_assert(segment_is_permanent(seg)); + ret = 0; + +finish: + pa_mutex_unlock(i->mutex); + return ret; +} + /* Self-locked */ -pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_id, +pa_memblock* pa_memimport_get(pa_memimport *i, pa_mem_type_t type, uint32_t block_id, uint32_t shm_id, size_t offset, size_t size, bool writable) { pa_memblock *b = NULL; pa_memimport_segment *seg; pa_assert(i); + pa_assert(pa_mem_type_is_shared(type)); pa_mutex_lock(i->mutex); @@ -1022,12 +1244,20 @@ pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_i if (pa_hashmap_size(i->blocks) >= PA_MEMIMPORT_SLOTS_MAX) goto finish; - if (!(seg = pa_hashmap_get(i->segments, PA_UINT32_TO_PTR(shm_id)))) - if (!(seg = segment_attach(i, shm_id, writable))) + if (!(seg = pa_hashmap_get(i->segments, PA_UINT32_TO_PTR(shm_id)))) { + if (type == PA_MEM_TYPE_SHARED_MEMFD) { + pa_log("Bailing out! No cached memimport segment for memfd ID %u", shm_id); + pa_log("Did the other PA endpoint forget registering its memfd pool?"); goto finish; + } + + pa_assert(type == PA_MEM_TYPE_SHARED_POSIX); + if (!(seg = segment_attach(i, type, shm_id, -1, writable))) + goto finish; + } - if (writable != seg->writable) { - pa_log("Cannot open segment - writable status changed!"); + if (writable && !seg->writable) { + pa_log("Cannot import cached segment in write mode - previously mapped as read-only"); goto finish; } @@ -1039,6 +1269,7 @@ pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_i PA_REFCNT_INIT(b); b->pool = i->pool; + pa_mempool_ref(b->pool); b->type = PA_MEMBLOCK_IMPORTED; b->read_only = !writable; b->is_silence = false; @@ -1090,12 +1321,13 @@ pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void pa_assert(p); pa_assert(cb); - if (!p->memory.shared) + if (!pa_mempool_is_shared(p)) return NULL; e = pa_xnew(pa_memexport, 1); e->mutex = pa_mutex_new(true, true); e->pool = p; + pa_mempool_ref(e->pool); PA_LLIST_HEAD_INIT(struct memexport_slot, e->free_slots); PA_LLIST_HEAD_INIT(struct memexport_slot, e->used_slots); e->n_init = 0; @@ -1123,6 +1355,7 @@ void pa_memexport_free(pa_memexport *e) { PA_LLIST_REMOVE(pa_memexport, e->pool->exports, e); pa_mutex_unlock(e->pool->mutex); + pa_mempool_unref(e->pool); pa_mutex_free(e->mutex); pa_xfree(e); } @@ -1217,13 +1450,15 @@ static pa_memblock *memblock_shared_copy(pa_mempool *p, pa_memblock *b) { } /* Self-locked */ -int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32_t *shm_id, size_t *offset, size_t * size) { - pa_shm *memory; +int pa_memexport_put(pa_memexport *e, pa_memblock *b, pa_mem_type_t *type, uint32_t *block_id, + uint32_t *shm_id, size_t *offset, size_t * size) { + pa_shm *memory; struct memexport_slot *slot; void *data; pa_assert(e); pa_assert(b); + pa_assert(type); pa_assert(block_id); pa_assert(shm_id); pa_assert(offset); @@ -1261,12 +1496,14 @@ int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32 } else { pa_assert(b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL); pa_assert(b->pool); + pa_assert(pa_mempool_is_shared(b->pool)); memory = &b->pool->memory; } pa_assert(data >= memory->ptr); pa_assert((uint8_t*) data + b->length <= (uint8_t*) memory->ptr + memory->size); + *type = memory->type; *shm_id = memory->id; *offset = (size_t) ((uint8_t*) data - (uint8_t*) memory->ptr); *size = b->length; diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h index 4faef75..57ae4b2 100644 --- a/src/pulsecore/memblock.h +++ b/src/pulsecore/memblock.h @@ -30,6 +30,7 @@ typedef struct pa_memblock pa_memblock; #include <pulse/xmalloc.h> #include <pulsecore/atomic.h> #include <pulsecore/memchunk.h> +#include <pulsecore/mem.h> /* A pa_memblock is a reference counted memory block. PulseAudio * passes references to pa_memblocks around instead of copying @@ -116,32 +117,43 @@ void *pa_memblock_acquire_chunk(const pa_memchunk *c); void pa_memblock_release(pa_memblock *b); size_t pa_memblock_get_length(pa_memblock *b); + +/* Note! Always unref the returned pool after use */ pa_mempool * pa_memblock_get_pool(pa_memblock *b); pa_memblock *pa_memblock_will_need(pa_memblock *b); /* The memory block manager */ -pa_mempool* pa_mempool_new(bool shared, size_t size); -void pa_mempool_free(pa_mempool *p); +pa_mempool *pa_mempool_new(pa_mem_type_t type, size_t size, bool per_client); +void pa_mempool_unref(pa_mempool *p); +pa_mempool* pa_mempool_ref(pa_mempool *p); const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p); void pa_mempool_vacuum(pa_mempool *p); int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id); bool pa_mempool_is_shared(pa_mempool *p); +bool pa_mempool_is_memfd_backed(const pa_mempool *p); +bool pa_mempool_is_global(pa_mempool *p); +bool pa_mempool_is_per_client(pa_mempool *p); bool pa_mempool_is_remote_writable(pa_mempool *p); void pa_mempool_set_is_remote_writable(pa_mempool *p, bool writable); size_t pa_mempool_block_size_max(pa_mempool *p); +int pa_mempool_take_memfd_fd(pa_mempool *p); +int pa_mempool_get_memfd_fd(pa_mempool *p); + /* For receiving blocks from other nodes */ pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata); void pa_memimport_free(pa_memimport *i); -pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_id, - size_t offset, size_t size, bool writable); +int pa_memimport_attach_memfd(pa_memimport *i, uint32_t shm_id, int memfd_fd, bool writable); +pa_memblock* pa_memimport_get(pa_memimport *i, pa_mem_type_t type, uint32_t block_id, + uint32_t shm_id, size_t offset, size_t size, bool writable); int pa_memimport_process_revoke(pa_memimport *i, uint32_t block_id); /* For sending blocks to other nodes */ pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata); void pa_memexport_free(pa_memexport *e); -int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32_t *shm_id, size_t *offset, size_t *size); +int pa_memexport_put(pa_memexport *e, pa_memblock *b, pa_mem_type_t *type, uint32_t *block_id, + uint32_t *shm_id, size_t *offset, size_t * size); int pa_memexport_process_release(pa_memexport *e, uint32_t id); #endif diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index d314d4e..d283ed2 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -530,6 +530,7 @@ int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) { } int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk) { + pa_mempool *pool; pa_memchunk tchunk, rchunk; int64_t ri; struct list_item *item; @@ -548,9 +549,11 @@ int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchun return 0; } - rchunk.memblock = pa_memblock_new(pa_memblock_get_pool(tchunk.memblock), block_size); + pool = pa_memblock_get_pool(tchunk.memblock); + rchunk.memblock = pa_memblock_new(pool, block_size); rchunk.index = 0; rchunk.length = tchunk.length; + pa_mempool_unref(pool), pool = NULL; pa_memchunk_memcpy(&rchunk, &tchunk); pa_memblock_unref(tchunk.memblock); diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c index eb5917c..8822134 100644 --- a/src/pulsecore/memchunk.c +++ b/src/pulsecore/memchunk.c @@ -32,6 +32,7 @@ #include "memchunk.h" pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min) { + pa_mempool *pool; pa_memblock *n; size_t l; void *tdata, *sdata; @@ -46,7 +47,9 @@ pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min) { l = PA_MAX(c->length, min); - n = pa_memblock_new(pa_memblock_get_pool(c->memblock), l); + pool = pa_memblock_get_pool(c->memblock); + n = pa_memblock_new(pool, l); + pa_mempool_unref(pool), pool = NULL; sdata = pa_memblock_acquire(c->memblock); tdata = pa_memblock_acquire(n); diff --git a/src/pulsecore/memfd-wrappers.h b/src/pulsecore/memfd-wrappers.h new file mode 100644 index 0000000..3bed9b2 --- /dev/null +++ b/src/pulsecore/memfd-wrappers.h @@ -0,0 +1,68 @@ +#ifndef foopulsememfdwrappershfoo +#define foopulsememfdwrappershfoo + +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_MEMFD + +#include <sys/syscall.h> +#include <fcntl.h> + +/* + * No glibc wrappers exist for memfd_create(2), so provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int memfd_create(const char *name, unsigned int flags) { + return syscall(SYS_memfd_create, name, flags); +} + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#endif /* HAVE_MEMFD */ + +#endif diff --git a/src/pulsecore/native-common.c b/src/pulsecore/native-common.c new file mode 100644 index 0000000..282a4ed --- /dev/null +++ b/src/pulsecore/native-common.c @@ -0,0 +1,78 @@ +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> + +#include "native-common.h" + +/* + * Command handlers shared between client and server + */ + +/* Check pa_pstream_register_memfd_mempool() for further details */ +int pa_common_command_register_memfd_shmid(pa_pstream *p, pa_pdispatch *pd, uint32_t version, + uint32_t command, pa_tagstruct *t) { +#if defined(HAVE_CREDS) && defined(HAVE_MEMFD) + pa_cmsg_ancil_data *ancil = NULL; + unsigned shm_id; + int ret = -1; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REGISTER_MEMFD_SHMID); + pa_assert(t); + + ancil = pa_pdispatch_take_ancil_data(pd); + if (!ancil) + goto finish; + + /* Upon fd leaks and reaching our open fd limit, recvmsg(2) + * just strips all passed fds from the ancillary data */ + if (ancil->nfd == 0) { + pa_log("Expected 1 memfd fd to be received over pipe; got 0"); + pa_log("Did we reach our open file descriptors limit?"); + goto finish; + } + + if (ancil->nfd != 1 || ancil->fds[0] == -1) + goto finish; + + if (version < 31 || pa_tagstruct_getu32(t, &shm_id) < 0 || !pa_tagstruct_eof(t)) + goto finish; + + pa_pstream_attach_memfd_shmid(p, shm_id, ancil->fds[0]); + + ret = 0; +finish: + if (ancil) + pa_cmsg_ancil_data_close_fds(ancil); + + return ret; +#else + return -1; +#endif +} diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index dc62895..70338b9 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -24,6 +24,10 @@ #include <pulse/cdecl.h> #include <pulse/def.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> + PA_C_DECL_BEGIN enum { @@ -179,6 +183,10 @@ enum { PA_COMMAND_ENABLE_SRBCHANNEL, PA_COMMAND_DISABLE_SRBCHANNEL, + /* Supported since protocol v31 (9.0) + * BOTH DIRECTIONS */ + PA_COMMAND_REGISTER_MEMFD_SHMID, + PA_COMMAND_MAX }; @@ -193,6 +201,9 @@ enum { #define PA_NATIVE_DEFAULT_UNIX_SOCKET "native" +int pa_common_command_register_memfd_shmid(pa_pstream *p, pa_pdispatch *pd, uint32_t version, + uint32_t command, pa_tagstruct *t); + PA_C_DECL_END #endif diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index f136875..ab632a5 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -195,6 +195,10 @@ static const char *command_names[PA_COMMAND_MAX] = { /* BOTH DIRECTIONS */ [PA_COMMAND_ENABLE_SRBCHANNEL] = "ENABLE_SRBCHANNEL", [PA_COMMAND_DISABLE_SRBCHANNEL] = "DISABLE_SRBCHANNEL", + + /* Supported since protocol v31 (9.0) */ + /* BOTH DIRECTIONS */ + [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID", }; #endif @@ -219,7 +223,7 @@ struct pa_pdispatch { PA_LLIST_HEAD(struct reply_info, replies); pa_pdispatch_drain_cb_t drain_callback; void *drain_userdata; - const pa_cmsg_ancil_data *ancil_data; + pa_cmsg_ancil_data *ancil_data; bool use_rtclock; }; @@ -289,7 +293,7 @@ static void run_action(pa_pdispatch *pd, struct reply_info *r, uint32_t command, pa_pdispatch_unref(pd); } -int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data, void *userdata) { +int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { uint32_t tag, command; pa_tagstruct *ts = NULL; int ret = -1; @@ -448,18 +452,24 @@ const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd) { return NULL; } -const int * pa_pdispatch_fds(pa_pdispatch *pd, int *nfd) { +/* Should be called only once during the dispatcher lifetime + * + * If the returned ancillary data contains any fds, caller maintains sole + * responsibility of closing them down using pa_cmsg_ancil_data_close_fds() */ +pa_cmsg_ancil_data *pa_pdispatch_take_ancil_data(pa_pdispatch *pd) { + pa_cmsg_ancil_data *ancil; + pa_assert(pd); pa_assert(PA_REFCNT_VALUE(pd) >= 1); - pa_assert(nfd); - if (pd->ancil_data) { - *nfd = pd->ancil_data->nfd; - return pd->ancil_data->fds; - } + ancil = pd->ancil_data; - *nfd = 0; - return NULL; + /* iochannel guarantees us that nfd will always be capped */ + if (ancil) + pa_assert(ancil->nfd <= MAX_ANCIL_DATA_FDS); + + pd->ancil_data = NULL; + return ancil; } #endif diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h index 9cb3419..af76981 100644 --- a/src/pulsecore/pdispatch.h +++ b/src/pulsecore/pdispatch.h @@ -39,7 +39,7 @@ pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, bool use_rtclock, const pa_pd void pa_pdispatch_unref(pa_pdispatch *pd); pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd); -int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *p, const pa_cmsg_ancil_data *ancil_data, void *userdata); +int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *p, pa_cmsg_ancil_data *ancil_data, void *userdata); void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t callback, void *userdata, pa_free_cb_t free_cb); @@ -51,7 +51,6 @@ void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t c void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata); const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd); - -const int * pa_pdispatch_fds(pa_pdispatch *pd, int *nfd); +pa_cmsg_ancil_data *pa_pdispatch_take_ancil_data(pa_pdispatch *pd); #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 145db04..5619b9c 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -48,6 +48,7 @@ #include <pulsecore/core-scache.h> #include <pulsecore/core-subscribe.h> #include <pulsecore/log.h> +#include <pulsecore/mem.h> #include <pulsecore/strlist.h> #include <pulsecore/shared.h> #include <pulsecore/sample-util.h> @@ -55,6 +56,7 @@ #include <pulsecore/core-util.h> #include <pulsecore/ipacl.h> #include <pulsecore/thread-mq.h> +#include <pulsecore/mem.h> #include "protocol-native.h" @@ -173,6 +175,12 @@ struct pa_native_connection { bool is_local:1; uint32_t version; pa_client *client; + /* R/W mempool, one per client connection, for srbchannel transport. + * Both server and client can write to this shm area. + * + * Note: This will be NULL if our connection with the client does + * not support srbchannels */ + pa_mempool *rw_mempool; pa_pstream *pstream; pa_pdispatch *pdispatch; pa_idxset *record_streams, *output_streams; @@ -294,6 +302,7 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_ERROR] = NULL, @@ -399,6 +408,8 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_ENABLE_SRBCHANNEL] = command_enable_srbchannel, + [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid, + [PA_COMMAND_EXTENSION] = command_extension }; @@ -1371,6 +1382,9 @@ static void native_connection_free(pa_object *o) { pa_pdispatch_unref(c->pdispatch); pa_pstream_unref(c->pstream); + if (c->rw_mempool) + pa_mempool_unref(c->rw_mempool); + pa_client_free(c->client); pa_xfree(c); @@ -2584,13 +2598,18 @@ static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */ } -static void setup_srbchannel(pa_native_connection *c) { +static void setup_srbchannel(pa_native_connection *c, pa_mem_type_t shm_type) { pa_srbchannel_template srbt; pa_srbchannel *srb; pa_memchunk mc; pa_tagstruct *t; int fdlist[2]; +#ifndef HAVE_CREDS + pa_log_debug("Disabling srbchannel, reason: No fd passing support"); + return; +#endif + if (!c->options->srbchannel) { pa_log_debug("Disabling srbchannel, reason: Must be enabled by module parameter"); return; @@ -2606,15 +2625,31 @@ static void setup_srbchannel(pa_native_connection *c) { return; } - if (!c->protocol->core->rw_mempool) { - pa_log_debug("Disabling srbchannel, reason: No rw memory pool"); + if (c->rw_mempool) { + pa_log_debug("Ignoring srbchannel setup, reason: received COMMAND_AUTH " + "more than once"); return; } - srb = pa_srbchannel_new(c->protocol->core->mainloop, c->protocol->core->rw_mempool); + if (!(c->rw_mempool = pa_mempool_new(shm_type, c->protocol->core->shm_size, true))) { + pa_log_warn("Disabling srbchannel, reason: Failed to allocate shared " + "writable memory pool."); + return; + } + + if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) { + const char *reason; + if (pa_pstream_register_memfd_mempool(c->pstream, c->rw_mempool, &reason)) { + pa_log_warn("Disabling srbchannel, reason: Failed to register memfd mempool: %s", reason); + goto fail; + } + } + pa_mempool_set_is_remote_writable(c->rw_mempool, true); + + srb = pa_srbchannel_new(c->protocol->core->mainloop, c->rw_mempool); if (!srb) { pa_log_debug("Failed to create srbchannel"); - return; + goto fail; } pa_log_debug("Enabling srbchannel..."); pa_srbchannel_export(srb, &srbt); @@ -2625,7 +2660,7 @@ static void setup_srbchannel(pa_native_connection *c) { pa_tagstruct_putu32(t, (size_t) srb); /* tag */ fdlist[0] = srbt.readfd; fdlist[1] = srbt.writefd; - pa_pstream_send_tagstruct_with_fds(c->pstream, t, 2, fdlist); + pa_pstream_send_tagstruct_with_fds(c->pstream, t, 2, fdlist, false); /* Send ringbuffer memblock to client */ mc.memblock = srbt.memblock; @@ -2634,6 +2669,13 @@ static void setup_srbchannel(pa_native_connection *c) { pa_pstream_send_memblock(c->pstream, 0, 0, 0, &mc); c->srbpending = srb; + return; + +fail: + if (c->rw_mempool) { + pa_mempool_unref(c->rw_mempool); + c->rw_mempool = NULL; + } } static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { @@ -2652,7 +2694,9 @@ static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32 static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); const void*cookie; + bool memfd_on_remote = false, do_memfd = false; pa_tagstruct *reply; + pa_mem_type_t shm_type; bool shm_on_remote = false, do_shm; pa_native_connection_assert_ref(c); @@ -2676,7 +2720,15 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta not. */ if (c->version >= 13) { shm_on_remote = !!(c->version & 0x80000000U); - c->version &= 0x7FFFFFFFU; + + /* Starting with protocol version 31, the second MSB of the version + * tag reflects whether memfd is supported on the other PA end. */ + if (c->version >= 31) + memfd_on_remote = !!(c->version & 0x40000000U); + + /* Reserve the two most-significant _bytes_ of the version tag + * for flags. */ + c->version &= 0x0000FFFFU; } pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION); @@ -2737,7 +2789,7 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta } } - /* Enable shared memory support if possible */ + /* Enable shared memory and memfd support if possible */ do_shm = pa_mempool_is_shared(c->protocol->core->mempool) && c->is_local; @@ -2763,8 +2815,24 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta pa_log_debug("Negotiated SHM: %s", pa_yes_no(do_shm)); pa_pstream_enable_shm(c->pstream, do_shm); + do_memfd = + do_shm && pa_mempool_is_memfd_backed(c->protocol->core->mempool); + + shm_type = PA_MEM_TYPE_PRIVATE; + if (do_shm) { + if (c->version >= 31 && memfd_on_remote && do_memfd) { + pa_pstream_enable_memfd(c->pstream); + shm_type = PA_MEM_TYPE_SHARED_MEMFD; + } else + shm_type = PA_MEM_TYPE_SHARED_POSIX; + + pa_log_debug("Memfd possible: %s", pa_yes_no(pa_memfd_is_locally_supported())); + pa_log_debug("Negotiated SHM type: %s", pa_mem_type_to_string(shm_type)); + } + reply = reply_new(tag); - pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION | (do_shm ? 0x80000000 : 0)); + pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION | (do_shm ? 0x80000000 : 0) | + (do_memfd ? 0x40000000 : 0)); #ifdef HAVE_CREDS { @@ -2781,7 +2849,29 @@ static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta pa_pstream_send_tagstruct(c->pstream, reply); #endif - setup_srbchannel(c); + /* The client enables memfd transport on its pstream only after + * inspecting our version flags to see if we support memfds too. + * + * Thus register any pools after sending the server's version + * flags and _never_ before it. */ + if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) { + const char *reason; + + if (pa_pstream_register_memfd_mempool(c->pstream, c->protocol->core->mempool, &reason)) + pa_log("Failed to register memfd mempool. Reason: %s", reason); + } + + setup_srbchannel(c, shm_type); +} + +static void command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_common_command_register_memfd_shmid(c->pstream, pd, c->version, command, t)) + protocol_error(c); } static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { @@ -4447,7 +4537,7 @@ static void command_set_stream_name(pa_pdispatch *pd, uint32_t command, uint32_t CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); - pa_sink_input_set_name(s->sink_input, name); + pa_sink_input_set_property(s->sink_input, PA_PROP_MEDIA_NAME, name); } else { record_stream *s; @@ -4456,7 +4546,7 @@ static void command_set_stream_name(pa_pdispatch *pd, uint32_t command, uint32_t s = pa_idxset_get_by_index(c->record_streams, idx); CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); - pa_source_output_set_name(s->source_output, name); + pa_source_output_set_property(s->source_output, PA_PROP_MEDIA_NAME, name); } pa_pstream_send_simple_ack(c->pstream, tag); @@ -4889,7 +4979,7 @@ static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, /*** pstream callbacks ***/ -static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data, void *userdata) { +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); pa_assert(p); @@ -5125,6 +5215,8 @@ void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_nati c->client->send_event = client_send_event_cb; c->client->userdata = c; + c->rw_mempool = NULL; + c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool); pa_pstream_set_receive_packet_callback(c->pstream, pstream_packet_callback, c); pa_pstream_set_receive_memblock_callback(c->pstream, pstream_memblock_callback, c); diff --git a/src/pulsecore/pstream-util.c b/src/pulsecore/pstream-util.c index e874503..d0d6c66 100644 --- a/src/pulsecore/pstream-util.c +++ b/src/pulsecore/pstream-util.c @@ -21,13 +21,16 @@ #include <config.h> #endif -#include <pulsecore/native-common.h> +#include <pulsecore/core-util.h> #include <pulsecore/macro.h> +#include <pulsecore/native-common.h> +#include <pulsecore/pstream.h> +#include <pulsecore/refcnt.h> #include <pulse/xmalloc.h> #include "pstream-util.h" -static void pa_pstream_send_tagstruct_with_ancil_data(pa_pstream *p, pa_tagstruct *t, const pa_cmsg_ancil_data *ancil_data) { +static void pa_pstream_send_tagstruct_with_ancil_data(pa_pstream *p, pa_tagstruct *t, pa_cmsg_ancil_data *ancil_data) { size_t length; const uint8_t *data; pa_packet *packet; @@ -58,12 +61,21 @@ void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL); } -void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds) { +/* @close_fds: If set then the pstreams code, after invoking a sendmsg(), + * will close all passed fds. + * + * Such fds cannot be closed here as this might lead to freeing them + * before they're actually passed to the other end. The internally-used + * pa_pstream_send_packet() does not do any actual writes and just + * defers write events over the pstream. */ +void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, + bool close_fds) { if (nfd > 0) { pa_cmsg_ancil_data a; a.nfd = nfd; a.creds_valid = false; + a.close_fds_on_cleanup = close_fds; pa_assert(nfd <= MAX_ANCIL_DATA_FDS); memcpy(a.fds, fds, sizeof(int) * nfd); pa_pstream_send_tagstruct_with_ancil_data(p, t, &a); @@ -78,7 +90,8 @@ void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL); } -void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds) { +void PA_GCC_NORETURN pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, + bool close_fds) { pa_assert_not_reached(); } @@ -102,3 +115,84 @@ void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag) { pa_tagstruct_putu32(t, tag); pa_pstream_send_tagstruct(p, t); } + +/* Before sending blocks from a memfd-backed pool over the pipe, we + * must call this method first. + * + * This is needed to transfer memfd blocks without passing their fd + * every time, thus minimizing overhead and avoiding fd leaks. + * + * On registration a packet is sent with the memfd fd as ancil data; + * such packet has an ID that uniquely identifies the pool's memfd + * region. Upon arrival the other end creates a permanent mapping + * between that ID and the passed memfd memory area. + * + * By doing so, we won't need to reference the pool's memfd fd any + * further - just its ID. Both endpoints can then close their fds. */ +int pa_pstream_register_memfd_mempool(pa_pstream *p, pa_mempool *pool, const char **fail_reason) { +#if defined(HAVE_CREDS) && defined(HAVE_MEMFD) + unsigned shm_id; + int memfd_fd, ret = -1; + pa_tagstruct *t; + bool per_client_mempool; + + pa_assert(p); + pa_assert(fail_reason); + + *fail_reason = NULL; + per_client_mempool = pa_mempool_is_per_client(pool); + + pa_pstream_ref(p); + + if (!pa_mempool_is_shared(pool)) { + *fail_reason = "mempool is not shared"; + goto finish; + } + + if (!pa_mempool_is_memfd_backed(pool)) { + *fail_reason = "mempool is not memfd-backed"; + goto finish; + } + + if (pa_mempool_get_shm_id(pool, &shm_id)) { + *fail_reason = "could not extract pool SHM ID"; + goto finish; + } + + if (!pa_pstream_get_memfd(p)) { + *fail_reason = "pipe does not support memfd transport"; + goto finish; + } + + memfd_fd = (per_client_mempool) ? pa_mempool_take_memfd_fd(pool) : + pa_mempool_get_memfd_fd(pool); + + /* Note! For per-client mempools we've taken ownership of the memfd + * fd, and we're thus the sole code path responsible for closing it. + * In case of any failure, it MUST be closed. */ + + if (pa_pstream_attach_memfd_shmid(p, shm_id, memfd_fd)) { + *fail_reason = "could not attach memfd SHM ID to pipe"; + + if (per_client_mempool) + pa_assert_se(pa_close(memfd_fd) == 0); + goto finish; + } + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_REGISTER_MEMFD_SHMID); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, shm_id); + pa_pstream_send_tagstruct_with_fds(p, t, 1, &memfd_fd, per_client_mempool); + + ret = 0; +finish: + pa_pstream_unref(p); + return ret; + +#else + pa_assert(fail_reason); + *fail_reason = "memfd support not compiled in"; + return -1; +#endif +} diff --git a/src/pulsecore/pstream-util.h b/src/pulsecore/pstream-util.h index 1366314..1191d48 100644 --- a/src/pulsecore/pstream-util.h +++ b/src/pulsecore/pstream-util.h @@ -27,11 +27,13 @@ /* The tagstruct is freed!*/ void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds); -void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds); +void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, bool close_fds); #define pa_pstream_send_tagstruct(p, t) pa_pstream_send_tagstruct_with_creds((p), (t), NULL) void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error); void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag); +int pa_pstream_register_memfd_mempool(pa_pstream *p, pa_mempool *pool, const char **fail_reason); + #endif diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c index 98a8382..1ea3c5b 100644 --- a/src/pulsecore/pstream.c +++ b/src/pulsecore/pstream.c @@ -32,6 +32,7 @@ #include <pulse/xmalloc.h> +#include <pulsecore/idxset.h> #include <pulsecore/socket.h> #include <pulsecore/queue.h> #include <pulsecore/log.h> @@ -44,6 +45,7 @@ /* We piggyback information if audio data blocks are stored in SHM on the seek mode */ #define PA_FLAG_SHMDATA 0x80000000LU +#define PA_FLAG_SHMDATA_MEMFD_BLOCK 0x20000000LU #define PA_FLAG_SHMRELEASE 0x40000000LU #define PA_FLAG_SHMREVOKE 0xC0000000LU #define PA_FLAG_SHMMASK 0xFF000000LU @@ -143,7 +145,17 @@ struct pa_pstream { struct pstream_read readio, readsrb; - bool use_shm; + /* @use_shm: beside copying the full audio data to the other + * PA end, this pipe supports just sending references of the + * same audio data blocks if they reside in a SHM pool. + * + * @use_memfd: pipe supports sending SHM memfd block references + * + * @registered_memfd_ids: registered memfd pools SHM IDs. Check + * pa_pstream_register_memfd_mempool() for more information. */ + bool use_shm, use_memfd; + pa_idxset *registered_memfd_ids; + pa_memimport *import; pa_memexport *export; @@ -168,11 +180,33 @@ struct pa_pstream { pa_mempool *mempool; #ifdef HAVE_CREDS - pa_cmsg_ancil_data read_ancil_data, write_ancil_data; + pa_cmsg_ancil_data read_ancil_data, *write_ancil_data; bool send_ancil_data_now; #endif }; +#ifdef HAVE_CREDS +/* Don't close the ancillary fds by your own! Always call this method; + * it guarantees necessary cleanups after fds close.. This method is + * also multiple-invocations safe. */ +void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil) { + if (ancil && ancil->close_fds_on_cleanup) { + int i; + + pa_assert(ancil->nfd <= MAX_ANCIL_DATA_FDS); + + for (i = 0; i < ancil->nfd; i++) + if (ancil->fds[i] != -1) { + pa_assert_se(pa_close(ancil->fds[i]) == 0); + ancil->fds[i] = -1; + } + + ancil->nfd = 0; + ancil->close_fds_on_cleanup = false; + } +} +#endif + static int do_write(pa_pstream *p); static int do_read(pa_pstream *p, struct pstream_read *re); @@ -287,6 +321,35 @@ pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *poo return p; } +/* Attach memfd<->SHM_ID mapping to given pstream and its memimport. + * Check pa_pstream_register_memfd_mempool() for further info. + * + * Caller owns the passed @memfd_fd and must close it down when appropriate. */ +int pa_pstream_attach_memfd_shmid(pa_pstream *p, unsigned shm_id, int memfd_fd) { + int err = -1; + + pa_assert(memfd_fd != -1); + + if (!p->use_memfd) { + pa_log_warn("Received memfd ID registration request over a pipe " + "that does not support memfds"); + return err; + } + + if (pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) { + pa_log_warn("previously registered memfd SHM ID = %u", shm_id); + return err; + } + + if (pa_memimport_attach_memfd(p->import, shm_id, memfd_fd, true)) { + pa_log("Failed to create permanent mapping for memfd region with ID = %u", shm_id); + return err; + } + + pa_assert_se(pa_idxset_put(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL) == 0); + return 0; +} + static void item_free(void *item) { struct item_info *i = item; pa_assert(i); @@ -299,6 +362,15 @@ static void item_free(void *item) { pa_packet_unref(i->packet); } +#ifdef HAVE_CREDS + /* On error recovery paths, there might be lingering items + * on the pstream send queue and they are usually freed with + * a call to 'pa_queue_free(p->send_queue, item_free)'. Make + * sure we do not leak any fds in that case! */ + if (i->with_ancil_data) + pa_cmsg_ancil_data_close_fds(&i->ancil_data); +#endif + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) pa_xfree(i); } @@ -328,18 +400,25 @@ static void pstream_free(pa_pstream *p) { if (p->readio.packet) pa_packet_unref(p->readio.packet); + if (p->registered_memfd_ids) + pa_idxset_free(p->registered_memfd_ids, NULL); + pa_xfree(p); } -void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data) { +void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data) { struct item_info *i; pa_assert(p); pa_assert(PA_REFCNT_VALUE(p) > 0); pa_assert(packet); - if (p->dead) + if (p->dead) { +#ifdef HAVE_CREDS + pa_cmsg_ancil_data_close_fds(ancil_data); +#endif return; + } if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) i = pa_xnew(struct item_info, 1); @@ -536,6 +615,7 @@ static void prepare_next_write_item(pa_pstream *p) { flags = (uint32_t) (p->write.current->seek_mode & PA_FLAG_SEEKMASK); if (p->use_shm) { + pa_mem_type_t type; uint32_t block_id, shm_id; size_t offset, length; uint32_t *shm_info = (uint32_t *) &p->write.minibuf[PA_PSTREAM_DESCRIPTOR_SIZE]; @@ -550,29 +630,50 @@ static void prepare_next_write_item(pa_pstream *p) { if (pa_memexport_put(current_export, p->write.current->chunk.memblock, + &type, &block_id, &shm_id, &offset, &length) >= 0) { - flags |= PA_FLAG_SHMDATA; - if (pa_mempool_is_remote_writable(current_pool)) - flags |= PA_FLAG_SHMWRITABLE; - send_payload = false; + if (type == PA_MEM_TYPE_SHARED_POSIX) + send_payload = false; + + if (type == PA_MEM_TYPE_SHARED_MEMFD && p->use_memfd) { + if (pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) { + flags |= PA_FLAG_SHMDATA_MEMFD_BLOCK; + send_payload = false; + } else { + if (pa_log_ratelimit(PA_LOG_ERROR)) { + pa_log("Cannot send block reference with non-registered memfd ID = %u", shm_id); + pa_log("Fallig back to copying full block data over socket"); + } + } + } + + if (send_payload) { + pa_assert_se(pa_memexport_process_release(current_export, block_id) == 0); + } else { + flags |= PA_FLAG_SHMDATA; + if (pa_mempool_is_remote_writable(current_pool)) + flags |= PA_FLAG_SHMWRITABLE; - shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id); - shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id); - shm_info[PA_PSTREAM_SHM_INDEX] = htonl((uint32_t) (offset + p->write.current->chunk.index)); - shm_info[PA_PSTREAM_SHM_LENGTH] = htonl((uint32_t) p->write.current->chunk.length); + shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id); + shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id); + shm_info[PA_PSTREAM_SHM_INDEX] = htonl((uint32_t) (offset + p->write.current->chunk.index)); + shm_info[PA_PSTREAM_SHM_LENGTH] = htonl((uint32_t) p->write.current->chunk.length); - p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(shm_size); - p->write.minibuf_validsize = PA_PSTREAM_DESCRIPTOR_SIZE + shm_size; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(shm_size); + p->write.minibuf_validsize = PA_PSTREAM_DESCRIPTOR_SIZE + shm_size; + } } /* else */ +/* FIXME: Avoid memexport slot leaks. Call pa_memexport_process_release() */ /* pa_log_warn("Failed to export memory block."); */ if (current_export != p->export) pa_memexport_free(current_export); + pa_mempool_unref(current_pool); } if (send_payload) { @@ -586,7 +687,7 @@ static void prepare_next_write_item(pa_pstream *p) { #ifdef HAVE_CREDS if ((p->send_ancil_data_now = p->write.current->with_ancil_data)) - p->write_ancil_data = p->write.current->ancil_data; + p->write_ancil_data = &p->write.current->ancil_data; #endif } @@ -646,14 +747,16 @@ static int do_write(pa_pstream *p) { #ifdef HAVE_CREDS if (p->send_ancil_data_now) { - if (p->write_ancil_data.creds_valid) { - pa_assert(p->write_ancil_data.nfd == 0); - if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_ancil_data.creds)) < 0) + if (p->write_ancil_data->creds_valid) { + pa_assert(p->write_ancil_data->nfd == 0); + if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_ancil_data->creds)) < 0) goto fail; } else - if ((r = pa_iochannel_write_with_fds(p->io, d, l, p->write_ancil_data.nfd, p->write_ancil_data.fds)) < 0) + if ((r = pa_iochannel_write_with_fds(p->io, d, l, p->write_ancil_data->nfd, p->write_ancil_data->fds)) < 0) goto fail; + + pa_cmsg_ancil_data_close_fds(p->write_ancil_data); p->send_ancil_data_now = false; } else #endif @@ -684,6 +787,10 @@ static int do_write(pa_pstream *p) { return (size_t) r == l ? 1 : 0; fail: +#ifdef HAVE_CREDS + if (p->send_ancil_data_now) + pa_cmsg_ancil_data_close_fds(p->write_ancil_data); +#endif if (release_memblock) pa_memblock_release(release_memblock); @@ -764,6 +871,7 @@ static int do_read(pa_pstream *p, struct pstream_read *re) { pa_assert(b.nfd <= MAX_ANCIL_DATA_FDS); p->read_ancil_data.nfd = b.nfd; memcpy(p->read_ancil_data.fds, b.fds, sizeof(int) * b.nfd); + p->read_ancil_data.close_fds_on_cleanup = b.close_fds_on_cleanup; } } #else @@ -840,7 +948,7 @@ static int do_read(pa_pstream *p, struct pstream_read *re) { return -1; } - if ((flags & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA) { + if (((flags & PA_FLAG_SHMMASK) & PA_FLAG_SHMDATA) != 0) { if (length != sizeof(re->shm_info)) { pa_log_warn("Received SHM memblock frame with invalid frame length."); @@ -883,18 +991,28 @@ static int do_read(pa_pstream *p, struct pstream_read *re) { pa_packet_unref(re->packet); } else { - pa_memblock *b; + pa_memblock *b = NULL; uint32_t flags = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]); - pa_assert((flags & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA); + uint32_t shm_id = ntohl(re->shm_info[PA_PSTREAM_SHM_SHMID]); + pa_mem_type_t type = (flags & PA_FLAG_SHMDATA_MEMFD_BLOCK) ? + PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX; + pa_assert(((flags & PA_FLAG_SHMMASK) & PA_FLAG_SHMDATA) != 0); pa_assert(p->import); - if (!(b = pa_memimport_get(p->import, - ntohl(re->shm_info[PA_PSTREAM_SHM_BLOCKID]), - ntohl(re->shm_info[PA_PSTREAM_SHM_SHMID]), - ntohl(re->shm_info[PA_PSTREAM_SHM_INDEX]), - ntohl(re->shm_info[PA_PSTREAM_SHM_LENGTH]), - !!(flags & PA_FLAG_SHMWRITABLE)))) { + if (type == PA_MEM_TYPE_SHARED_MEMFD && p->use_memfd && + !pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) { + + if (pa_log_ratelimit(PA_LOG_ERROR)) + pa_log("Ignoring received block reference with non-registered memfd ID = %u", shm_id); + + } else if (!(b = pa_memimport_get(p->import, + type, + ntohl(re->shm_info[PA_PSTREAM_SHM_BLOCKID]), + shm_id, + ntohl(re->shm_info[PA_PSTREAM_SHM_INDEX]), + ntohl(re->shm_info[PA_PSTREAM_SHM_LENGTH]), + !!(flags & PA_FLAG_SHMWRITABLE)))) { if (pa_log_ratelimit(PA_LOG_DEBUG)) pa_log_debug("Failed to import memory block."); @@ -937,6 +1055,13 @@ frame_done: re->data = NULL; #ifdef HAVE_CREDS + /* FIXME: Close received ancillary data fds if the pstream's + * receive_packet_callback did not do so. + * + * Malicious clients can attach fds to unknown commands, or attach them + * to commands that does not expect fds. By doing so, server will reach + * its open fd limit and future clients' SHM transfers will always fail. + */ p->read_ancil_data.creds_valid = false; p->read_ancil_data.nfd = 0; #endif @@ -1085,6 +1210,18 @@ void pa_pstream_enable_shm(pa_pstream *p, bool enable) { } } +void pa_pstream_enable_memfd(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->use_shm); + + p->use_memfd = true; + + if (!p->registered_memfd_ids) { + p->registered_memfd_ids = pa_idxset_new(NULL, NULL); + } +} + bool pa_pstream_get_shm(pa_pstream *p) { pa_assert(p); pa_assert(PA_REFCNT_VALUE(p) > 0); @@ -1092,6 +1229,13 @@ bool pa_pstream_get_shm(pa_pstream *p) { return p->use_shm; } +bool pa_pstream_get_memfd(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + return p->use_memfd; +} + void pa_pstream_set_srbchannel(pa_pstream *p, pa_srbchannel *srb) { pa_assert(p); pa_assert(PA_REFCNT_VALUE(p) > 0 || srb == NULL); diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h index f4e1462..2bff270 100644 --- a/src/pulsecore/pstream.h +++ b/src/pulsecore/pstream.h @@ -36,7 +36,7 @@ typedef struct pa_pstream pa_pstream; -typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data, void *userdata); +typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata); typedef void (*pa_pstream_memblock_cb_t)(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata); typedef void (*pa_pstream_notify_cb_t)(pa_pstream *p, void *userdata); typedef void (*pa_pstream_block_id_cb_t)(pa_pstream *p, uint32_t block_id, void *userdata); @@ -48,7 +48,9 @@ void pa_pstream_unref(pa_pstream*p); void pa_pstream_unlink(pa_pstream *p); -void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data); +int pa_pstream_attach_memfd_shmid(pa_pstream *p, unsigned shm_id, int memfd_fd); + +void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data); void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk); void pa_pstream_send_release(pa_pstream *p, uint32_t block_id); void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id); @@ -63,7 +65,9 @@ void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, bool pa_pstream_is_pending(pa_pstream *p); void pa_pstream_enable_shm(pa_pstream *p, bool enable); +void pa_pstream_enable_memfd(pa_pstream *p); bool pa_pstream_get_shm(pa_pstream *p); +bool pa_pstream_get_memfd(pa_pstream *p); /* Enables shared ringbuffer channel. Note that the srbchannel is now owned by the pstream. Setting srb to NULL will free any existing srbchannel. */ diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index d613168..bcf7182 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -45,12 +45,14 @@ #include <pulse/xmalloc.h> #include <pulse/gccmacro.h> +#include <pulsecore/memfd-wrappers.h> #include <pulsecore/core-error.h> #include <pulsecore/log.h> #include <pulsecore/random.h> #include <pulsecore/core-util.h> #include <pulsecore/macro.h> #include <pulsecore/atomic.h> +#include <pulsecore/mem.h> #include "shm.h" @@ -91,7 +93,12 @@ struct shm_marker { uint64_t _reserved4; } PA_GCC_PACKED; -#define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker)) +static inline size_t shm_marker_size(pa_shm *m) { + if (m->type == PA_MEM_TYPE_SHARED_POSIX) + return PA_ALIGN(sizeof(struct shm_marker)); + + return 0; +} #ifdef HAVE_SHM_OPEN static char *segment_name(char *fn, size_t l, unsigned id) { @@ -100,106 +107,153 @@ static char *segment_name(char *fn, size_t l, unsigned id) { } #endif -int pa_shm_create_rw(pa_shm *m, size_t size, bool shared, mode_t mode) { -#ifdef HAVE_SHM_OPEN - char fn[32]; - int fd = -1; -#endif - +static int privatemem_create(pa_shm *m, size_t size) { pa_assert(m); pa_assert(size > 0); - pa_assert(size <= MAX_SHM_SIZE); - pa_assert(!(mode & ~0777)); - pa_assert(mode >= 0600); - - /* Each time we create a new SHM area, let's first drop all stale - * ones */ - pa_shm_cleanup(); - /* Round up to make it page aligned */ - size = PA_PAGE_ALIGN(size); - - if (!shared) { - m->id = 0; - m->size = size; + m->type = PA_MEM_TYPE_PRIVATE; + m->id = 0; + m->size = size; + m->do_unlink = false; + m->fd = -1; #ifdef MAP_ANONYMOUS - if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) { - pa_log("mmap() failed: %s", pa_cstrerror(errno)); - goto fail; - } + if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + return -1; + } #elif defined(HAVE_POSIX_MEMALIGN) - { - int r; + { + int r; - if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) { - pa_log("posix_memalign() failed: %s", pa_cstrerror(r)); - goto fail; - } + if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) { + pa_log("posix_memalign() failed: %s", pa_cstrerror(r)); + return r; } + } #else - m->ptr = pa_xmalloc(m->size); + m->ptr = pa_xmalloc(m->size); #endif - m->do_unlink = false; + return 0; +} - } else { -#ifdef HAVE_SHM_OPEN - struct shm_marker *marker; +static int sharedmem_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) + char fn[32]; + int fd = -1; + struct shm_marker *marker; + bool do_unlink = false; - pa_random(&m->id, sizeof(m->id)); + /* Each time we create a new SHM area, let's first drop all stale + * ones */ + pa_shm_cleanup(); + + pa_random(&m->id, sizeof(m->id)); + + switch (type) { +#ifdef HAVE_SHM_OPEN + case PA_MEM_TYPE_SHARED_POSIX: segment_name(fn, sizeof(fn), m->id); + fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode); + do_unlink = true; + break; +#endif +#ifdef HAVE_MEMFD + case PA_MEM_TYPE_SHARED_MEMFD: + fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING); + break; +#endif + default: + goto fail; + } - if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) { - pa_log("shm_open() failed: %s", pa_cstrerror(errno)); - goto fail; - } + if (fd < 0) { + pa_log("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno)); + goto fail; + } - m->size = size + SHM_MARKER_SIZE; + m->type = type; + m->size = size + shm_marker_size(m); + m->do_unlink = do_unlink; - if (ftruncate(fd, (off_t) m->size) < 0) { - pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); - goto fail; - } + if (ftruncate(fd, (off_t) m->size) < 0) { + pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); + goto fail; + } #ifndef MAP_NORESERVE #define MAP_NORESERVE 0 #endif - if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) { - pa_log("mmap() failed: %s", pa_cstrerror(errno)); - goto fail; - } + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + goto fail; + } + if (type == PA_MEM_TYPE_SHARED_POSIX) { /* We store our PID at the end of the shm block, so that we * can check for dead shm segments later */ - marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE); + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(m)); pa_atomic_store(&marker->pid, (int) getpid()); pa_atomic_store(&marker->marker, SHM_MARKER); + } + /* For memfds, we keep the fd open until we pass it + * to the other PA endpoint over unix domain socket. */ + if (type == PA_MEM_TYPE_SHARED_MEMFD) + m->fd = fd; + else { pa_assert_se(pa_close(fd) == 0); - m->do_unlink = true; -#else - goto fail; -#endif + m->fd = -1; } - m->shared = shared; - return 0; fail: - -#ifdef HAVE_SHM_OPEN if (fd >= 0) { - shm_unlink(fn); +#ifdef HAVE_SHM_OPEN + if (type == PA_MEM_TYPE_SHARED_POSIX) + shm_unlink(fn); +#endif pa_close(fd); } -#endif +#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */ return -1; } +int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { + pa_assert(m); + pa_assert(size > 0); + pa_assert(size <= MAX_SHM_SIZE); + pa_assert(!(mode & ~0777)); + pa_assert(mode >= 0600); + + /* Round up to make it page aligned */ + size = PA_PAGE_ALIGN(size); + + if (type == PA_MEM_TYPE_PRIVATE) + return privatemem_create(m, size); + + return sharedmem_create(m, type, size, mode); +} + +static void privatemem_free(pa_shm *m) { + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); + +#ifdef MAP_ANONYMOUS + if (munmap(m->ptr, m->size) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); +#elif defined(HAVE_POSIX_MEMALIGN) + free(m->ptr); +#else + pa_xfree(m->ptr); +#endif +} + void pa_shm_free(pa_shm *m) { pa_assert(m); pa_assert(m->ptr); @@ -209,34 +263,35 @@ void pa_shm_free(pa_shm *m) { pa_assert(m->ptr != MAP_FAILED); #endif - if (!m->shared) { -#ifdef MAP_ANONYMOUS - if (munmap(m->ptr, m->size) < 0) - pa_log("munmap() failed: %s", pa_cstrerror(errno)); -#elif defined(HAVE_POSIX_MEMALIGN) - free(m->ptr); -#else - pa_xfree(m->ptr); -#endif - } else { -#ifdef HAVE_SHM_OPEN - if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0) - pa_log("munmap() failed: %s", pa_cstrerror(errno)); + if (m->type == PA_MEM_TYPE_PRIVATE) { + privatemem_free(m); + goto finish; + } - if (m->do_unlink) { - char fn[32]; +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) + if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); - segment_name(fn, sizeof(fn), m->id); +#ifdef HAVE_SHM_OPEN + if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) { + char fn[32]; + + segment_name(fn, sizeof(fn), m->id); + if (shm_unlink(fn) < 0) + pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno)); + } +#endif +#ifdef HAVE_MEMFD + if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1) + pa_assert_se(pa_close(m->fd) == 0); +#endif - if (shm_unlink(fn) < 0) - pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno)); - } #else - /* We shouldn't be here without shm support */ - pa_assert_not_reached(); + /* We shouldn't be here without shm or memfd support */ + pa_assert_not_reached(); #endif - } +finish: pa_zero(*m); } @@ -286,9 +341,8 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { #endif } -#ifdef HAVE_SHM_OPEN - -static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) { +static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) { +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) char fn[32]; int fd = -1; int prot; @@ -296,11 +350,25 @@ static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) { pa_assert(m); - segment_name(fn, sizeof(fn), m->id = id); - - if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) { - if ((errno != EACCES && errno != ENOENT) || !for_cleanup) - pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + switch (type) { +#ifdef HAVE_SHM_OPEN + case PA_MEM_TYPE_SHARED_POSIX: + pa_assert(memfd_fd == -1); + segment_name(fn, sizeof(fn), id); + if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) { + if ((errno != EACCES && errno != ENOENT) || !for_cleanup) + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + goto fail; + } + break; +#endif +#ifdef HAVE_MEMFD + case PA_MEM_TYPE_SHARED_MEMFD: + pa_assert(memfd_fd != -1); + fd = memfd_fd; + break; +#endif + default: goto fail; } @@ -310,45 +378,48 @@ static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) { } if (st.st_size <= 0 || - st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) || + st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(m) || PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) { pa_log("Invalid shared memory segment size"); goto fail; } - m->size = (size_t) st.st_size; - prot = writable ? PROT_READ | PROT_WRITE : PROT_READ; - if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { pa_log("mmap() failed: %s", pa_cstrerror(errno)); goto fail; } - m->do_unlink = false; - m->shared = true; + /* In case of attaching to memfd areas, _the caller_ maintains + * ownership of the passed fd and has the sole responsibility + * of closing it down.. For other types, we're the code path + * which created the fd in the first place and we're thus the + * ones responsible for closing it down */ + if (type != PA_MEM_TYPE_SHARED_MEMFD) + pa_assert_se(pa_close(fd) == 0); - pa_assert_se(pa_close(fd) == 0); + m->type = type; + m->id = id; + m->size = (size_t) st.st_size; + m->do_unlink = false; + m->fd = -1; return 0; fail: - if (fd >= 0) + /* In case of memfds, caller maintains fd ownership */ + if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD) pa_close(fd); - return -1; -} - -int pa_shm_attach(pa_shm *m, unsigned id, bool writable) { - return shm_attach(m, id, writable, false); -} +#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */ -#else /* HAVE_SHM_OPEN */ - -int pa_shm_attach(pa_shm *m, unsigned id, bool writable) { return -1; } -#endif /* HAVE_SHM_OPEN */ +/* Caller owns passed @memfd_fd and must close it down when appropriate. */ +int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) { + return shm_attach(m, type, id, memfd_fd, writable, false); +} int pa_shm_cleanup(void) { @@ -379,15 +450,15 @@ int pa_shm_cleanup(void) { if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0) continue; - if (shm_attach(&seg, id, false, true) < 0) + if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0) continue; - if (seg.size < SHM_MARKER_SIZE) { + if (seg.size < shm_marker_size(&seg)) { pa_shm_free(&seg); continue; } - m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE); + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(&seg)); if (pa_atomic_load(&m->marker) != SHM_MARKER) { pa_shm_free(&seg); diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h index d438961..67a2114 100644 --- a/src/pulsecore/shm.h +++ b/src/pulsecore/shm.h @@ -23,17 +23,34 @@ #include <sys/types.h> #include <pulsecore/macro.h> +#include <pulsecore/mem.h> typedef struct pa_shm { + pa_mem_type_t type; unsigned id; void *ptr; size_t size; + + /* Only for type = PA_MEM_TYPE_SHARED_POSIX */ bool do_unlink:1; - bool shared:1; + + /* Only for type = PA_MEM_TYPE_SHARED_MEMFD + * + * To avoid fd leaks, we keep this fd open only until we pass it + * to the other PA endpoint over unix domain socket. + * + * When we don't have ownership for the memfd fd in question (e.g. + * pa_shm_attach()), or the file descriptor has now been closed, + * this is set to -1. + * + * For the special case of a global mempool, we keep this fd + * always open. Check comments on top of pa_mempool_new() for + * rationale. */ + int fd; } pa_shm; -int pa_shm_create_rw(pa_shm *m, size_t size, bool shared, mode_t mode); -int pa_shm_attach(pa_shm *m, unsigned id, bool writable); +int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode); +int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable); void pa_shm_punch(pa_shm *m, size_t offset, size_t size); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 8ec63b5..361b445 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1426,17 +1426,124 @@ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) { pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], i); } +void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value) { + char *old_value = NULL; + const char *new_value; + + pa_assert(i); + pa_assert(key); + + if (pa_proplist_contains(i->proplist, key)) { + old_value = pa_xstrdup(pa_proplist_gets(i->proplist, key)); + if (old_value) { + if (pa_streq(value, old_value)) + goto finish; + } else + old_value = pa_xstrdup("(data)"); + } else { + if (!value) + goto finish; + + old_value = pa_xstrdup("(unset)"); + } + + if (value) { + pa_proplist_sets(i->proplist, key, value); + new_value = value; + } else { + pa_proplist_unset(i->proplist, key); + new_value = "(unset)"; + } + + if (PA_SINK_INPUT_IS_LINKED(i->state)) { + pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value, new_value); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } + +finish: + pa_xfree(old_value); +} + +void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes) { + const uint8_t *old_value; + size_t old_nbytes; + const char *old_value_str; + const char *new_value_str; + + pa_assert(i); + pa_assert(key); + + if (pa_proplist_get(i->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) { + if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes)) + return; + + old_value_str = "(data)"; + + } else { + if (!value) + return; + + old_value_str = "(unset)"; + } + + if (value) { + pa_proplist_set(i->proplist, key, value, nbytes); + new_value_str = "(data)"; + } else { + pa_proplist_unset(i->proplist, key); + new_value_str = "(unset)"; + } + + if (PA_SINK_INPUT_IS_LINKED(i->state)) { + pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value_str, new_value_str); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } +} + /* Called from main thread */ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) { + void *state; + const char *key; + const uint8_t *value; + size_t nbytes; + pa_sink_input_assert_ref(i); + pa_assert(p); pa_assert_ctl_context(); - if (p) - pa_proplist_update(i->proplist, mode, p); + switch (mode) { + case PA_UPDATE_SET: { + /* Delete everything that is not in p. */ + for (state = NULL; (key = pa_proplist_iterate(i->proplist, &state));) { + if (!pa_proplist_contains(p, key)) + pa_sink_input_set_property(i, key, NULL); + } - if (PA_SINK_INPUT_IS_LINKED(i->state)) { - pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); - pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + /* Fall through. */ + } + + case PA_UPDATE_REPLACE: { + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_sink_input_set_property_arbitrary(i, key, value, nbytes); + } + + break; + } + + case PA_UPDATE_MERGE: { + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + if (pa_proplist_contains(i->proplist, key)) + continue; + + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_sink_input_set_property_arbitrary(i, key, value, nbytes); + } + + break; + } } } @@ -1468,31 +1575,6 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { } /* Called from main context */ -void pa_sink_input_set_name(pa_sink_input *i, const char *name) { - const char *old; - pa_sink_input_assert_ref(i); - pa_assert_ctl_context(); - - if (!name && !pa_proplist_contains(i->proplist, PA_PROP_MEDIA_NAME)) - return; - - old = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME); - - if (old && name && pa_streq(old, name)) - return; - - if (name) - pa_proplist_sets(i->proplist, PA_PROP_MEDIA_NAME, name); - else - pa_proplist_unset(i->proplist, PA_PROP_MEDIA_NAME); - - if (PA_SINK_INPUT_IS_LINKED(i->state)) { - pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); - pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); - } -} - -/* Called from main context */ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_ctl_context(); @@ -1538,6 +1620,9 @@ bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { if (dest == i->sink) return true; + if (dest->unlink_requested) + return false; + if (!pa_sink_input_may_move(i)) return false; diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 86deab2..8bbee4e 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -335,8 +335,6 @@ int pa_sink_input_new( void pa_sink_input_put(pa_sink_input *i); void pa_sink_input_unlink(pa_sink_input* i); -void pa_sink_input_set_name(pa_sink_input *i, const char *name); - pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec); /* Request that the specified number of bytes already written out to @@ -373,6 +371,8 @@ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save); +void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value); +void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes); void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p); pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 0b44fc7..3f1ef72 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -676,9 +676,10 @@ void pa_sink_unlink(pa_sink* s) { * reversing pa_sink_put(). It also undoes the registrations * already done in pa_sink_new()! */ - /* All operations here shall be idempotent, i.e. pa_sink_unlink() - * may be called multiple times on the same sink without bad - * effects. */ + if (s->unlink_requested) + return; + + s->unlink_requested = true; linked = PA_SINK_IS_LINKED(s->state); diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 5df109e..b64a666 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -63,6 +63,12 @@ struct pa_sink { pa_core *core; pa_sink_state_t state; + + /* Set in the beginning of pa_sink_unlink() before setting the sink state + * to UNLINKED. The purpose is to prevent moving streams to a sink that is + * about to be removed. */ + bool unlink_requested; + pa_sink_flags_t flags; pa_suspend_cause_t suspend_cause; diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index c73c548..d74a60e 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -792,14 +792,14 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->thread_info.soft_volume); } - if (!o->thread_info.resampler) { - if (nvfs) { - pa_memchunk_make_writable(&qchunk, 0); - pa_volume_memchunk(&qchunk, &o->thread_info.sample_spec, &o->volume_factor_source); - } + if (nvfs) { + pa_memchunk_make_writable(&qchunk, 0); + pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->volume_factor_source); + } + if (!o->thread_info.resampler) o->push(o, &qchunk); - } else { + else { pa_memchunk rchunk; if (mbs == 0) @@ -810,14 +810,8 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk); - if (rchunk.length > 0) { - if (nvfs) { - pa_memchunk_make_writable(&rchunk, 0); - pa_volume_memchunk(&rchunk, &o->thread_info.sample_spec, &o->volume_factor_source); - } - + if (rchunk.length > 0) o->push(o, &rchunk); - } if (rchunk.memblock) pa_memblock_unref(rchunk.memblock); @@ -1084,17 +1078,124 @@ void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) { pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], o); } +void pa_source_output_set_property(pa_source_output *o, const char *key, const char *value) { + char *old_value = NULL; + const char *new_value; + + pa_assert(o); + pa_assert(key); + + if (pa_proplist_contains(o->proplist, key)) { + old_value = pa_xstrdup(pa_proplist_gets(o->proplist, key)); + if (old_value) { + if (pa_streq(value, old_value)) + goto finish; + } else + old_value = pa_xstrdup("(data)"); + } else { + if (!value) + goto finish; + + old_value = pa_xstrdup("(unset)"); + } + + if (value) { + pa_proplist_sets(o->proplist, key, value); + new_value = value; + } else { + pa_proplist_unset(o->proplist, key); + new_value = "(unset)"; + } + + if (PA_SINK_INPUT_IS_LINKED(o->state)) { + pa_log_debug("Source output %u: proplist[%s]: %s -> %s", o->index, key, old_value, new_value); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } + +finish: + pa_xfree(old_value); +} + +void pa_source_output_set_property_arbitrary(pa_source_output *o, const char *key, const uint8_t *value, size_t nbytes) { + const uint8_t *old_value; + size_t old_nbytes; + const char *old_value_str; + const char *new_value_str; + + pa_assert(o); + pa_assert(key); + + if (pa_proplist_get(o->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) { + if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes)) + return; + + old_value_str = "(data)"; + + } else { + if (!value) + return; + + old_value_str = "(unset)"; + } + + if (value) { + pa_proplist_set(o->proplist, key, value, nbytes); + new_value_str = "(data)"; + } else { + pa_proplist_unset(o->proplist, key); + new_value_str = "(unset)"; + } + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { + pa_log_debug("Source output %u: proplist[%s]: %s -> %s", o->index, key, old_value_str, new_value_str); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } +} + /* Called from main thread */ void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) { + void *state; + const char *key; + const uint8_t *value; + size_t nbytes; + pa_source_output_assert_ref(o); + pa_assert(p); pa_assert_ctl_context(); - if (p) - pa_proplist_update(o->proplist, mode, p); + switch (mode) { + case PA_UPDATE_SET: { + /* Delete everything that is not in p. */ + for (state = NULL; (key = pa_proplist_iterate(o->proplist, &state));) { + if (!pa_proplist_contains(p, key)) + pa_source_output_set_property(o, key, NULL); + } + + /* Fall through. */ + } - if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { - pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); - pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + case PA_UPDATE_REPLACE: { + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_source_output_set_property_arbitrary(o, key, value, nbytes); + } + + break; + } + + case PA_UPDATE_MERGE: { + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + if (pa_proplist_contains(o->proplist, key)) + continue; + + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_source_output_set_property_arbitrary(o, key, value, nbytes); + } + + break; + } } } @@ -1126,31 +1227,6 @@ int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) { } /* Called from main context */ -void pa_source_output_set_name(pa_source_output *o, const char *name) { - const char *old; - pa_assert_ctl_context(); - pa_source_output_assert_ref(o); - - if (!name && !pa_proplist_contains(o->proplist, PA_PROP_MEDIA_NAME)) - return; - - old = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME); - - if (old && name && pa_streq(old, name)) - return; - - if (name) - pa_proplist_sets(o->proplist, PA_PROP_MEDIA_NAME, name); - else - pa_proplist_unset(o->proplist, PA_PROP_MEDIA_NAME); - - if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { - pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); - pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); - } -} - -/* Called from main context */ pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) { pa_source_output_assert_ref(o); pa_assert_ctl_context(); @@ -1193,6 +1269,9 @@ bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest) { if (dest == o->source) return true; + if (dest->unlink_requested) + return false; + if (!pa_source_output_may_move(o)) return false; @@ -1250,6 +1329,9 @@ int pa_source_output_start_move(pa_source_output *o) { pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0); pa_source_update_status(o->source); + + pa_cvolume_remap(&o->volume_factor_source, &o->source->channel_map, &o->channel_map); + o->source = NULL; pa_source_output_unref(o); diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index 26be484..7ac44bd 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -291,8 +291,6 @@ int pa_source_output_new( void pa_source_output_put(pa_source_output *o); void pa_source_output_unlink(pa_source_output*o); -void pa_source_output_set_name(pa_source_output *o, const char *name); - pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec); void pa_source_output_cork(pa_source_output *o, bool b); @@ -316,6 +314,8 @@ pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save); +void pa_source_output_set_property(pa_source_output *o, const char *key, const char *value); +void pa_source_output_set_property_arbitrary(pa_source_output *o, const char *key, const uint8_t *value, size_t nbytes); void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p); pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o); diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index f4b96ab..98374ae 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -618,6 +618,11 @@ void pa_source_unlink(pa_source *s) { /* See pa_sink_unlink() for a couple of comments how this function * works. */ + if (s->unlink_requested) + return; + + s->unlink_requested = true; + linked = PA_SOURCE_IS_LINKED(s->state); if (linked) diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 19fb41b..91e8674 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -64,6 +64,12 @@ struct pa_source { pa_core *core; pa_source_state_t state; + + /* Set in the beginning of pa_source_unlink() before setting the source + * state to UNLINKED. The purpose is to prevent moving streams to a source + * that is about to be removed. */ + bool unlink_requested; + pa_source_flags_t flags; pa_suspend_cause_t suspend_cause; diff --git a/src/tests/connect-stress.c b/src/tests/connect-stress.c index 7c755e9..a243df9 100644 --- a/src/tests/connect-stress.c +++ b/src/tests/connect-stress.c @@ -63,7 +63,8 @@ static const pa_sample_spec sample_spec = { static void context_state_callback(pa_context *c, void *userdata); -static void connect(const char *name, int *try) { +/* Note: don't conflict with connect(2) declaration */ +static void _connect(const char *name, int *try) { int ret; pa_mainloop_api *api; @@ -80,14 +81,14 @@ static void connect(const char *name, int *try) { /* Connect the context */ if (pa_context_connect(context, NULL, 0, NULL) < 0) { fprintf(stderr, "pa_context_connect() failed.\n"); - fail(); + ck_abort(); } ret = pa_threaded_mainloop_start(mainloop); fail_unless(ret == 0); } -static void disconnect(void) { +static void _disconnect(void) { int i; fail_unless(mainloop != NULL); @@ -144,7 +145,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { default: case PA_STREAM_FAILED: fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - fail(); + ck_abort(); } } @@ -190,7 +191,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); - fail(); + ck_abort(); } } @@ -201,9 +202,9 @@ START_TEST (connect_stress_test) { streams[i] = NULL; for (i = 0; i < NTESTS; i++) { - connect(bname, &i); + _connect(bname, &i); usleep(rand() % 500000); - disconnect(); + _disconnect(); usleep(rand() % 500000); } diff --git a/src/tests/cpu-mix-test.c b/src/tests/cpu-mix-test.c index f3bc0cc..a3189b7 100644 --- a/src/tests/cpu-mix-test.c +++ b/src/tests/cpu-mix-test.c @@ -76,7 +76,7 @@ static void run_mix_test( samples_ref = out_ref + (8 - align); nsamples = channels * (SAMPLES - (8 - align)); - fail_unless((pool = pa_mempool_new(false, 0)) != NULL, NULL); + fail_unless((pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)) != NULL, NULL); pa_random(samples0, nsamples * sizeof(int16_t)); c0.memblock = pa_memblock_new_fixed(pool, samples0, nsamples * sizeof(int16_t), false); @@ -118,7 +118,7 @@ static void run_mix_test( i, samples[i], samples_ref[i], samples0[i], samples1[i]); - fail(); + ck_abort(); } } } @@ -142,7 +142,7 @@ static void run_mix_test( pa_memblock_unref(c0.memblock); pa_memblock_unref(c1.memblock); - pa_mempool_free(pool); + pa_mempool_unref(pool); } START_TEST (mix_special_test) { diff --git a/src/tests/cpu-remap-test.c b/src/tests/cpu-remap-test.c index cfe8647..7af5e29 100644 --- a/src/tests/cpu-remap-test.c +++ b/src/tests/cpu-remap-test.c @@ -70,7 +70,7 @@ static void run_remap_test_float( pa_log_debug("Correctness test failed: align=%d", align); pa_log_debug("%d: %.24f != %.24f\n", i, out[i], out_ref[i]); - fail(); + ck_abort(); } } } @@ -123,7 +123,7 @@ static void run_remap_test_s16( if (abs(out[i] - out_ref[i]) > 3) { pa_log_debug("Correctness test failed: align=%d", align); pa_log_debug("%d: %d != %d\n", i, out[i], out_ref[i]); - fail(); + ck_abort(); } } } diff --git a/src/tests/cpu-sconv-test.c b/src/tests/cpu-sconv-test.c index 2eb51de..3f189d1 100644 --- a/src/tests/cpu-sconv-test.c +++ b/src/tests/cpu-sconv-test.c @@ -65,7 +65,7 @@ static void run_conv_test_float_to_s16( if (abs(samples[i] - samples_ref[i]) > 1) { pa_log_debug("Correctness test failed: align=%d", align); pa_log_debug("%d: %04hx != %04hx (%.24f)\n", i, samples[i], samples_ref[i], floats[i]); - fail(); + ck_abort(); } } } @@ -115,7 +115,7 @@ static void run_conv_test_s16_to_float( if (fabsf(floats[i] - floats_ref[i]) > 0.0001f) { pa_log_debug("Correctness test failed: align=%d", align); pa_log_debug("%d: %.24f != %.24f (%d)\n", i, floats[i], floats_ref[i], samples[i]); - fail(); + ck_abort(); } } } diff --git a/src/tests/cpu-volume-test.c b/src/tests/cpu-volume-test.c index 01ac2fc..15f0658 100644 --- a/src/tests/cpu-volume-test.c +++ b/src/tests/cpu-volume-test.c @@ -78,7 +78,7 @@ static void run_volume_test( pa_log_debug("Correctness test failed: align=%d, channels=%d", align, channels); pa_log_debug("%d: %04hx != %04hx (%04hx * %08x)\n", i, samples[i], samples_ref[i], samples_orig[i], volumes[i % channels]); - fail(); + ck_abort(); } } } diff --git a/src/tests/cpulimit-test.c b/src/tests/cpulimit-test.c index 3a8124f..e01a5b8 100644 --- a/src/tests/cpulimit-test.c +++ b/src/tests/cpulimit-test.c @@ -48,7 +48,7 @@ static void func(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata if ((now - start) >= 30) { m->quit(m, 1); fprintf(stderr, "Test failed\n"); - fail(); + ck_abort(); } else raise(SIGUSR1); } @@ -78,7 +78,7 @@ START_TEST (cpulimit_test) { if ((now - start) >= 30) { fprintf(stderr, "Test failed\n"); - fail(); + ck_abort(); break; } } diff --git a/src/tests/extended-test.c b/src/tests/extended-test.c index ee766b8..0d08fac 100644 --- a/src/tests/extended-test.c +++ b/src/tests/extended-test.c @@ -100,7 +100,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { default: case PA_STREAM_FAILED: fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - fail(); + ck_abort(); } } @@ -151,7 +151,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); - fail(); + ck_abort(); } } diff --git a/src/tests/get-binary-name-test.c b/src/tests/get-binary-name-test.c index 4c855bb..cd53bde 100644 --- a/src/tests/get-binary-name-test.c +++ b/src/tests/get-binary-name-test.c @@ -39,7 +39,7 @@ START_TEST (getbinaryname_test) { if (!pa_get_binary_name(exename, allocated)) { pa_log_error("failed to read binary name"); pa_xfree(exename); - fail(); + ck_abort(); } if (strlen(exename) < allocated - 1) { diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c index 7a7bffc..b4cf18c 100644 --- a/src/tests/interpol-test.c +++ b/src/tests/interpol-test.c @@ -144,7 +144,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c))); - fail(); + ck_abort(); } } diff --git a/src/tests/lfe-filter-test.c b/src/tests/lfe-filter-test.c index 389a2b9..ba63895 100644 --- a/src/tests/lfe-filter-test.c +++ b/src/tests/lfe-filter-test.c @@ -136,7 +136,7 @@ START_TEST (lfe_filter_test) { a.format = PA_SAMPLE_S16NE; lft.ss = &a; - pa_assert_se(lft.pool = pa_mempool_new(false, 0)); + pa_assert_se(lft.pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)); /* We prepare pseudo-random input audio samples for lfe-filter rewind testing*/ ori_sample_ptr = pa_xmalloc(pa_frame_size(lft.ss) * TOTAL_SAMPLES); @@ -163,7 +163,7 @@ START_TEST (lfe_filter_test) { pa_lfe_filter_free(lft.lf); - pa_mempool_free(lft.pool); + pa_mempool_unref(lft.pool); if (!ret) pa_log_debug("lfe-filter-test: tests for both rewind to block boundary and rewind to middle position of a block passed!"); diff --git a/src/tests/mcalign-test.c b/src/tests/mcalign-test.c index 0d27dfd..3127ccf 100644 --- a/src/tests/mcalign-test.c +++ b/src/tests/mcalign-test.c @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { pa_mcalign *a; pa_memchunk c; - p = pa_mempool_new(false, 0); + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); a = pa_mcalign_new(11); @@ -100,7 +100,7 @@ int main(int argc, char *argv[]) { if (c.memblock) pa_memblock_unref(c.memblock); - pa_mempool_free(p); + pa_mempool_unref(p); return 0; } diff --git a/src/tests/memblock-test.c b/src/tests/memblock-test.c index 2b51108..d48349f 100644 --- a/src/tests/memblock-test.c +++ b/src/tests/memblock-test.c @@ -74,17 +74,18 @@ START_TEST (memblock_test) { pa_memblock *mb_a, *mb_b, *mb_c; int r, i; pa_memblock* blocks[5]; + pa_mem_type_t mem_type; uint32_t id, shm_id; size_t offset, size; char *x; const char txt[] = "This is a test!"; - pool_a = pa_mempool_new(true, 0); + pool_a = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true); fail_unless(pool_a != NULL); - pool_b = pa_mempool_new(true, 0); + pool_b = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true); fail_unless(pool_b != NULL); - pool_c = pa_mempool_new(true, 0); + pool_c = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true); fail_unless(pool_c != NULL); pa_mempool_get_shm_id(pool_a, &id_a); @@ -122,22 +123,22 @@ START_TEST (memblock_test) { import_c = pa_memimport_new(pool_c, release_cb, (void*) "C"); fail_unless(import_b != NULL); - r = pa_memexport_put(export_a, mb_a, &id, &shm_id, &offset, &size); + r = pa_memexport_put(export_a, mb_a, &mem_type, &id, &shm_id, &offset, &size); fail_unless(r >= 0); fail_unless(shm_id == id_a); pa_log("A: Memory block exported as %u", id); - mb_b = pa_memimport_get(import_b, id, shm_id, offset, size, false); + mb_b = pa_memimport_get(import_b, PA_MEM_TYPE_SHARED_POSIX, id, shm_id, offset, size, false); fail_unless(mb_b != NULL); - r = pa_memexport_put(export_b, mb_b, &id, &shm_id, &offset, &size); + r = pa_memexport_put(export_b, mb_b, &mem_type, &id, &shm_id, &offset, &size); fail_unless(r >= 0); fail_unless(shm_id == id_a || shm_id == id_b); pa_memblock_unref(mb_b); pa_log("B: Memory block exported as %u", id); - mb_c = pa_memimport_get(import_c, id, shm_id, offset, size, false); + mb_c = pa_memimport_get(import_c, PA_MEM_TYPE_SHARED_POSIX, id, shm_id, offset, size, false); fail_unless(mb_c != NULL); x = pa_memblock_acquire(mb_c); pa_log_debug("1 data=%s", x); @@ -169,9 +170,9 @@ START_TEST (memblock_test) { pa_log("vacuuming done..."); - pa_mempool_free(pool_a); - pa_mempool_free(pool_b); - pa_mempool_free(pool_c); + pa_mempool_unref(pool_a); + pa_mempool_unref(pool_b); + pa_mempool_unref(pool_c); } END_TEST diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c index eea6cfa..2404ee2 100644 --- a/src/tests/memblockq-test.c +++ b/src/tests/memblockq-test.c @@ -108,7 +108,7 @@ START_TEST (memblockq_test) { pa_log_set_level(PA_LOG_DEBUG); - p = pa_mempool_new(false, 0); + p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true); silence.memblock = pa_memblock_new_fixed(p, (char*) "__", 2, 1); fail_unless(silence.memblock != NULL); @@ -208,7 +208,7 @@ START_TEST (memblockq_test) { pa_memblock_unref(chunk3.memblock); pa_memblock_unref(chunk4.memblock); - pa_mempool_free(p); + pa_mempool_unref(p); } END_TEST diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c index c8af600..972f5d7 100644 --- a/src/tests/mix-test.c +++ b/src/tests/mix-test.c @@ -286,7 +286,7 @@ START_TEST (mix_test) { if (!getenv("MAKE_CHECK")) pa_log_set_level(PA_LOG_DEBUG); - fail_unless((pool = pa_mempool_new(false, 0)) != NULL, NULL); + fail_unless((pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)) != NULL, NULL); a.channels = 1; a.rate = 44100; @@ -338,7 +338,7 @@ START_TEST (mix_test) { pa_memblock_unref(k.memblock); } - pa_mempool_free(pool); + pa_mempool_unref(pool); } END_TEST diff --git a/src/tests/mult-s16-test.c b/src/tests/mult-s16-test.c index 7340d85..91740c2 100644 --- a/src/tests/mult-s16-test.c +++ b/src/tests/mult-s16-test.c @@ -65,7 +65,7 @@ START_TEST (mult_s16_test) { if (a != b) { pa_log_debug("%d: %d != %d", i, a, b); - fail(); + ck_abort(); } } diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c index 6feb8dc..e21c109 100644 --- a/src/tests/remix-test.c +++ b/src/tests/remix-test.c @@ -51,7 +51,7 @@ int main(int argc, char *argv[]) { pa_log_set_level(PA_LOG_DEBUG); - pa_assert_se(pool = pa_mempool_new(false, 0)); + pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)); for (i = 0; maps[i].channels > 0; i++) for (j = 0; maps[j].channels > 0; j++) { @@ -75,7 +75,7 @@ int main(int argc, char *argv[]) { pa_resampler_free(r); } - pa_mempool_free(pool); + pa_mempool_unref(pool); return 0; } diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c index 9832a31..40000d5 100644 --- a/src/tests/resampler-test.c +++ b/src/tests/resampler-test.c @@ -252,26 +252,26 @@ static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) { } static void help(const char *argv0) { - printf(_("%s [options]\n\n" - "-h, --help Show this help\n" - "-v, --verbose Print debug messages\n" - " --from-rate=SAMPLERATE From sample rate in Hz (defaults to 44100)\n" - " --from-format=SAMPLEFORMAT From sample type (defaults to s16le)\n" - " --from-channels=CHANNELS From number of channels (defaults to 1)\n" - " --to-rate=SAMPLERATE To sample rate in Hz (defaults to 44100)\n" - " --to-format=SAMPLEFORMAT To sample type (defaults to s16le)\n" - " --to-channels=CHANNELS To number of channels (defaults to 1)\n" - " --resample-method=METHOD Resample method (defaults to auto)\n" - " --seconds=SECONDS From stream duration (defaults to 60)\n" - "\n" - "If the formats are not specified, the test performs all formats combinations,\n" - "back and forth.\n" - "\n" - "Sample type must be one of s16le, s16be, u8, float32le, float32be, ulaw, alaw,\n" - "s24le, s24be, s24-32le, s24-32be, s32le, s32be (defaults to s16ne)\n" - "\n" - "See --dump-resample-methods for possible values of resample methods.\n"), - argv0); + printf("%s [options]\n\n" + "-h, --help Show this help\n" + "-v, --verbose Print debug messages\n" + " --from-rate=SAMPLERATE From sample rate in Hz (defaults to 44100)\n" + " --from-format=SAMPLEFORMAT From sample type (defaults to s16le)\n" + " --from-channels=CHANNELS From number of channels (defaults to 1)\n" + " --to-rate=SAMPLERATE To sample rate in Hz (defaults to 44100)\n" + " --to-format=SAMPLEFORMAT To sample type (defaults to s16le)\n" + " --to-channels=CHANNELS To number of channels (defaults to 1)\n" + " --resample-method=METHOD Resample method (defaults to auto)\n" + " --seconds=SECONDS From stream duration (defaults to 60)\n" + "\n" + "If the formats are not specified, the test performs all formats combinations,\n" + "back and forth.\n" + "\n" + "Sample type must be one of s16le, s16be, u8, float32le, float32be, ulaw, alaw,\n" + "s24le, s24be, s24-32le, s24-32be, s32le, s32be (defaults to s16ne)\n" + "\n" + "See --dump-resample-methods for possible values of resample methods.\n", + argv0); } enum { @@ -350,7 +350,7 @@ int main(int argc, char *argv[]) { break; case ARG_VERSION: - printf(_("%s %s\n"), argv[0], PACKAGE_VERSION); + printf("%s %s\n", argv[0], PACKAGE_VERSION); ret = 0; goto quit; @@ -404,7 +404,7 @@ int main(int argc, char *argv[]) { } ret = 0; - pa_assert_se(pool = pa_mempool_new(false, 0)); + pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)); if (!all_formats) { @@ -473,7 +473,7 @@ int main(int argc, char *argv[]) { quit: if (pool) - pa_mempool_free(pool); + pa_mempool_unref(pool); return ret; } diff --git a/src/tests/srbchannel-test.c b/src/tests/srbchannel-test.c index cd4d397..0e7b0ce 100644 --- a/src/tests/srbchannel-test.c +++ b/src/tests/srbchannel-test.c @@ -34,7 +34,7 @@ static unsigned packets_received; static unsigned packets_checksum; static size_t packets_length; -static void packet_received(pa_pstream *p, pa_packet *packet, const pa_cmsg_ancil_data *ancil_data, void *userdata) { +static void packet_received(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { const uint8_t *pdata; size_t plen; unsigned i; @@ -85,7 +85,7 @@ START_TEST (srbchannel_test) { int pipefd[4]; pa_mainloop *ml = pa_mainloop_new(); - pa_mempool *mp = pa_mempool_new(true, 0); + pa_mempool *mp = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true); pa_iochannel *io1, *io2; pa_pstream *p1, *p2; pa_srbchannel *sr1, *sr2; @@ -116,7 +116,7 @@ START_TEST (srbchannel_test) { pa_pstream_unref(p1); pa_pstream_unref(p2); - pa_mempool_free(mp); + pa_mempool_unref(mp); pa_mainloop_free(ml); } END_TEST diff --git a/src/tests/sync-playback.c b/src/tests/sync-playback.c index f06b459..9ef038c 100644 --- a/src/tests/sync-playback.c +++ b/src/tests/sync-playback.c @@ -106,7 +106,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { default: case PA_STREAM_FAILED: fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - fail(); + ck_abort(); } } @@ -148,7 +148,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); - fail(); + ck_abort(); } } diff --git a/src/utils/padsp.c b/src/utils/padsp.c index 5e336bb..943479b 100644 --- a/src/utils/padsp.c +++ b/src/utils/padsp.c @@ -2278,6 +2278,7 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) break; } +#if HAVE_DECL_SOUND_PCM_READ_RATE case SOUND_PCM_READ_RATE: debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_RATE\n"); @@ -2285,7 +2286,9 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) *(int*) argp = i->sample_spec.rate; pa_threaded_mainloop_unlock(i->mainloop); break; +#endif +#if HAVE_DECL_SOUND_PCM_READ_CHANNELS case SOUND_PCM_READ_CHANNELS: debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_CHANNELS\n"); @@ -2293,7 +2296,9 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) *(int*) argp = i->sample_spec.channels; pa_threaded_mainloop_unlock(i->mainloop); break; +#endif +#if HAVE_DECL_SOUND_PCM_READ_BITS case SOUND_PCM_READ_BITS: debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_BITS\n"); @@ -2301,6 +2306,7 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) *(int*) argp = pa_sample_size(&i->sample_spec)*8; pa_threaded_mainloop_unlock(i->mainloop); break; +#endif case SNDCTL_DSP_GETOPTR: { count_info *info; |