diff options
Diffstat (limited to 'src')
252 files changed, 27112 insertions, 3703 deletions
diff --git a/src/.gitignore b/src/.gitignore index 82331524..64ab2973 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,4 @@ +usergroup-test sigbus-test TAGS alsa-time-test @@ -62,4 +63,5 @@ thread-test utf8-test voltest start-pulseaudio-x11 +start-pulseaudio-kde vector-test diff --git a/src/Makefile.am b/src/Makefile.am index c022fa7c..f0d5a5e8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -32,6 +32,7 @@ xdgautostartdir=$(sysconfdir)/xdg/autostart alsaprofilesetsdir=$(datadir)/pulseaudio/alsa-mixer/profile-sets alsapathsdir=$(datadir)/pulseaudio/alsa-mixer/paths udevrulesdir=/lib/udev/rules.d +dbuspolicydir=$(sysconfdir)/dbus-1/system.d ################################### # Defines # @@ -69,10 +70,13 @@ AM_CFLAGS = \ -I$(top_builddir)/src/modules/x11 \ -I$(top_srcdir)/src/modules/jack \ -I$(top_builddir)/src/modules/jack \ + -I$(top_srcdir)/src/modules/dbus \ + -I$(top_builddir)/src/modules/dbus \ $(PTHREAD_CFLAGS) -D_POSIX_PTHREAD_SEMANTICS \ $(LIBSAMPLERATE_CFLAGS) \ $(LIBSNDFILE_CFLAGS) \ $(LIBSPEEX_CFLAGS) \ + -DPA_BUILDDIR=\"$(abs_builddir)\" \ -DPA_DLSEARCHPATH=\"$(modlibexecdir)\" \ -DPA_DEFAULT_CONFIG_DIR=\"$(PA_DEFAULT_CONFIG_DIR)\" \ -DPA_BINARY=\"$(PA_BINARY)\" \ @@ -82,8 +86,8 @@ AM_CFLAGS = \ -DAO_REQUIRE_CAS \ -DPULSE_LOCALEDIR=\"$(pulselocaledir)\" \ -DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" \ - -DPA_ALSA_PATHS_DIR=\"$(alsapathsdir)\" \ - -DPA_ALSA_PROFILE_SETS_DIR=\"$(alsaprofilesetsdir)\" + -DPA_ALSA_PATHS_DIR=\"$(alsapathsdir)\" \ + -DPA_ALSA_PROFILE_SETS_DIR=\"$(alsaprofilesetsdir)\" AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) @@ -115,10 +119,13 @@ EXTRA_DIST = \ depmod.py \ daemon/esdcompat.in \ daemon/start-pulseaudio-x11.in \ + daemon/start-pulseaudio-kde.in \ utils/padsp \ modules/module-defs.h.m4 \ daemon/pulseaudio.desktop.in \ + daemon/pulseaudio-kde.desktop.in \ map-file \ + daemon/pulseaudio-system.conf \ modules/alsa/mixer/profile-sets/default.conf \ modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \ modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf \ @@ -134,8 +141,10 @@ EXTRA_DIST = \ modules/alsa/mixer/paths/analog-input-tvtuner.conf \ modules/alsa/mixer/paths/analog-input-video.conf \ modules/alsa/mixer/paths/analog-output.conf \ + modules/alsa/mixer/paths/analog-output-speaker.conf \ modules/alsa/mixer/paths/analog-output.conf.common \ modules/alsa/mixer/paths/analog-output-headphones.conf \ + modules/alsa/mixer/paths/analog-output-headphones-2.conf \ modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ modules/alsa/mixer/paths/analog-output-mono.conf @@ -145,9 +154,13 @@ pulseconf_DATA = \ daemon.conf \ client.conf +dbuspolicy_DATA = \ + daemon/pulseaudio-system.conf + if HAVE_X11 xdgautostart_in_files = \ - daemon/pulseaudio.desktop.in + daemon/pulseaudio.desktop.in \ + daemon/pulseaudio-kde.desktop.in endif xdgautostart_DATA = $(xdgautostart_in_files:.desktop.in=.desktop) @INTLTOOL_DESKTOP_RULE@ @@ -163,7 +176,7 @@ BUILT_SOURCES = \ bin_PROGRAMS = pulseaudio pulseaudio_SOURCES = \ - daemon/caps.h daemon/caps.c \ + 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 \ @@ -171,11 +184,17 @@ pulseaudio_SOURCES = \ daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \ daemon/main.c -pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(LIBOIL_CFLAGS) $(DBUS_CFLAGS) -pulseaudio_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS) $(DBUS_LIBS) +pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) +pulseaudio_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) # This is needed because automake doesn't properly expand the foreach below pulseaudio_DEPENDENCIES = libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(PREOPEN_LIBS) +if HAVE_DBUS +pulseaudio_CFLAGS += $(DBUS_CFLAGS) +pulseaudio_SOURCES += daemon/server-lookup.c daemon/server-lookup.h +pulseaudio_LDADD += $(DBUS_LIBS) +endif + if PREOPEN_MODS PREOPEN_LIBS = $(PREOPEN_MODS) else @@ -209,7 +228,7 @@ if HAVE_AVAHI bin_PROGRAMS += pabrowse endif -bin_SCRIPTS = esdcompat start-pulseaudio-x11 +bin_SCRIPTS = esdcompat start-pulseaudio-x11 start-pulseaudio-kde pacat_SOURCES = utils/pacat.c pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) @@ -274,7 +293,8 @@ TESTS = \ proplist-test \ lock-autospawn-test \ prioq-test \ - sigbus-test + sigbus-test \ + usergroup-test TESTS_BINARIES = \ mainloop-test \ @@ -312,7 +332,8 @@ TESTS_BINARIES = \ stripnul \ lock-autospawn-test \ prioq-test \ - sigbus-test + sigbus-test \ + usergroup-test if HAVE_SIGXCPU #TESTS += \ @@ -488,18 +509,18 @@ sig2str_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) resampler_test_SOURCES = tests/resampler-test.c resampler_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -resampler_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -resampler_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +resampler_test_CFLAGS = $(AM_CFLAGS) +resampler_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) mix_test_SOURCES = tests/mix-test.c mix_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -mix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +mix_test_CFLAGS = $(AM_CFLAGS) +mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) remix_test_SOURCES = tests/remix-test.c remix_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -remix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +remix_test_CFLAGS = $(AM_CFLAGS) +remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) smoother_test_SOURCES = tests/smoother-test.c smoother_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la @@ -508,38 +529,38 @@ smoother_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) envelope_test_SOURCES = tests/envelope-test.c envelope_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -envelope_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +envelope_test_CFLAGS = $(AM_CFLAGS) +envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) proplist_test_SOURCES = tests/proplist-test.c proplist_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -proplist_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +proplist_test_CFLAGS = $(AM_CFLAGS) +proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) rtstutter_SOURCES = tests/rtstutter.c rtstutter_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -rtstutter_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -rtstutter_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +rtstutter_CFLAGS = $(AM_CFLAGS) +rtstutter_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) stripnul_SOURCES = tests/stripnul.c stripnul_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -stripnul_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -stripnul_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +stripnul_CFLAGS = $(AM_CFLAGS) +stripnul_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) lock_autospawn_test_SOURCES = tests/lock-autospawn-test.c lock_autospawn_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -lock_autospawn_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -lock_autospawn_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +lock_autospawn_test_CFLAGS = $(AM_CFLAGS) +lock_autospawn_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) prioq_test_SOURCES = tests/prioq-test.c prioq_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -prioq_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -prioq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +prioq_test_CFLAGS = $(AM_CFLAGS) +prioq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) sigbus_test_SOURCES = tests/sigbus-test.c sigbus_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la -sigbus_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -sigbus_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +sigbus_test_CFLAGS = $(AM_CFLAGS) +sigbus_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) gtk_test_SOURCES = tests/gtk-test.c gtk_test_LDADD = $(AM_LDADD) libpulse.la libpulse-mainloop-glib.la @@ -551,6 +572,11 @@ alsa_time_test_LDADD = $(AM_LDADD) alsa_time_test_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) alsa_time_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(ASOUNDLIB_LIBS) +usergroup_test_SOURCES = tests/usergroup-test.c +usergroup_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la +usergroup_test_CFLAGS = $(AM_CFLAGS) +usergroup_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + ################################### # Common library # ################################### @@ -615,6 +641,7 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/tagstruct.c pulsecore/tagstruct.h \ pulsecore/time-smoother.c pulsecore/time-smoother.h \ pulsecore/tokenizer.c pulsecore/tokenizer.h \ + pulsecore/usergroup.c pulsecore/usergroup.h \ pulsecore/sndfile-util.c pulsecore/sndfile-util.h \ pulsecore/winsock.h @@ -675,6 +702,7 @@ pulseinclude_HEADERS = \ pulse/context.h \ pulse/def.h \ pulse/error.h \ + pulse/ext-device-manager.h \ pulse/ext-stream-restore.h \ pulse/gccmacro.h \ pulse/introspect.h \ @@ -725,6 +753,7 @@ libpulse_la_SOURCES = \ pulse/context.c pulse/context.h \ pulse/def.h \ pulse/error.c pulse/error.h \ + pulse/ext-device-manager.c pulse/ext-device-manager.h \ pulse/ext-stream-restore.c pulse/ext-stream-restore.h \ pulse/gccmacro.h \ pulse/internal.h \ @@ -776,7 +805,7 @@ libpulse_mainloop_glib_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version # OSS emulation # ################################### -if HAVE_OSS +if HAVE_OSS_WRAPPER lib_LTLIBRARIES += libpulsedsp.la bin_SCRIPTS += utils/padsp endif @@ -817,11 +846,18 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/object.c pulsecore/object.h \ pulsecore/play-memblockq.c pulsecore/play-memblockq.h \ pulsecore/play-memchunk.c pulsecore/play-memchunk.h \ + pulsecore/remap.c pulsecore/remap.h \ + pulsecore/remap_mmx.c pulsecore/remap_sse.c \ pulsecore/resampler.c pulsecore/resampler.h \ pulsecore/rtpoll.c pulsecore/rtpoll.h \ pulsecore/sample-util.c pulsecore/sample-util.h \ + pulsecore/cpu-arm.c pulsecore/cpu-arm.h \ + pulsecore/cpu-x86.c pulsecore/cpu-x86.h \ + pulsecore/svolume_c.c pulsecore/svolume_arm.c \ + pulsecore/svolume_mmx.c pulsecore/svolume_sse.c \ pulsecore/sconv-s16be.c pulsecore/sconv-s16be.h \ pulsecore/sconv-s16le.c pulsecore/sconv-s16le.h \ + pulsecore/sconv_sse.c \ pulsecore/sconv.c pulsecore/sconv.h \ pulsecore/shared.c pulsecore/shared.h \ pulsecore/shm.c pulsecore/shm.h \ @@ -837,9 +873,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/time-smoother.c pulsecore/time-smoother.h \ pulsecore/database.h -libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS) $(LIBOIL_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS = -avoid-version -libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libpulsecore-foreign.la +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libpulsecore-foreign.la if HAVE_X11 libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/x11wrap.c pulsecore/x11wrap.h @@ -848,7 +884,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS += $(X11_LIBS) endif if HAVE_DBUS -libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/dbus-shared.c pulsecore/dbus-shared.h +libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += \ + pulsecore/dbus-shared.c pulsecore/dbus-shared.h \ + pulsecore/protocol-dbus.c pulsecore/protocol-dbus.h libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(DBUS_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(DBUS_LIBS) endif @@ -865,6 +903,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(TDB_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(TDB_LIBS) endif +if HAVE_SIMPLEDB +libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-simple.c +endif # We split the foreign code off to not be annoyed by warnings we don't care about noinst_LTLIBRARIES = libpulsecore-foreign.la @@ -966,6 +1007,7 @@ modlibexec_LTLIBRARIES += \ module-sine-source.la \ module-detect.la \ module-volume-restore.la \ + module-device-manager.la \ module-device-restore.la \ module-stream-restore.la \ module-card-restore.la \ @@ -987,8 +1029,8 @@ modlibexec_LTLIBRARIES += \ module-tunnel-source.la \ module-position-event-sounds.la \ module-augment-properties.la \ - module-cork-music-on-phone.la - + module-cork-music-on-phone.la \ + module-loopback.la # See comment at librtp.la above if !OS_IS_WIN32 @@ -1031,7 +1073,7 @@ modlibexec_LTLIBRARIES += \ module-x11-cork-request.la endif -if HAVE_OSS +if HAVE_OSS_OUTPUT modlibexec_LTLIBRARIES += \ liboss-util.la \ module-oss.la @@ -1120,6 +1162,11 @@ modlibexec_LTLIBRARIES += \ module-hal-detect.la endif +if HAVE_HAL_COMPAT +modlibexec_LTLIBRARIES += \ + module-hal-detect.la +endif + if HAVE_UDEV modlibexec_LTLIBRARIES += \ module-udev-detect.la @@ -1127,7 +1174,8 @@ endif if HAVE_DBUS modlibexec_LTLIBRARIES += \ - module-rygel-media-server.la + module-rygel-media-server.la \ + module-dbus-protocol.la endif if HAVE_BLUEZ @@ -1153,6 +1201,11 @@ modlibexec_LTLIBRARIES += \ endif endif +if HAVE_FFTW +modlibexec_LTLIBRARIES += \ + module-equalizer-sink.la +endif + # These are generated by an M4 script SYMDEF_FILES = \ modules/module-cli-symdef.h \ @@ -1171,6 +1224,7 @@ SYMDEF_FILES = \ modules/module-combine-symdef.h \ modules/module-remap-sink-symdef.h \ modules/module-ladspa-sink-symdef.h \ + modules/module-equalizer-sink-symdef.h \ modules/module-esound-compat-spawnfd-symdef.h \ modules/module-esound-compat-spawnpid-symdef.h \ modules/module-match-symdef.h \ @@ -1202,6 +1256,7 @@ SYMDEF_FILES = \ modules/jack/module-jack-sink-symdef.h \ modules/jack/module-jack-source-symdef.h \ modules/module-volume-restore-symdef.h \ + modules/module-device-manager-symdef.h \ modules/module-device-restore-symdef.h \ modules/module-stream-restore-symdef.h \ modules/module-card-restore-symdef.h \ @@ -1221,7 +1276,9 @@ SYMDEF_FILES = \ modules/module-position-event-sounds-symdef.h \ modules/module-augment-properties-symdef.h \ modules/module-cork-music-on-phone-symdef.h \ - modules/module-console-kit-symdef.h + modules/module-console-kit-symdef.h \ + modules/dbus/module-dbus-protocol-symdef.h \ + modules/module-loopback-symdef.h EXTRA_DIST += $(SYMDEF_FILES) BUILT_SOURCES += $(SYMDEF_FILES) @@ -1270,6 +1327,24 @@ module_http_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_HTTP $(A module_http_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS) module_http_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libprotocol-http.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +# D-Bus protocol + +module_dbus_protocol_la_SOURCES = \ + modules/dbus/iface-card.c modules/dbus/iface-card.h \ + modules/dbus/iface-card-profile.c modules/dbus/iface-card-profile.h \ + modules/dbus/iface-client.c modules/dbus/iface-client.h \ + modules/dbus/iface-core.c modules/dbus/iface-core.h \ + modules/dbus/iface-device.c modules/dbus/iface-device.h \ + modules/dbus/iface-device-port.c modules/dbus/iface-device-port.h \ + modules/dbus/iface-memstats.c modules/dbus/iface-memstats.h \ + modules/dbus/iface-module.c modules/dbus/iface-module.h \ + modules/dbus/iface-sample.c modules/dbus/iface-sample.h \ + modules/dbus/iface-stream.c modules/dbus/iface-stream.h \ + modules/dbus/module-dbus-protocol.c +module_dbus_protocol_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +module_dbus_protocol_la_LDFLAGS = $(MODULE_LDFLAGS) +module_dbus_protocol_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la + # Native protocol module_native_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c @@ -1350,6 +1425,11 @@ module_ladspa_sink_la_CFLAGS = -DLADSPA_PATH=\"$(libdir)/ladspa:/usr/local/lib/l module_ladspa_sink_la_LDFLAGS = $(MODULE_LDFLAGS) module_ladspa_sink_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_equalizer_sink_la_SOURCES = modules/module-equalizer-sink.c +module_equalizer_sink_la_CFLAGS = $(AM_CFLAGS) $(FFTW_CFLAGS) +module_equalizer_sink_la_LDFLAGS = $(MODULE_LDFLAGS) +module_equalizer_sink_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) $(FFTW_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la + module_match_la_SOURCES = modules/module-match.c module_match_la_LDFLAGS = $(MODULE_LDFLAGS) module_match_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -1363,6 +1443,10 @@ module_tunnel_source_la_SOURCES = modules/module-tunnel.c module_tunnel_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_loopback_la_SOURCES = modules/module-loopback.c +module_loopback_la_LDFLAGS = $(MODULE_LDFLAGS) +module_loopback_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la + # X11 module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c @@ -1505,6 +1589,12 @@ module_cork_music_on_phone_la_LDFLAGS = $(MODULE_LDFLAGS) module_cork_music_on_phone_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_cork_music_on_phone_la_CFLAGS = $(AM_CFLAGS) +# Device description restore module +module_device_manager_la_SOURCES = modules/module-device-manager.c +module_device_manager_la_LDFLAGS = $(MODULE_LDFLAGS) +module_device_manager_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_device_manager_la_CFLAGS = $(AM_CFLAGS) + # Device volume/muted restore module module_device_restore_la_SOURCES = modules/module-device-restore.c module_device_restore_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -1517,6 +1607,11 @@ module_stream_restore_la_LDFLAGS = $(MODULE_LDFLAGS) module_stream_restore_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_stream_restore_la_CFLAGS = $(AM_CFLAGS) +if HAVE_DBUS +module_stream_restore_la_LIBADD += $(DBUS_LIBS) +module_stream_restore_la_CFLAGS += $(DBUS_CFLAGS) +endif + # Card profile restore module module_card_restore_la_SOURCES = modules/module-card-restore.c module_card_restore_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -1576,10 +1671,16 @@ module_jack_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_jack_source_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la $(JACK_LIBS) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_jack_source_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) +if HAVE_HAL_COMPAT +module_hal_detect_la_SOURCES = modules/module-hal-detect-compat.c +module_hal_detect_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_hal_detect_la_CFLAGS = $(AM_CFLAGS) +else module_hal_detect_la_SOURCES = modules/module-hal-detect.c -module_hal_detect_la_LDFLAGS = $(MODULE_LDFLAGS) module_hal_detect_la_LIBADD = $(AM_LIBADD) $(HAL_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_hal_detect_la_CFLAGS = $(AM_CFLAGS) $(HAL_CFLAGS) +endif +module_hal_detect_la_LDFLAGS = $(MODULE_LDFLAGS) module_udev_detect_la_SOURCES = modules/module-udev-detect.c module_udev_detect_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -1661,7 +1762,7 @@ module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) # Some minor stuff # ################################### -CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 daemon/pulseaudio.desktop +CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 start-pulseaudio-kde daemon/pulseaudio.desktop daemon/pulseaudio-kde.desktop esdcompat: daemon/esdcompat.in Makefile sed -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \ @@ -1674,6 +1775,11 @@ start-pulseaudio-x11: daemon/start-pulseaudio-x11.in Makefile -e 's,@PACTL_BINARY\@,$(bindir)/pactl,g' < $< > $@ chmod +x start-pulseaudio-x11 +start-pulseaudio-kde: daemon/start-pulseaudio-kde.in Makefile + sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' \ + -e 's,@PACTL_BINARY\@,$(bindir)/pactl,g' < $< > $@ + chmod +x start-pulseaudio-kde + client.conf: pulse/client.conf.in Makefile sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@ @@ -1698,7 +1804,7 @@ daemon.conf: daemon/daemon.conf.in Makefile -e 's,@PA_DEFAULT_CONFIG_FILE\@,$(DEFAULT_CONFIG_DIR),g' < $< > $@ install-exec-hook: - chown root $(DESTDIR)$(bindir)/pulseaudio ; true + -chown root $(DESTDIR)$(pulselibexecdir)/proximity-helper -chmod u+s $(DESTDIR)$(pulselibexecdir)/proximity-helper ln -sf pacat $(DESTDIR)$(bindir)/parec ln -sf pacat $(DESTDIR)$(bindir)/pamon diff --git a/src/daemon/.gitignore b/src/daemon/.gitignore index 0efa55ba..54e4299b 100644 --- a/src/daemon/.gitignore +++ b/src/daemon/.gitignore @@ -1,2 +1,3 @@ org.pulseaudio.policy pulseaudio.desktop +pulseaudio-kde.desktop diff --git a/src/daemon/caps.c b/src/daemon/caps.c index 294be494..76b62e03 100644 --- a/src/daemon/caps.c +++ b/src/daemon/caps.c @@ -57,24 +57,29 @@ void pa_drop_root(void) { #ifdef HAVE_GETUID uid_t uid; + gid_t gid; + pa_log_debug(_("Cleaning up privileges.")); uid = getuid(); - if (uid == 0 || geteuid() != 0) - return; - - pa_log_info(_("Dropping root privileges.")); + gid = getgid(); #if defined(HAVE_SETRESUID) pa_assert_se(setresuid(uid, uid, uid) >= 0); + pa_assert_se(setresgid(gid, gid, gid) >= 0); #elif defined(HAVE_SETREUID) pa_assert_se(setreuid(uid, uid) >= 0); + pa_assert_se(setregid(gid, gid) >= 0); #else pa_assert_se(setuid(uid) >= 0); pa_assert_se(seteuid(uid) >= 0); + pa_assert_se(setgid(gid) >= 0); + pa_assert_se(setegid(gid) >= 0); #endif pa_assert_se(getuid() == uid); pa_assert_se(geteuid() == uid); + pa_assert_se(getgid() == gid); + pa_assert_se(getegid() == gid); #endif #ifdef HAVE_SYS_PRCTL_H @@ -82,7 +87,7 @@ void pa_drop_root(void) { #endif #ifdef HAVE_SYS_CAPABILITY_H - { + if (uid != 0) { cap_t caps; pa_assert_se(caps = cap_init()); pa_assert_se(cap_clear(caps) == 0); diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index ecb38486..f6cdcdc8 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -385,11 +385,6 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d pa_xfree(conf->script_commands); conf->script_commands = pa_strbuf_tostring_free(buf); - if (!conf->script_commands) { - pa_xfree(conf->script_commands); - conf->script_commands = NULL; - } - *d = optind; return 0; diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c index c2877ecf..f5042a75 100644 --- a/src/daemon/cpulimit.c +++ b/src/daemon/cpulimit.c @@ -188,15 +188,13 @@ int pa_cpu_limit_init(pa_mainloop_api *m) { last_time = pa_rtclock_now(); /* Prepare the main loop pipe */ - if (pipe(the_pipe) < 0) { + if (pa_pipe_cloexec(the_pipe) < 0) { pa_log("pipe() failed: %s", pa_cstrerror(errno)); return -1; } pa_make_fd_nonblock(the_pipe[0]); pa_make_fd_nonblock(the_pipe[1]); - pa_make_fd_cloexec(the_pipe[0]); - pa_make_fd_cloexec(the_pipe[1]); api = m; io_event = api->io_new(m, the_pipe[0], PA_IO_EVENT_INPUT, callback, NULL); diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index 9010f2f6..bfd5c118 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -83,7 +83,10 @@ static const pa_daemon_conf default_conf = { .config_file = NULL, .use_pid_file = TRUE, .system_instance = FALSE, - .no_cpu_limit = FALSE, +#ifdef HAVE_DBUS + .local_server_type = PA_SERVER_TYPE_UNSET, /* The actual default is _USER, but we have to detect when the user doesn't specify this option. */ +#endif + .no_cpu_limit = TRUE, .disable_shm = FALSE, .lock_memory = FALSE, .default_n_fragments = 4, @@ -133,9 +136,25 @@ static const pa_daemon_conf default_conf = { }; pa_daemon_conf* pa_daemon_conf_new(void) { - pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1); + pa_daemon_conf *c; + + c = pa_xnewdup(pa_daemon_conf, &default_conf, 1); + +#if defined(__linux__) && !defined(__OPTIMIZE__) + + /* We abuse __OPTIMIZE__ as a check whether we are a debug build + * or not. If we are and are run from the build tree then we + * override the search path to point to our build tree */ + + if (pa_run_from_build_tree()) { + pa_log_notice("Detected that we are run from the build tree, fixing search path."); + c->dl_search_path = pa_xstrdup(PA_BUILDDIR "/.libs/"); + + } else + +#endif + c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH); - c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH); return c; } @@ -204,6 +223,22 @@ int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) { return 0; } +int pa_daemon_conf_set_local_server_type(pa_daemon_conf *c, const char *string) { + pa_assert(c); + pa_assert(string); + + if (!strcmp(string, "user")) + c->local_server_type = PA_SERVER_TYPE_USER; + else if (!strcmp(string, "system")) { + c->local_server_type = PA_SERVER_TYPE_SYSTEM; + } else if (!strcmp(string, "none")) { + c->local_server_type = PA_SERVER_TYPE_NONE; + } else + return -1; + + return 0; +} + static int parse_log_target(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { pa_daemon_conf *c = data; @@ -431,6 +466,22 @@ static int parse_rtprio(const char *filename, unsigned line, const char *section return 0; } +static int parse_server_type(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_local_server_type(c, rvalue) < 0) { + pa_log(_("[%s:%u] Invalid server type '%s'."), filename, line, rvalue); + return -1; + } + + return 0; +} + int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { int r = -1; FILE *f = NULL; @@ -441,11 +492,18 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { { "high-priority", pa_config_parse_bool, &c->high_priority, NULL }, { "realtime-scheduling", pa_config_parse_bool, &c->realtime_scheduling, NULL }, { "disallow-module-loading", pa_config_parse_bool, &c->disallow_module_loading, NULL }, + { "allow-module-loading", pa_config_parse_not_bool, &c->disallow_module_loading, NULL }, { "disallow-exit", pa_config_parse_bool, &c->disallow_exit, NULL }, + { "allow-exit", pa_config_parse_not_bool, &c->disallow_exit, NULL }, { "use-pid-file", pa_config_parse_bool, &c->use_pid_file, NULL }, { "system-instance", pa_config_parse_bool, &c->system_instance, NULL }, +#ifdef HAVE_DBUS + { "local-server-type", parse_server_type, c, NULL }, +#endif { "no-cpu-limit", pa_config_parse_bool, &c->no_cpu_limit, NULL }, + { "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 }, { "flat-volumes", pa_config_parse_bool, &c->flat_volumes, NULL }, { "lock-memory", pa_config_parse_bool, &c->lock_memory, NULL }, { "exit-idle-time", pa_config_parse_int, &c->exit_idle_time, NULL }, @@ -465,7 +523,9 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { { "default-fragment-size-msec", parse_fragment_size_msec, c, NULL }, { "nice-level", parse_nice_level, c, NULL }, { "disable-remixing", pa_config_parse_bool, &c->disable_remixing, NULL }, + { "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL }, { "disable-lfe-remixing", pa_config_parse_bool, &c->disable_lfe_remixing, NULL }, + { "enable-lfe-remixing", pa_config_parse_not_bool, &c->disable_lfe_remixing, NULL }, { "load-default-script-file", pa_config_parse_bool, &c->load_default_script_file, NULL }, { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, { "log-meta", pa_config_parse_bool, &c->log_meta, NULL }, @@ -517,7 +577,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { c->config_file = NULL; f = filename ? - fopen(c->config_file = pa_xstrdup(filename), "r") : + pa_fopen_cloexec(c->config_file = pa_xstrdup(filename), "r") : pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file); if (!f && errno != ENOENT) { @@ -592,7 +652,7 @@ FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c) { else f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file); } else - f = fopen(c->default_script_file, "r"); + f = pa_fopen_cloexec(c->default_script_file, "r"); return f; } @@ -605,6 +665,14 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) { [PA_LOG_WARN] = "warning", [PA_LOG_ERROR] = "error" }; + + static const char* const server_type_to_string[] = { + [PA_SERVER_TYPE_UNSET] = "!!UNSET!!", + [PA_SERVER_TYPE_USER] = "user", + [PA_SERVER_TYPE_SYSTEM] = "system", + [PA_SERVER_TYPE_NONE] = "none" + }; + pa_strbuf *s; char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; @@ -623,12 +691,15 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) { pa_strbuf_printf(s, "nice-level = %i\n", c->nice_level); pa_strbuf_printf(s, "realtime-scheduling = %s\n", pa_yes_no(c->realtime_scheduling)); pa_strbuf_printf(s, "realtime-priority = %i\n", c->realtime_priority); - pa_strbuf_printf(s, "disallow-module-loading = %s\n", pa_yes_no(c->disallow_module_loading)); - pa_strbuf_printf(s, "disallow-exit = %s\n", pa_yes_no(c->disallow_exit)); + pa_strbuf_printf(s, "allow-module-loading = %s\n", pa_yes_no(!c->disallow_module_loading)); + pa_strbuf_printf(s, "allow-exit = %s\n", pa_yes_no(!c->disallow_exit)); pa_strbuf_printf(s, "use-pid-file = %s\n", pa_yes_no(c->use_pid_file)); pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance)); - pa_strbuf_printf(s, "no-cpu-limit = %s\n", pa_yes_no(c->no_cpu_limit)); - pa_strbuf_printf(s, "disable-shm = %s\n", pa_yes_no(c->disable_shm)); +#ifdef HAVE_DBUS + pa_strbuf_printf(s, "local-server-type = %s\n", server_type_to_string[c->local_server_type]); +#endif + pa_strbuf_printf(s, "cpu-limit = %s\n", pa_yes_no(!c->no_cpu_limit)); + pa_strbuf_printf(s, "enable-shm = %s\n", pa_yes_no(!c->disable_shm)); pa_strbuf_printf(s, "flat-volumes = %s\n", pa_yes_no(c->flat_volumes)); pa_strbuf_printf(s, "lock-memory = %s\n", pa_yes_no(c->lock_memory)); pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time); @@ -639,8 +710,8 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) { pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr")); pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]); pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method)); - pa_strbuf_printf(s, "disable-remixing = %s\n", pa_yes_no(c->disable_remixing)); - pa_strbuf_printf(s, "disable-lfe-remixing = %s\n", pa_yes_no(c->disable_lfe_remixing)); + pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing)); + pa_strbuf_printf(s, "enable-lfe-remixing = %s\n", pa_yes_no(!c->disable_lfe_remixing)); pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format)); pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate); pa_strbuf_printf(s, "default-sample-channels = %u\n", c->default_sample_spec.channels); diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index dd69e048..41c3c4b7 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -28,6 +28,7 @@ #include <pulsecore/log.h> #include <pulsecore/macro.h> +#include <pulsecore/core.h> #include <pulsecore/core-util.h> #ifdef HAVE_SYS_RESOURCE_H @@ -75,6 +76,7 @@ typedef struct pa_daemon_conf { log_time, flat_volumes, lock_memory; + pa_server_type_t local_server_type; int exit_idle_time, scache_idle_time, auto_log_target, @@ -152,6 +154,7 @@ int pa_daemon_conf_env(pa_daemon_conf *c); int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string); int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string); int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string); +int pa_daemon_conf_set_local_server_type(pa_daemon_conf *c, const char *string); const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c); FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c); diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index 6931359c..7c1a7b8c 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -21,14 +21,15 @@ ; daemonize = no ; fail = yes -; disallow-module-loading = no -; disallow-exit = no +; allow-module-loading = yes +; allow-exit = yes ; use-pid-file = yes ; system-instance = no -; disable-shm = no +; local-server-type = user +; enable-shm = yes ; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB ; lock-memory = no -; no-cpu-limit = no +; cpu-limit = no ; high-priority = yes ; nice-level = -11 @@ -51,8 +52,8 @@ ; log-backtrace = 0 ; resample-method = speex-float-3 -; disable-remixing = no -; disable-lfe-remixing = yes +; enable-remixing = yes +; enable-lfe-remixing = no ; flat-volumes = yes @@ -70,7 +71,7 @@ ; rlimit-msgqueue = -1 ; rlimit-nice = 31 ; rlimit-rtprio = 9 -; rlimit-rtttime = 1000000 +; rlimit-rttime = 1000000 ; default-sample-format = s16le ; default-sample-rate = 44100 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index 00c000eb..f9b9eadd 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -66,6 +66,9 @@ load-module module-bluetooth-discover .ifexists module-esound-protocol-unix@PA_SOEXT@ load-module module-esound-protocol-unix .endif +.ifexists module-dbus-protocol@PA_SOEXT@ +load-module module-dbus-protocol +.endif load-module module-native-protocol-unix ### Network access (may be configured with paprefs, so leave this commented diff --git a/src/daemon/main.c b/src/daemon/main.c index b209c514..48911827 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -39,8 +39,6 @@ #include <sys/types.h> #include <sys/stat.h> -#include <liboil/liboil.h> - #ifdef HAVE_SYS_MMAN_H #include <sys/mman.h> #endif @@ -65,10 +63,6 @@ #include <dbus/dbus.h> #endif -#ifdef __linux__ -#include <sys/personality.h> -#endif - #include <pulse/mainloop.h> #include <pulse/mainloop-signal.h> #include <pulse/timeval.h> @@ -99,6 +93,8 @@ #ifdef HAVE_DBUS #include <pulsecore/dbus-shared.h> #endif +#include <pulsecore/cpu-arm.h> +#include <pulsecore/cpu-x86.h> #include "cmdline.h" #include "cpulimit.h" @@ -106,6 +102,7 @@ #include "dumpmodules.h" #include "caps.h" #include "ltdl-bind-now.h" +#include "server-lookup.h" #ifdef HAVE_LIBWRAP /* Only one instance of these variables */ @@ -113,7 +110,7 @@ int allow_severity = LOG_INFO; int deny_severity = LOG_WARNING; #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_WRAPPER /* padsp looks for this symbol in the running process and disables * itself if it finds it and it is set to 7 (which is actually a bit * mask). For details see padsp. */ @@ -263,9 +260,14 @@ static int change_user(void) { pa_set_env("HOME", PA_SYSTEM_RUNTIME_PATH); /* Relevant for pa_runtime_path() */ - pa_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH); - pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_CONFIG_PATH); - pa_set_env("PULSE_STATE_PATH", PA_SYSTEM_STATE_PATH); + if (!getenv("PULSE_RUNTIME_PATH")) + pa_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH); + + if (!getenv("PULSE_CONFIG_PATH")) + pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_CONFIG_PATH); + + if (!getenv("PULSE_STATE_PATH")) + pa_set_env("PULSE_STATE_PATH", PA_SYSTEM_STATE_PATH); pa_log_info(_("Successfully dropped root privileges.")); @@ -342,33 +344,31 @@ static void set_all_rlimits(const pa_daemon_conf *conf) { #endif #ifdef HAVE_DBUS -static pa_dbus_connection *register_dbus(pa_core *c) { +static pa_dbus_connection *register_dbus_name(pa_core *c, DBusBusType bus, const char* name) { DBusError error; pa_dbus_connection *conn; dbus_error_init(&error); - if (!(conn = pa_dbus_bus_get(c, pa_in_system_mode() ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + if (!(conn = pa_dbus_bus_get(c, bus, &error)) || dbus_error_is_set(&error)) { pa_log_warn("Unable to contact D-Bus: %s: %s", error.name, error.message); goto fail; } - if (dbus_bus_request_name(pa_dbus_connection_get(conn), "org.pulseaudio.Server", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { - pa_log_debug("Got org.pulseaudio.Server!"); + if (dbus_bus_request_name(pa_dbus_connection_get(conn), name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + pa_log_debug("Got %s!", name); return conn; } if (dbus_error_is_set(&error)) - pa_log_warn("Failed to acquire org.pulseaudio.Server: %s: %s", error.name, error.message); + pa_log_error("Failed to acquire %s: %s: %s", name, error.name, error.message); else - pa_log_warn("D-Bus name org.pulseaudio.Server already taken. Weird shit!"); + pa_log_error("D-Bus name %s already taken. Weird shit!", name); /* PA cannot be started twice by the same user and hence we can - * ignore mostly the case that org.pulseaudio.Server is already - * taken. */ + * ignore mostly the case that a name is already taken. */ fail: - if (conn) pa_dbus_connection_unref(conn); @@ -398,7 +398,10 @@ int main(int argc, char *argv[]) { int autospawn_fd = -1; pa_bool_t autospawn_locked = FALSE; #ifdef HAVE_DBUS - pa_dbus_connection *dbus = NULL; + pa_dbusobj_server_lookup *server_lookup = NULL; /* /org/pulseaudio/server_lookup */ + pa_dbus_connection *lookup_service_bus = NULL; /* Always the user bus. */ + pa_dbus_connection *server_bus = NULL; /* The bus where we reserve org.pulseaudio.Server, either the user or the system bus. */ + pa_bool_t start_server; #endif pa_log_set_ident("pulseaudio"); @@ -415,23 +418,31 @@ int main(int argc, char *argv[]) { if (!getenv("LD_BIND_NOW")) { char *rp; + char *canonical_rp; /* We have to execute ourselves, because the libc caches the * value of $LD_BIND_NOW on initialization. */ pa_set_env("LD_BIND_NOW", "1"); - if ((rp = pa_readlink("/proc/self/exe"))) { + if ((canonical_rp = pa_realpath(PA_BINARY))) { - if (pa_streq(rp, PA_BINARY)) - pa_assert_se(execv(rp, argv) == 0); - else - pa_log_warn("/proc/self/exe does not point to " PA_BINARY ", cannot self execute. Are you playing games?"); + if ((rp = pa_readlink("/proc/self/exe"))) { + + if (pa_streq(rp, canonical_rp)) + pa_assert_se(execv(rp, argv) == 0); + else + pa_log_warn("/proc/self/exe does not point to %s, cannot self execute. Are you playing games?", canonical_rp); + + pa_xfree(rp); + + } else + pa_log_warn("Couldn't read /proc/self/exe, cannot self execute. Running in a chroot()?"); - pa_xfree(rp); + pa_xfree(canonical_rp); } else - pa_log_warn("Couldn't read /proc/self/exe, cannot self execute. Running in a chroot()?"); + pa_log_warn("Couldn't canonicalize binary path, cannot self execute."); } #endif @@ -446,15 +457,12 @@ int main(int argc, char *argv[]) { * context we have been started. Let's cleanup our execution * context as good as possible */ -#ifdef __linux__ - if (personality(PER_LINUX) < 0) - pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno)); -#endif - + pa_reset_personality(); pa_drop_root(); pa_close_all(passed_fd, -1); pa_reset_sigs(-1); pa_unblock_sigs(-1); + pa_reset_priority(); setlocale(LC_ALL, ""); pa_init_i18n(); @@ -480,6 +488,32 @@ int main(int argc, char *argv[]) { pa_log_set_flags(PA_LOG_PRINT_TIME, PA_LOG_SET); pa_log_set_show_backtrace(conf->log_backtrace); +#ifdef HAVE_DBUS + /* conf->system_instance and conf->local_server_type control almost the + * same thing; make them agree about what is requested. */ + switch (conf->local_server_type) { + case PA_SERVER_TYPE_UNSET: + conf->local_server_type = conf->system_instance ? PA_SERVER_TYPE_SYSTEM : PA_SERVER_TYPE_USER; + break; + case PA_SERVER_TYPE_USER: + case PA_SERVER_TYPE_NONE: + conf->system_instance = FALSE; + break; + case PA_SERVER_TYPE_SYSTEM: + conf->system_instance = TRUE; + break; + default: + pa_assert_not_reached(); + } + + start_server = conf->local_server_type == PA_SERVER_TYPE_USER || (getuid() == 0 && conf->local_server_type == PA_SERVER_TYPE_SYSTEM); + + if (!start_server && conf->local_server_type == PA_SERVER_TYPE_SYSTEM) { + pa_log_notice(_("System mode refused for non-root user. Only starting the D-Bus server lookup service.")); + conf->system_instance = FALSE; + } +#endif + LTDL_SET_PRELOADED_SYMBOLS(); pa_ltdl_init(); ltdl_init = TRUE; @@ -566,10 +600,12 @@ int main(int argc, char *argv[]) { if (getuid() == 0 && !conf->system_instance) pa_log_warn(_("This program is not intended to be run as root (unless --system is specified).")); +#ifndef HAVE_DBUS /* A similar, only a notice worthy check was done earlier, if D-Bus is enabled. */ else if (getuid() != 0 && conf->system_instance) { pa_log(_("Root privileges required.")); goto finish; } +#endif if (conf->cmd == PA_CMD_START && conf->system_instance) { pa_log(_("--start not supported for system instances.")); @@ -612,7 +648,6 @@ int main(int argc, char *argv[]) { if (conf->daemonize) { pid_t child; - int tty_fd; if (pa_stdio_acquire() < 0) { pa_log(_("Failed to acquire stdio.")); @@ -621,7 +656,7 @@ int main(int argc, char *argv[]) { #ifdef HAVE_FORK if (pipe(daemon_pipe) < 0) { - pa_log(_("pipe failed: %s"), pa_cstrerror(errno)); + pa_log(_("pipe() failed: %s"), pa_cstrerror(errno)); goto finish; } @@ -672,22 +707,27 @@ int main(int argc, char *argv[]) { pa_log_set_target(PA_LOG_SYSLOG); #ifdef HAVE_SETSID - setsid(); -#endif -#ifdef HAVE_SETPGID - setpgid(0,0); + if (setsid() < 0) { + pa_log(_("setsid() failed: %s"), pa_cstrerror(errno)); + goto finish; + } #endif -#ifndef OS_IS_WIN32 - pa_close(0); - pa_close(1); - pa_close(2); + /* We now are a session and process group leader. Let's fork + * again and let the father die, so that we'll become a + * process that can never acquire a TTY again, in a session and + * process group without leader */ - pa_assert_se(open("/dev/null", O_RDONLY) == 0); - pa_assert_se(open("/dev/null", O_WRONLY) == 1); - pa_assert_se(open("/dev/null", O_WRONLY) == 2); -#else - FreeConsole(); +#ifdef HAVE_FORK + if ((child = fork()) < 0) { + pa_log(_("fork() failed: %s"), pa_cstrerror(errno)); + goto finish; + } + + if (child != 0) { + retval = 0; + goto finish; + } #endif #ifdef SIGTTOU @@ -700,15 +740,10 @@ int main(int argc, char *argv[]) { signal(SIGTSTP, SIG_IGN); #endif -#ifdef TIOCNOTTY - if ((tty_fd = open("/dev/tty", O_RDWR)) >= 0) { - ioctl(tty_fd, TIOCNOTTY, (char*) 0); - pa_assert_se(pa_close(tty_fd) == 0); - } -#endif + pa_nullify_stdfds(); } - pa_set_env("PULSE_INTERNAL", "1"); + pa_set_env_and_record("PULSE_INTERNAL", "1"); pa_assert_se(chdir("/") == 0); umask(0022); @@ -723,7 +758,7 @@ int main(int argc, char *argv[]) { if (change_user() < 0) goto finish; - pa_set_env("PULSE_SYSTEM", conf->system_instance ? "1" : "0"); + pa_set_env_and_record("PULSE_SYSTEM", conf->system_instance ? "1" : "0"); pa_log_info(_("This is PulseAudio %s"), PACKAGE_VERSION); pa_log_debug(_("Compilation host: %s"), CANONICAL_HOST); @@ -745,6 +780,8 @@ int main(int argc, char *argv[]) { pa_log_debug(_("Running in valgrind mode: %s"), pa_yes_no(pa_in_valgrind())); + pa_log_debug(_("Running in VM: %s"), pa_yes_no(pa_running_in_vm())); + #ifdef __OPTIMIZE__ pa_log_debug(_("Optimized build: yes")); #else @@ -781,6 +818,8 @@ int main(int argc, char *argv[]) { pa_log_info(_("Using state directory %s."), s); pa_xfree(s); + pa_log_info(_("Using modules directory %s."), conf->dl_search_path); + pa_log_info(_("Running in system mode: %s"), pa_yes_no(pa_in_system_mode())); if (pa_in_system_mode()) @@ -828,6 +867,11 @@ int main(int argc, char *argv[]) { pa_memtrap_install(); + if (!getenv("PULSE_NO_SIMD")) { + pa_cpu_init_x86(); + pa_cpu_init_arm(); + } + pa_assert_se(mainloop = pa_mainloop_new()); if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm, conf->shm_size))) { @@ -849,6 +893,9 @@ int main(int argc, char *argv[]) { c->running_as_daemon = !!conf->daemonize; c->disallow_exit = conf->disallow_exit; c->flat_volumes = conf->flat_volumes; +#ifdef HAVE_DBUS + c->server_type = conf->local_server_type; +#endif pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0); pa_signal_new(SIGINT, signal_callback, c); @@ -867,40 +914,51 @@ int main(int argc, char *argv[]) { win32_timer = pa_mainloop_get_api(mainloop)->rtclock_time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&win32_tv), message_cb, NULL); #endif - oil_init(); - if (!conf->no_cpu_limit) pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0); buf = pa_strbuf_new(); - if (conf->load_default_script_file) { - FILE *f; - if ((f = pa_daemon_conf_open_default_script_file(conf))) { - r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail); - fclose(f); +#ifdef HAVE_DBUS + if (start_server) { +#endif + if (conf->load_default_script_file) { + FILE *f; + + if ((f = pa_daemon_conf_open_default_script_file(conf))) { + r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail); + fclose(f); + } } - } - if (r >= 0) - r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail); + if (r >= 0) + r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail); - pa_log_error("%s", s = pa_strbuf_tostring_free(buf)); - pa_xfree(s); + pa_log_error("%s", s = pa_strbuf_tostring_free(buf)); + pa_xfree(s); - /* We completed the initial module loading, so let's disable it - * from now on, if requested */ - c->disallow_module_loading = !!conf->disallow_module_loading; + if (r < 0 && conf->fail) { + pa_log(_("Failed to initialize daemon.")); + goto finish; + } - if (r < 0 && conf->fail) { - pa_log(_("Failed to initialize daemon.")); - goto finish; + if (!c->modules || pa_idxset_size(c->modules) == 0) { + pa_log(_("Daemon startup without any loaded modules, refusing to work.")); + goto finish; + } +#ifdef HAVE_DBUS + } else { + /* When we just provide the D-Bus server lookup service, we don't want + * any modules to be loaded. We haven't loaded any so far, so one might + * think there's no way to contact the server, but receiving certain + * signals could still cause modules to load. */ + conf->disallow_module_loading = TRUE; } +#endif - if (!c->modules || pa_idxset_size(c->modules) == 0) { - pa_log(_("Daemon startup without any loaded modules, refusing to work.")); - goto finish; - } + /* We completed the initial module loading, so let's disable it + * from now on, if requested */ + c->disallow_module_loading = !!conf->disallow_module_loading; #ifdef HAVE_FORK if (daemon_pipe[1] >= 0) { @@ -912,7 +970,15 @@ int main(int argc, char *argv[]) { #endif #ifdef HAVE_DBUS - dbus = register_dbus(c); + if (!conf->system_instance) { + if (!(server_lookup = pa_dbusobj_server_lookup_new(c))) + goto finish; + if (!(lookup_service_bus = register_dbus_name(c, DBUS_BUS_SESSION, "org.PulseAudio1"))) + goto finish; + } + + if (start_server && !(server_bus = register_dbus_name(c, conf->system_instance ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, "org.pulseaudio.Server"))) + goto finish; #endif pa_log_info(_("Daemon startup complete.")); @@ -925,8 +991,12 @@ int main(int argc, char *argv[]) { finish: #ifdef HAVE_DBUS - if (dbus) - pa_dbus_connection_unref(dbus); + if (server_bus) + pa_dbus_connection_unref(server_bus); + if (lookup_service_bus) + pa_dbus_connection_unref(lookup_service_bus); + if (server_lookup) + pa_dbusobj_server_lookup_free(server_lookup); #endif if (autospawn_fd >= 0) { @@ -967,6 +1037,9 @@ finish: if (valid_pid_file) pa_pid_file_remove(); + /* This has no real purpose except making things valgrind-clean */ + pa_unset_env_recorded(); + #ifdef OS_IS_WIN32 WSACleanup(); #endif diff --git a/src/daemon/org.pulseaudio.policy.in b/src/daemon/org.pulseaudio.policy.in deleted file mode 100644 index 1d0b6a7d..00000000 --- a/src/daemon/org.pulseaudio.policy.in +++ /dev/null @@ -1,50 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?><!--*-nxml-*--> -<!DOCTYPE policyconfig PUBLIC - "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" - "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> - -<!-- -This file is part of PulseAudio. - -PulseAudio is free software; you can redistribute it and/or modify it -under the terms of the GNU Lesser General Public License as -published by the Free Software Foundation; either version 2.1 of the -License, or (at your option) any later version. - -PulseAudio is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -USA. ---> - -<policyconfig> - <vendor>The PulseAudio Project</vendor> - <vendor_url>http://pulseaudio.org/</vendor_url> - <icon_name>audio-card</icon_name> - - <action id="org.pulseaudio.acquire-real-time"> - <_description>Real-time scheduling for the PulseAudio daemon</_description> - <_message>System policy prevents PulseAudio from acquiring real-time scheduling.</_message> - <defaults> - <allow_any>no</allow_any> - <allow_inactive>no</allow_inactive> - <allow_active>no</allow_active> - </defaults> - </action> - - <action id="org.pulseaudio.acquire-high-priority"> - <_description>High-priority scheduling (negative Unix nice level) for the PulseAudio daemon</_description> - <_message>System policy prevents PulseAudio from acquiring high-priority scheduling.</_message> - <defaults> - <allow_any>no</allow_any> - <allow_inactive>no</allow_inactive> - <allow_active>no</allow_active> - </defaults> - </action> - -</policyconfig> diff --git a/src/daemon/polkit.c b/src/daemon/polkit.c deleted file mode 100644 index 9799e094..00000000 --- a/src/daemon/polkit.c +++ /dev/null @@ -1,172 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB - - 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, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <unistd.h> -#include <stdlib.h> -#include <inttypes.h> - -#include <dbus/dbus.h> -#include <polkit-dbus/polkit-dbus.h> - -#include <pulse/i18n.h> - -#include <pulsecore/log.h> -#include <pulsecore/macro.h> - -#include "polkit.h" - -int pa_polkit_check(const char *action_id) { - int ret = -1; - DBusError dbus_error; - DBusConnection *bus = NULL; - PolKitCaller *caller = NULL; - PolKitAction *action = NULL; - PolKitContext *context = NULL; - PolKitError *polkit_error = NULL; - PolKitSession *session = NULL; - PolKitResult polkit_result; - - dbus_error_init(&dbus_error); - - if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error))) { - pa_log_error(_("Cannot connect to system bus: %s"), dbus_error.message); - goto finish; - } - - /* There seems to be a bug in some versions of D-Bus that causes - * dbus_shutdown() to call exit() when a connection without this - * flag disabled was created during runtime.*/ - dbus_connection_set_exit_on_disconnect(bus, FALSE); - - if (!(caller = polkit_caller_new_from_pid(bus, getpid(), &dbus_error))) { - pa_log_error(_("Cannot get caller from PID: %s"), dbus_error.message); - goto finish; - } - - /* This function is called when PulseAudio is called SUID root. We - * want to authenticate the real user that called us and not the - * effective user we gained through being SUID root. Hence we - * overwrite the UID caller data here explicitly, just for - * paranoia. In fact PolicyKit should fill in the UID here anyway - * -- an not the EUID or any other user id. */ - - if (!(polkit_caller_set_uid(caller, getuid()))) { - pa_log_error(_("Cannot set UID on caller object.")); - goto finish; - } - - if (!(polkit_caller_get_ck_session(caller, &session))) { - pa_log_error(_("Failed to get CK session.")); - goto finish; - } - - /* We need to overwrite the UID in both the caller and the session - * object */ - - if (!(polkit_session_set_uid(session, getuid()))) { - pa_log_error(_("Cannot set UID on session object.")); - goto finish; - } - - if (!(action = polkit_action_new())) { - pa_log_error(_("Cannot allocate PolKitAction.")); - goto finish; - } - - if (!polkit_action_set_action_id(action, action_id)) { - pa_log_error(_("Cannot set action_id")); - goto finish; - } - - if (!(context = polkit_context_new())) { - pa_log_error(_("Cannot allocate PolKitContext.")); - goto finish; - } - - if (!polkit_context_init(context, &polkit_error)) { - pa_log_error(_("Cannot initialize PolKitContext: %s"), polkit_error_get_error_message(polkit_error)); - goto finish; - } - - for (;;) { - - polkit_result = polkit_context_is_caller_authorized(context, action, caller, TRUE, &polkit_error); - - if (polkit_error_is_set(polkit_error)) { - pa_log_error(_("Could not determine whether caller is authorized: %s"), polkit_error_get_error_message(polkit_error)); - goto finish; - } - - if (polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH || - polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_SESSION || - polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_ALWAYS || - polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT || - polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH || - polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_SESSION || - polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_ALWAYS || - polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT - ) { - - if (polkit_auth_obtain(action_id, 0, getpid(), &dbus_error)) { - polkit_result = POLKIT_RESULT_YES; - break; - } - - if (dbus_error_is_set(&dbus_error)) { - pa_log_error(_("Cannot obtain auth: %s"), dbus_error.message); - goto finish; - } - } - - break; - } - - if (polkit_result != POLKIT_RESULT_YES && polkit_result != POLKIT_RESULT_NO) - pa_log_warn(_("PolicyKit responded with '%s'"), polkit_result_to_string_representation(polkit_result)); - - ret = polkit_result == POLKIT_RESULT_YES; - -finish: - - if (caller) - polkit_caller_unref(caller); - - if (action) - polkit_action_unref(action); - - if (context) - polkit_context_unref(context); - - if (bus) - dbus_connection_unref(bus); - - dbus_error_free(&dbus_error); - - if (polkit_error) - polkit_error_free(polkit_error); - - return ret; -} diff --git a/src/daemon/pulseaudio-kde.desktop.in b/src/daemon/pulseaudio-kde.desktop.in new file mode 100644 index 00000000..06846421 --- /dev/null +++ b/src/daemon/pulseaudio-kde.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +_Name=PulseAudio Sound System KDE Routing Policy +_Comment=Start the PulseAudio Sound System with KDE Routing Policy +Exec=start-pulseaudio-kde +Terminal=false +Type=Application +Categories= +GenericName= +OnlyShowIn=KDE; diff --git a/src/daemon/pulseaudio-system.conf b/src/daemon/pulseaudio-system.conf new file mode 100644 index 00000000..edddaf93 --- /dev/null +++ b/src/daemon/pulseaudio-system.conf @@ -0,0 +1,37 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<!-- +This file is part of PulseAudio. + +PulseAudio is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation; either version 2.1 of the +License, or (at your option) any later version. + +PulseAudio is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA. +--> + +<busconfig> + + <!-- System-wide PulseAudio runs as 'pulse' user. This fragment is + not necessary for user PulseAudio instances. --> + + <policy user="pulse"> + <allow own="org.pulseaudio.Server"/> + + <!-- Allow pulseaudio to talk to HAL for device detection --> + <allow send_destination="org.freedesktop.Hal" send_interface="org.freedesktop.Hal.Manager"/> + <allow send_destination="org.freedesktop.Hal" send_interface="org.freedesktop.Hal.Device"/> + </policy> + +</busconfig> diff --git a/src/daemon/server-lookup.c b/src/daemon/server-lookup.c new file mode 100644 index 00000000..45796e72 --- /dev/null +++ b/src/daemon/server-lookup.c @@ -0,0 +1,522 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/client-conf.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/macro.h> +#include <pulsecore/protocol-dbus.h> + +#include "server-lookup.h" + +#define OBJECT_PATH "/org/pulseaudio/server_lookup1" +#define INTERFACE "org.PulseAudio.ServerLookup1" + +struct pa_dbusobj_server_lookup { + pa_core *core; + pa_dbus_connection *conn; + pa_bool_t path_registered; +}; + +static const char introspection[] = + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "<node>" + " <!-- If you are looking for documentation make sure to check out\n" + " http://pulseaudio.org/wiki/DBusInterface -->\n" + " <interface name=\"" INTERFACE "\">\n" + " <property name=\"Address\" type=\"s\" access=\"read\"/>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + "</node>\n"; + +static void unregister_cb(DBusConnection *conn, void *user_data) { + pa_dbusobj_server_lookup *sl = user_data; + + pa_assert(sl); + pa_assert(sl->path_registered); + + sl->path_registered = FALSE; +} + +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + const char *i = introspection; + DBusMessage *reply = NULL; + + pa_assert(conn); + pa_assert(msg); + + if (!(reply = dbus_message_new_method_return(msg))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID)) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + +finish: + if (reply) + dbus_message_unref(reply); + + return r; +} + +enum get_address_result_t { + SUCCESS, + FAILED_TO_LOAD_CLIENT_CONF, + SERVER_FROM_TYPE_FAILED +}; + +/* Caller frees the returned address. */ +static enum get_address_result_t get_address(pa_server_type_t server_type, char **address) { + enum get_address_result_t r = SUCCESS; + pa_client_conf *conf = pa_client_conf_new(); + + *address = NULL; + + if (pa_client_conf_load(conf, NULL) < 0) { + r = FAILED_TO_LOAD_CLIENT_CONF; + goto finish; + } + + if (conf->default_dbus_server) + *address = pa_xstrdup(conf->default_dbus_server); + else if (!(*address = pa_get_dbus_address_from_server_type(server_type))) { + r = SERVER_FROM_TYPE_FAILED; + goto finish; + } + +finish: + pa_client_conf_free(conf); + return r; +} + +static DBusHandlerResult handle_get_address(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + DBusMessage *reply = NULL; + char *address = NULL; + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + switch (get_address(sl->core->server_type, &address)) { + case SUCCESS: + if (!(reply = dbus_message_new_method_return(msg))) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + dbus_message_iter_init_append(reply, &msg_iter); + if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&msg_iter, &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + case FAILED_TO_LOAD_CLIENT_CONF: + if (!(reply = dbus_message_new_error(msg, "org.pulseaudio.ClientConfLoadError", "Failed to load client.conf."))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + case SERVER_FROM_TYPE_FAILED: + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + default: + pa_assert_not_reached(); + } + +finish: + pa_xfree(address); + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult handle_get(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + const char* interface; + const char* property; + DBusMessage *reply = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + if (*interface && !pa_streq(interface, INTERFACE)) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + + if (!pa_streq(property, "Address")) { + if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + r = handle_get_address(conn, msg, sl); + +finish: + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult handle_set(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + const char* interface; + const char* property; + DBusMessage *reply = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + if (*interface && !pa_streq(interface, INTERFACE)) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + + if (!pa_streq(property, "Address")) { + if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + if (!(reply = dbus_message_new_error_printf(msg, DBUS_ERROR_ACCESS_DENIED, "%s: Property not settable", property))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + +finish: + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult handle_get_all(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) { + DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED; + DBusMessage *reply = NULL; + const char *property = "Address"; + char *interface = NULL; + char *address = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter variant_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) { + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + } + + switch (get_address(sl->core->server_type, &address)) { + case SUCCESS: + if (!(reply = dbus_message_new_method_return(msg))) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + dbus_message_iter_init_append(reply, &msg_iter); + if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&dict_entry_iter, &variant_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_message_iter_close_container(&msg_iter, &dict_iter)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + case FAILED_TO_LOAD_CLIENT_CONF: + if (!(reply = dbus_message_new_error(msg, "org.pulseaudio.ClientConfLoadError", "Failed to load client.conf."))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + case SERVER_FROM_TYPE_FAILED: + if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) { + r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto finish; + } + if (!dbus_connection_send(conn, reply, NULL)) { + r = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto finish; + } + r = DBUS_HANDLER_RESULT_HANDLED; + goto finish; + + default: + pa_assert_not_reached(); + } + +finish: + pa_xfree(address); + if (reply) + dbus_message_unref(reply); + + return r; +} + +static DBusHandlerResult message_cb(DBusConnection *conn, DBusMessage *msg, void *user_data) { + pa_dbusobj_server_lookup *sl = user_data; + + pa_assert(conn); + pa_assert(msg); + pa_assert(sl); + + /* pa_log("Got message! type = %s path = %s iface = %s member = %s dest = %s", dbus_message_type_to_string(dbus_message_get_type(msg)), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), dbus_message_get_destination(msg)); */ + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, "Introspect") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Introspect"))) + return handle_introspect(conn, msg, sl); + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Get") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Get"))) + return handle_get(conn, msg, sl); + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Set") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Set"))) + return handle_set(conn, msg, sl); + + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "GetAll") || + (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "GetAll"))) + return handle_get_all(conn, msg, sl); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusObjectPathVTable vtable = { + .unregister_function = unregister_cb, + .message_function = message_cb, + .dbus_internal_pad1 = NULL, + .dbus_internal_pad2 = NULL, + .dbus_internal_pad3 = NULL, + .dbus_internal_pad4 = NULL +}; + +pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c) { + pa_dbusobj_server_lookup *sl; + DBusError error; + + dbus_error_init(&error); + + sl = pa_xnew(pa_dbusobj_server_lookup, 1); + sl->core = c; + sl->path_registered = FALSE; + + if (!(sl->conn = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + pa_log("Unable to contact D-Bus: %s: %s", error.name, error.message); + goto fail; + } + + if (!dbus_connection_register_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH, &vtable, sl)) { + pa_log("dbus_connection_register_object_path() failed for " OBJECT_PATH "."); + goto fail; + } + + sl->path_registered = TRUE; + + return sl; + +fail: + dbus_error_free(&error); + + pa_dbusobj_server_lookup_free(sl); + + return NULL; +} + +void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl) { + pa_assert(sl); + + if (sl->path_registered) { + pa_assert(sl->conn); + if (!dbus_connection_unregister_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH)) + pa_log_debug("dbus_connection_unregister_object_path() failed for " OBJECT_PATH "."); + } + + if (sl->conn) + pa_dbus_connection_unref(sl->conn); + + pa_xfree(sl); +} diff --git a/src/daemon/server-lookup.h b/src/daemon/server-lookup.h new file mode 100644 index 00000000..c930d5b7 --- /dev/null +++ b/src/daemon/server-lookup.h @@ -0,0 +1,40 @@ +#ifndef fooserverlookuphfoo +#define fooserverlookuphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus object at path + * /org/pulseaudio/server_lookup. Implemented interfaces + * are org.pulseaudio.ServerLookup and org.freedesktop.DBus.Introspectable. + * + * See http://pulseaudio.org/wiki/DBusInterface for the ServerLookup interface + * documentation. + */ + +#include <pulsecore/core.h> + +typedef struct pa_dbusobj_server_lookup pa_dbusobj_server_lookup; + +pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c); +void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl); + +#endif diff --git a/src/daemon/start-pulseaudio-kde.in b/src/daemon/start-pulseaudio-kde.in new file mode 100755 index 00000000..c319e7dd --- /dev/null +++ b/src/daemon/start-pulseaudio-kde.in @@ -0,0 +1,30 @@ +#!/bin/sh + +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +set -e + +[ -z "$PULSE_SERVER" ] + +@PA_BINARY@ --start "$@" + +if [ x"$DISPLAY" != x ] ; then + + @PACTL_BINARY@ load-module module-device-manager "do_routing=1" > /dev/null + +fi diff --git a/src/daemon/system.pa.in b/src/daemon/system.pa.in index 27e42815..0ca32bd3 100755 --- a/src/daemon/system.pa.in +++ b/src/daemon/system.pa.in @@ -32,6 +32,10 @@ load-module module-detect .ifexists module-esound-protocol-unix@PA_SOEXT@ load-module module-esound-protocol-unix .endif +.ifexists module-dbus-protocol@PA_SOEXT@ +### If you want to allow TCP connections, set access to "remote" or "local,remote". +load-module module-dbus-protocol access=local +.endif load-module module-native-protocol-unix ### Automatically restore the volume of streams and devices diff --git a/src/map-file b/src/map-file index c6c8acca..1fffaff9 100644 --- a/src/map-file +++ b/src/map-file @@ -66,6 +66,7 @@ pa_context_get_source_info_list; pa_context_get_source_output_info; pa_context_get_source_output_info_list; pa_context_get_state; +pa_context_get_tile_size; pa_context_is_local; pa_context_is_pending; pa_context_kill_client; @@ -123,13 +124,19 @@ pa_cvolume_avg_mask; pa_cvolume_channels_equal_to; pa_cvolume_compatible; pa_cvolume_compatible_with_channel_map; +pa_cvolume_dec; pa_cvolume_equal; pa_cvolume_get_balance; pa_cvolume_get_fade; pa_cvolume_get_position; +pa_cvolume_inc; +pa_cvolume_inc_clamp; pa_cvolume_init; pa_cvolume_max; pa_cvolume_max_mask; +pa_cvolume_merge; +pa_cvolume_min; +pa_cvolume_min_mask; pa_cvolume_remap; pa_cvolume_scale; pa_cvolume_scale_mask; @@ -139,6 +146,14 @@ pa_cvolume_set_fade; pa_cvolume_set_position; pa_cvolume_snprint; pa_cvolume_valid; +pa_ext_device_manager_delete; +pa_ext_device_manager_enable_role_device_priority_routing; +pa_ext_device_manager_read; +pa_ext_device_manager_reorder_devices_for_role; +pa_ext_device_manager_set_device_description; +pa_ext_device_manager_set_subscribe_cb; +pa_ext_device_manager_subscribe; +pa_ext_device_manager_test; pa_ext_stream_restore_delete; pa_ext_stream_restore_read; pa_ext_stream_restore_set_subscribe_cb; @@ -180,6 +195,7 @@ pa_path_get_filename; pa_proplist_clear; pa_proplist_contains; pa_proplist_copy; +pa_proplist_equal; pa_proplist_free; pa_proplist_from_string; pa_proplist_get; diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index 6a0b4ab7..8b13239c 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -479,7 +479,6 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; pa_channel_position_mask_t mask = 0; - pa_volume_t max_channel_volume = PA_VOLUME_MUTED; unsigned k; pa_assert(m); @@ -546,9 +545,6 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann f = from_alsa_volume(value, e->min_volume, e->max_volume); } - if (f > max_channel_volume) - max_channel_volume = f; - for (k = 0; k < cm->channels; k++) if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) if (v->values[k] < f) @@ -559,7 +555,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann for (k = 0; k < cm->channels; k++) if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) - v->values[k] = max_channel_volume; + v->values[k] = PA_VOLUME_NORM; return 0; } @@ -681,7 +677,6 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; pa_channel_position_mask_t mask = 0; - pa_volume_t max_channel_volume = PA_VOLUME_MUTED; unsigned k; pa_assert(m); @@ -771,9 +766,6 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann f = from_alsa_volume(value, e->min_volume, e->max_volume); } - if (f > max_channel_volume) - max_channel_volume = f; - for (k = 0; k < cm->channels; k++) if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) if (rv.values[k] < f) @@ -784,7 +776,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann for (k = 0; k < cm->channels; k++) if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) - rv.values[k] = max_channel_volume; + rv.values[k] = PA_VOLUME_NORM; *v = rv; return 0; @@ -929,7 +921,7 @@ static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) { int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) { pa_alsa_element *e; - int r; + int r = 0; pa_assert(m); pa_assert(p); @@ -1716,11 +1708,13 @@ static int option_verify(pa_alsa_option *o) { { "input-radio", N_("Radio") }, { "input-video", N_("Video") }, { "input-agc-on", N_("Automatic Gain Control") }, - { "input-agc-off", "" }, + { "input-agc-off", N_("No Automatic Gain Control") }, { "input-boost-on", N_("Boost") }, - { "input-boost-off", "" }, + { "input-boost-off", N_("No Boost") }, { "output-amplifier-on", N_("Amplifier") }, - { "output-amplifier-off", "" } + { "output-amplifier-off", N_("No Amplifier") }, + { "output-speaker", N_("Speaker") }, + { "output-headphones", N_("Headphones") } }; pa_assert(o); @@ -1778,15 +1772,17 @@ static int element_verify(pa_alsa_element *e) { static int path_verify(pa_alsa_path *p) { static const struct description_map well_known_descriptions[] = { - { "analog-input", N_("Analog Input") }, - { "analog-input-microphone", N_("Analog Microphone") }, - { "analog-input-linein", N_("Analog Line-In") }, - { "analog-input-radio", N_("Analog Radio") }, - { "analog-input-video", N_("Analog Video") }, - { "analog-output", N_("Analog Output") }, - { "analog-output-headphones", N_("Analog Headphones") }, - { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") }, - { "analog-output-mono", N_("Analog Mono Output") } + { "analog-input", N_("Analog Input") }, + { "analog-input-microphone", N_("Analog Microphone") }, + { "analog-input-linein", N_("Analog Line-In") }, + { "analog-input-radio", N_("Analog Radio") }, + { "analog-input-video", N_("Analog Video") }, + { "analog-output", N_("Analog Output") }, + { "analog-output-headphones", N_("Analog Headphones") }, + { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") }, + { "analog-output-mono", N_("Analog Mono Output") }, + { "analog-output-headphones-2", N_("Analog Headphones 2") }, + { "analog-output-speaker", N_("Analog Speaker") } }; pa_alsa_element *e; @@ -1849,7 +1845,12 @@ pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction) items[1].data = &p->description; items[2].data = &p->name; - fn = pa_maybe_prefix_path(fname, PA_ALSA_PATHS_DIR); + fn = pa_maybe_prefix_path(fname, +#if defined(__linux__) && !defined(__OPTIMIZE__) + pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" : +#endif + PA_ALSA_PATHS_DIR); + r = pa_config_parse(fn, NULL, items, p); pa_xfree(fn); @@ -2838,9 +2839,9 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { if (bonus) { if (pa_channel_map_equal(&m->channel_map, bonus)) - m->priority += 5000; + m->priority += 50; else if (m->channel_map.channels == bonus->channels) - m->priority += 4000; + m->priority += 30; } return 0; @@ -2884,7 +2885,7 @@ static void profile_set_add_auto_pair( else name = pa_sprintf_malloc("input:%s", n->name); - if ((p = pa_hashmap_get(ps->profiles, name))) { + if (pa_hashmap_get(ps->profiles, name)) { pa_xfree(name); return; } @@ -3110,7 +3111,12 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel if (!fname) fname = "default.conf"; - fn = pa_maybe_prefix_path(fname, PA_ALSA_PROFILE_SETS_DIR); + fn = pa_maybe_prefix_path(fname, +#if defined(__linux__) && !defined(__OPTIMIZE__) + pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" : +#endif + PA_ALSA_PROFILE_SETS_DIR); + r = pa_config_parse(fn, NULL, items, ps); pa_xfree(fn); @@ -3135,7 +3141,13 @@ fail: return NULL; } -void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss) { +void pa_alsa_profile_set_probe( + pa_alsa_profile_set *ps, + const char *dev_id, + const pa_sample_spec *ss, + unsigned default_n_fragments, + unsigned default_fragment_size_msec) { + void *state; pa_alsa_profile *p, *last = NULL; pa_alsa_mapping *m; @@ -3150,6 +3162,7 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons PA_HASHMAP_FOREACH(p, ps->profiles, state) { pa_sample_spec try_ss; pa_channel_map try_map; + snd_pcm_uframes_t try_period_size, try_buffer_size; uint32_t idx; /* Is this already marked that it is supported? (i.e. from the config file) */ @@ -3203,13 +3216,18 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons try_ss = *ss; try_ss.channels = try_map.channels; + try_period_size = + pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / + pa_frame_size(&try_ss); + try_buffer_size = default_n_fragments * try_period_size; + if (!(m ->output_pcm = pa_alsa_open_by_template( m->device_strings, dev_id, NULL, &try_ss, &try_map, SND_PCM_STREAM_PLAYBACK, - NULL, NULL, 0, NULL, NULL, + &try_period_size, &try_buffer_size, 0, NULL, NULL, TRUE))) { p->supported = FALSE; break; @@ -3227,13 +3245,18 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons try_ss = *ss; try_ss.channels = try_map.channels; + try_period_size = + pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) / + pa_frame_size(&try_ss); + try_buffer_size = default_n_fragments * try_period_size; + if (!(m ->input_pcm = pa_alsa_open_by_template( m->device_strings, dev_id, NULL, &try_ss, &try_map, SND_PCM_STREAM_CAPTURE, - NULL, NULL, 0, NULL, NULL, + &try_period_size, &try_buffer_size, 0, NULL, NULL, TRUE))) { p->supported = FALSE; break; diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h index 76788183..a0d4fcbe 100644 --- a/src/modules/alsa/alsa-mixer.h +++ b/src/modules/alsa/alsa-mixer.h @@ -269,7 +269,7 @@ void pa_alsa_mapping_dump(pa_alsa_mapping *m); void pa_alsa_profile_dump(pa_alsa_profile *p); pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus); -void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss); +void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); void pa_alsa_profile_set_free(pa_alsa_profile_set *s); void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 1c38430f..856adb14 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -62,11 +62,26 @@ /* #define DEBUG_TIMING */ #define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */ -#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */ -#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */ -#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */ -#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/ + +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */ + +#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */ +#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms -- When everything's great, decrease watermark by this */ +#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s -- How long after a drop out recheck if things are good now */ +#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms -- If the buffer level ever below this theshold, increase the watermark */ +#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms -- If the buffer level didn't drop below this theshold in the verification time, decrease the watermark */ + +/* Note that TSCHED_WATERMARK_INC_THRESHOLD_USEC == 0 means tht we + * will increase the watermark only if we hit a real underrun. */ + +#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */ +#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/ + +#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms -- min smoother update interval */ +#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms -- max smoother update inteval */ + +#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ struct userdata { pa_core *core; @@ -94,9 +109,13 @@ struct userdata { hwbuf_unused, min_sleep, min_wakeup, - watermark_step; + watermark_inc_step, + watermark_dec_step, + watermark_inc_threshold, + watermark_dec_threshold; + + pa_usec_t watermark_dec_not_before; - unsigned nfragments; pa_memchunk memchunk; char *device_name; /* name of the PCM device */ @@ -113,6 +132,8 @@ struct userdata { pa_smoother *smoother; uint64_t write_count; uint64_t since_start; + pa_usec_t smoother_interval; + pa_usec_t last_smoother_update; pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; @@ -241,6 +262,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; max_use_2 = pa_frame_align(max_use/2, &u->sink->sample_spec); @@ -255,6 +277,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) { static void fix_tsched_watermark(struct userdata *u) { size_t max_use; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; @@ -265,7 +288,7 @@ static void fix_tsched_watermark(struct userdata *u) { u->tsched_watermark = u->min_wakeup; } -static void adjust_after_underrun(struct userdata *u) { +static void increase_watermark(struct userdata *u) { size_t old_watermark; pa_usec_t old_min_latency, new_min_latency; @@ -274,31 +297,64 @@ static void adjust_after_underrun(struct userdata *u) { /* First, just try to increase the watermark */ old_watermark = u->tsched_watermark; - u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step); + u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step); fix_tsched_watermark(u); if (old_watermark != u->tsched_watermark) { - pa_log_notice("Increasing wakeup watermark to %0.2f ms", - (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + pa_log_info("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); return; } /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */ old_min_latency = u->sink->thread_info.min_latency; - new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC); + new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC); new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency); if (old_min_latency != new_min_latency) { - pa_log_notice("Increasing minimal latency to %0.2f ms", - (double) new_min_latency / PA_USEC_PER_MSEC); + pa_log_info("Increasing minimal latency to %0.2f ms", + (double) new_min_latency / PA_USEC_PER_MSEC); pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency); - return; } /* When we reach this we're officialy fucked! */ } +static void decrease_watermark(struct userdata *u) { + size_t old_watermark; + pa_usec_t now; + + pa_assert(u); + pa_assert(u->use_tsched); + + now = pa_rtclock_now(); + + if (u->watermark_dec_not_before <= 0) + goto restart; + + if (u->watermark_dec_not_before > now) + return; + + old_watermark = u->tsched_watermark; + + if (u->tsched_watermark < u->watermark_dec_step) + u->tsched_watermark = u->tsched_watermark / 2; + else + u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step); + + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_info("Decreasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + + /* We don't change the latency range*/ + +restart: + u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC; +} + static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { pa_usec_t usec, wm; @@ -306,6 +362,7 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p pa_assert(process_usec); pa_assert(u); + pa_assert(u->use_tsched); usec = pa_sink_get_requested_latency_within_thread(u->sink); @@ -353,42 +410,65 @@ static int try_recover(struct userdata *u, const char *call, int err) { return 0; } -static size_t check_left_to_play(struct userdata *u, size_t n_bytes) { +static size_t check_left_to_play(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) { size_t left_to_play; + pa_bool_t underrun = FALSE; /* We use <= instead of < for this check here because an underrun * only happens after the last sample was processed, not already when * it is removed from the buffer. This is particularly important * when block transfer is used. */ - if (n_bytes <= u->hwbuf_size) { + if (n_bytes <= u->hwbuf_size) left_to_play = u->hwbuf_size - n_bytes; + else { + + /* We got a dropout. What a mess! */ + left_to_play = 0; + underrun = TRUE; #ifdef DEBUG_TIMING - pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + PA_DEBUG_TRAP; #endif - } else { - left_to_play = 0; + if (!u->first && !u->after_rewind) + if (pa_log_ratelimit()) + pa_log_info("Underrun!"); + } #ifdef DEBUG_TIMING - PA_DEBUG_TRAP; + pa_log_debug("%0.2f ms left to play; inc threshold = %0.2f ms; dec threshold = %0.2f ms", + (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(u->watermark_inc_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(u->watermark_dec_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC); #endif + if (u->use_tsched) { + pa_bool_t reset_not_before = TRUE; + if (!u->first && !u->after_rewind) { + if (underrun || left_to_play < u->watermark_inc_threshold) + increase_watermark(u); + else if (left_to_play > u->watermark_dec_threshold) { + reset_not_before = FALSE; - if (pa_log_ratelimit()) - pa_log_info("Underrun!"); + /* We decrease the watermark only if have actually + * been woken up by a timeout. If something else woke + * us up it's too easy to fulfill the deadlines... */ - if (u->use_tsched) - adjust_after_underrun(u); + if (on_timeout) + decrease_watermark(u); + } } + + if (reset_not_before) + u->watermark_dec_not_before = 0; } return left_to_play; } -static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { pa_bool_t work_done = TRUE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_play; @@ -423,7 +503,8 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle pa_log_debug("avail: %lu", (unsigned long) n_bytes); #endif - left_to_play = check_left_to_play(u, n_bytes); + left_to_play = check_left_to_play(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) @@ -558,7 +639,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle return work_done ? 1 : 0; } -static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { pa_bool_t work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_play; @@ -574,6 +655,7 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle snd_pcm_sframes_t n; size_t n_bytes; int r; + pa_bool_t after_avail = TRUE; if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { @@ -584,7 +666,8 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle } n_bytes = (size_t) n * u->frame_size; - left_to_play = check_left_to_play(u, n_bytes); + left_to_play = check_left_to_play(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) @@ -627,7 +710,6 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle for (;;) { snd_pcm_sframes_t frames; void *p; - pa_bool_t after_avail = TRUE; /* pa_log_debug("%lu frames to write", (unsigned long) frames); */ @@ -721,18 +803,27 @@ static void update_smoother(struct userdata *u) { now1 = pa_timespec_load(&htstamp); } + /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */ + if (now1 <= 0) + now1 = pa_rtclock_now(); + + /* check if the time since the last update is bigger than the interval */ + if (u->last_smoother_update > 0) + if (u->last_smoother_update + u->smoother_interval > now1) + return; + position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) u->frame_size); if (PA_UNLIKELY(position < 0)) position = 0; - /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */ - if (now1 <= 0) - now1 = pa_rtclock_now(); - now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec); pa_smoother_put(u->smoother, now1, now2); + + u->last_smoother_update = now1; + /* exponentially increase the update interval up to the MAX limit */ + u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL); } static pa_usec_t sink_get_latency(struct userdata *u) { @@ -836,7 +927,7 @@ static int update_sw_params(struct userdata *u) { pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); - if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) { pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err)); return err; } @@ -851,8 +942,7 @@ static int unsuspend(struct userdata *u) { pa_sample_spec ss; int err; pa_bool_t b, d; - unsigned nfrags; - snd_pcm_uframes_t period_size; + snd_pcm_uframes_t period_size, buffer_size; pa_assert(u); pa_assert(!u->pcm_handle); @@ -860,7 +950,7 @@ static int unsuspend(struct userdata *u) { pa_log_info("Trying resume..."); if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, - /*SND_PCM_NONBLOCK|*/ + SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| SND_PCM_NO_AUTO_FORMAT)) < 0) { @@ -869,12 +959,12 @@ static int unsuspend(struct userdata *u) { } ss = u->sink->sample_spec; - nfrags = u->nfragments; period_size = u->fragment_size / u->frame_size; + buffer_size = u->hwbuf_size / u->frame_size; b = u->use_mmap; d = u->use_tsched; - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) { pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err)); goto fail; } @@ -889,10 +979,11 @@ static int unsuspend(struct userdata *u) { goto fail; } - if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", - (unsigned long) u->nfragments, (unsigned long) u->fragment_size, - (unsigned long) nfrags, period_size * u->frame_size); + if (period_size*u->frame_size != u->fragment_size || + buffer_size*u->frame_size != u->hwbuf_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)", + (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size, + (unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size)); goto fail; } @@ -904,11 +995,12 @@ static int unsuspend(struct userdata *u) { u->write_count = 0; pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; + u->last_smoother_update = 0; u->first = TRUE; u->since_start = 0; - pa_log_info("Resumed successfully..."); return 0; @@ -919,7 +1011,7 @@ fail: u->pcm_handle = NULL; } - return -1; + return -PA_ERR_IO; } /* Called from IO context */ @@ -943,28 +1035,33 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { - case PA_SINK_SUSPENDED: + case PA_SINK_SUSPENDED: { + int r; + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); - if (suspend(u) < 0) - return -1; + if ((r = suspend(u)) < 0) + return r; break; + } case PA_SINK_IDLE: - case PA_SINK_RUNNING: + case PA_SINK_RUNNING: { + int r; if (u->sink->thread_info.state == PA_SINK_INIT) { if (build_pollfd(u) < 0) - return -1; + return -PA_ERR_IO; } if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { - if (unsuspend(u) < 0) - return -1; + if ((r = unsuspend(u)) < 0) + return r; } break; + } case PA_SINK_UNLINKED: case PA_SINK_INIT: @@ -992,7 +1089,7 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t new_state) { reserve_done(u); else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state)) if (reserve_init(u, u->device_name) < 0) - return -1; + return -PA_ERR_BUSY; return 0; } @@ -1007,7 +1104,7 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { return 0; if (mask & SND_CTL_EVENT_MASK_VALUE) { - pa_sink_get_volume(u->sink, TRUE, FALSE); + pa_sink_get_volume(u->sink, TRUE); pa_sink_get_mute(u->sink, TRUE); } @@ -1034,15 +1131,11 @@ static void sink_get_volume_cb(pa_sink *s) { if (pa_cvolume_equal(&u->hardware_volume, &r)) return; - s->virtual_volume = u->hardware_volume = r; + s->real_volume = u->hardware_volume = r; - if (u->mixer_path->has_dB) { - pa_cvolume reset; - - /* Hmm, so the hardware volume changed, let's reset our software volume */ - pa_cvolume_reset(&reset, s->sample_spec.channels); - pa_sink_set_soft_volume(s, &reset); - } + /* Hmm, so the hardware volume changed, let's reset our software volume */ + if (u->mixer_path->has_dB) + pa_sink_set_soft_volume(s, NULL); } static void sink_set_volume_cb(pa_sink *s) { @@ -1055,7 +1148,7 @@ static void sink_set_volume_cb(pa_sink *s) { pa_assert(u->mixer_handle); /* Shift up by the base volume */ - pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume); + pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume); if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) return; @@ -1066,13 +1159,26 @@ static void sink_set_volume_cb(pa_sink *s) { u->hardware_volume = r; if (u->mixer_path->has_dB) { + pa_cvolume new_soft_volume; + pa_bool_t accurate_enough; /* Match exactly what the user requested by software */ - pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume); + pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume); + + /* If the adjustment to do in software is only minimal we + * can skip it. That saves us CPU at the expense of a bit of + * accuracy */ + accurate_enough = + (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume)); + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume)); pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); - pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume)); + pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume), + pa_yes_no(accurate_enough)); + + if (!accurate_enough) + s->soft_volume = new_soft_volume; } else { pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); @@ -1080,7 +1186,7 @@ static void sink_set_volume_cb(pa_sink *s) { /* We can't match exactly what the user requested, hence let's * at least tell the user about it */ - s->virtual_volume = r; + s->real_volume = r; } } @@ -1252,15 +1358,16 @@ static void thread_func(void *userdata) { if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { int work_done; pa_usec_t sleep_usec = 0; + pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll); if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) if (process_rewind(u) < 0) goto fail; if (u->use_mmap) - work_done = mmap_write(u, &sleep_usec, revents & POLLOUT); + work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout); else - work_done = unix_write(u, &sleep_usec, revents & POLLOUT); + work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout); if (work_done < 0) goto fail; @@ -1292,7 +1399,8 @@ static void thread_func(void *userdata) { * we have filled the buffer at least once * completely.*/ - pa_log_debug("Cutting sleep time for the initial iterations by half."); + if (pa_log_ratelimit()) + pa_log_debug("Cutting sleep time for the initial iterations by half."); sleep_usec /= 2; } @@ -1534,8 +1642,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca const char *dev_id = NULL; pa_sample_spec ss, requested_ss; pa_channel_map map; - uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; - snd_pcm_uframes_t period_frames, tsched_frames; + uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames; size_t frame_size; pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; pa_sink_new_data data; @@ -1569,8 +1677,10 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } - hwbuf_size = frag_size * nfrags; + buffer_size = nfrags * frag_size; + period_frames = frag_size/frame_size; + buffer_frames = buffer_size/frame_size; tsched_frames = tsched_size/frame_size; if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { @@ -1588,10 +1698,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } - if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); - use_tsched = FALSE; - } + use_tsched = pa_alsa_may_tsched(use_tsched); u = pa_xnew0(struct userdata, 1); u->core = m->core; @@ -1610,6 +1717,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca 5, pa_rtclock_now(), TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; dev_id = pa_modargs_get_value( ma, "device_id", @@ -1636,7 +1744,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, mapping))) goto fail; @@ -1651,7 +1759,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, profile_set, &mapping))) goto fail; @@ -1663,7 +1771,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, FALSE))) goto fail; } @@ -1689,11 +1797,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca u->use_tsched = use_tsched = FALSE; } - if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) { - pa_log_info("Device is not a hardware device, disabling timer-based scheduling."); - u->use_tsched = use_tsched = FALSE; - } - if (u->use_mmap) pa_log_info("Successfully enabled mmap() mode."); @@ -1715,7 +1818,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size)); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); @@ -1756,21 +1859,28 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_sink_set_rtpoll(u->sink, u->rtpoll); u->frame_size = frame_size; - u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); - u->nfragments = nfrags; - u->hwbuf_size = u->fragment_size * nfrags; - u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec); + u->fragment_size = frag_size = (size_t) (period_frames * frame_size); + u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size); pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels); - pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", - nfrags, (long unsigned) u->fragment_size, + pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)", + (double) u->hwbuf_size / (double) u->fragment_size, + (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC, + (long unsigned) u->hwbuf_size, (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); pa_sink_set_max_request(u->sink, u->hwbuf_size); pa_sink_set_max_rewind(u->sink, u->hwbuf_size); if (u->use_tsched) { - u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->sink->sample_spec); + u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec); + + u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->sink->sample_spec); + u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->sink->sample_spec); + + u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->sink->sample_spec); + u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->sink->sample_spec); fix_min_sleep_wakeup(u); fix_tsched_watermark(u); @@ -1784,6 +1894,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca } else pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss)); + reserve_update(u); if (update_sw_params(u) < 0) diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 9a51f857..e775b20c 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -59,11 +59,24 @@ /* #define DEBUG_TIMING */ #define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ -#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ -#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ -#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ -#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */ + +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ + +#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ +#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms */ +#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s */ +#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms */ +#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms */ +#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ + +#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ +#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */ + +#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms */ +#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms */ + +#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) struct userdata { pa_core *core; @@ -91,9 +104,12 @@ struct userdata { hwbuf_unused, min_sleep, min_wakeup, - watermark_step; + watermark_inc_step, + watermark_dec_step, + watermark_inc_threshold, + watermark_dec_threshold; - unsigned nfragments; + pa_usec_t watermark_dec_not_before; char *device_name; char *control_device; @@ -106,6 +122,8 @@ struct userdata { pa_smoother *smoother; uint64_t read_count; + pa_usec_t smoother_interval; + pa_usec_t last_smoother_update; pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; @@ -234,6 +252,7 @@ static int reserve_monitor_init(struct userdata *u, const char *dname) { static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec); @@ -248,6 +267,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) { static void fix_tsched_watermark(struct userdata *u) { size_t max_use; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; @@ -258,7 +278,7 @@ static void fix_tsched_watermark(struct userdata *u) { u->tsched_watermark = u->min_wakeup; } -static void adjust_after_overrun(struct userdata *u) { +static void increase_watermark(struct userdata *u) { size_t old_watermark; pa_usec_t old_min_latency, new_min_latency; @@ -267,36 +287,72 @@ static void adjust_after_overrun(struct userdata *u) { /* First, just try to increase the watermark */ old_watermark = u->tsched_watermark; - u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step); - + u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step); fix_tsched_watermark(u); if (old_watermark != u->tsched_watermark) { - pa_log_notice("Increasing wakeup watermark to %0.2f ms", - (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); + pa_log_info("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); return; } /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */ old_min_latency = u->source->thread_info.min_latency; - new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC); + new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC); new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency); if (old_min_latency != new_min_latency) { - pa_log_notice("Increasing minimal latency to %0.2f ms", - (double) new_min_latency / PA_USEC_PER_MSEC); + pa_log_info("Increasing minimal latency to %0.2f ms", + (double) new_min_latency / PA_USEC_PER_MSEC); pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency); - return; } /* When we reach this we're officialy fucked! */ } +static void decrease_watermark(struct userdata *u) { + size_t old_watermark; + pa_usec_t now; + + pa_assert(u); + pa_assert(u->use_tsched); + + now = pa_rtclock_now(); + + if (u->watermark_dec_not_before <= 0) + goto restart; + + if (u->watermark_dec_not_before > now) + return; + + old_watermark = u->tsched_watermark; + + if (u->tsched_watermark < u->watermark_dec_step) + u->tsched_watermark = u->tsched_watermark / 2; + else + u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step); + + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_info("Decreasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); + + /* We don't change the latency range*/ + +restart: + u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC; +} + static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { pa_usec_t wm, usec; + pa_assert(sleep_usec); + pa_assert(process_usec); + pa_assert(u); + pa_assert(u->use_tsched); usec = pa_source_get_requested_latency_within_thread(u->source); @@ -345,24 +401,23 @@ static int try_recover(struct userdata *u, const char *call, int err) { return 0; } -static size_t check_left_to_record(struct userdata *u, size_t n_bytes) { +static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) { size_t left_to_record; size_t rec_space = u->hwbuf_size - u->hwbuf_unused; + pa_bool_t overrun = FALSE; /* We use <= instead of < for this check here because an overrun * only happens after the last sample was processed, not already when * it is removed from the buffer. This is particularly important * when block transfer is used. */ - if (n_bytes <= rec_space) { + if (n_bytes <= rec_space) left_to_record = rec_space - n_bytes; + else { -#ifdef DEBUG_TIMING - pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); -#endif - - } else { + /* We got a dropout. What a mess! */ left_to_record = 0; + overrun = TRUE; #ifdef DEBUG_TIMING PA_DEBUG_TRAP; @@ -370,15 +425,36 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes) { if (pa_log_ratelimit()) pa_log_info("Overrun!"); + } - if (u->use_tsched) - adjust_after_overrun(u); +#ifdef DEBUG_TIMING + pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); +#endif + + if (u->use_tsched) { + pa_bool_t reset_not_before = TRUE; + + if (overrun || left_to_record < u->watermark_inc_threshold) + increase_watermark(u); + else if (left_to_record > u->watermark_dec_threshold) { + reset_not_before = FALSE; + + /* We decrease the watermark only if have actually been + * woken up by a timeout. If something else woke us up + * it's too easy to fulfill the deadlines... */ + + if (on_timeout) + decrease_watermark(u); + } + + if (reset_not_before) + u->watermark_dec_not_before = 0; } return left_to_record; } -static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { pa_bool_t work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_record; @@ -410,7 +486,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled pa_log_debug("avail: %lu", (unsigned long) n_bytes); #endif - left_to_record = check_left_to_record(u, n_bytes); + left_to_record = check_left_to_record(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) if (!polled && @@ -536,7 +613,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled return work_done ? 1 : 0; } -static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { int work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_record; @@ -563,7 +640,8 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled } n_bytes = (size_t) n * u->frame_size; - left_to_record = check_left_to_record(u, n_bytes); + left_to_record = check_left_to_record(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) if (!polled && @@ -689,15 +767,23 @@ static void update_smoother(struct userdata *u) { now1 = pa_timespec_load(&htstamp); } - position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size); - /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */ if (now1 <= 0) now1 = pa_rtclock_now(); + /* check if the time since the last update is bigger than the interval */ + if (u->last_smoother_update > 0) + if (u->last_smoother_update + u->smoother_interval > now1) + return; + + position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size); now2 = pa_bytes_to_usec(position, &u->source->sample_spec); pa_smoother_put(u->smoother, now1, now2); + + u->last_smoother_update = now1; + /* exponentially increase the update interval up to the MAX limit */ + u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL); } static pa_usec_t source_get_latency(struct userdata *u) { @@ -791,7 +877,7 @@ static int update_sw_params(struct userdata *u) { pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); - if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) { pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err)); return err; } @@ -803,8 +889,7 @@ static int unsuspend(struct userdata *u) { pa_sample_spec ss; int err; pa_bool_t b, d; - unsigned nfrags; - snd_pcm_uframes_t period_size; + snd_pcm_uframes_t period_size, buffer_size; pa_assert(u); pa_assert(!u->pcm_handle); @@ -812,7 +897,7 @@ static int unsuspend(struct userdata *u) { pa_log_info("Trying resume..."); if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, - /*SND_PCM_NONBLOCK|*/ + SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| SND_PCM_NO_AUTO_FORMAT)) < 0) { @@ -821,12 +906,12 @@ static int unsuspend(struct userdata *u) { } ss = u->source->sample_spec; - nfrags = u->nfragments; period_size = u->fragment_size / u->frame_size; + buffer_size = u->hwbuf_size / u->frame_size; b = u->use_mmap; d = u->use_tsched; - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) { pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err)); goto fail; } @@ -841,10 +926,11 @@ static int unsuspend(struct userdata *u) { goto fail; } - if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", - (unsigned long) u->nfragments, (unsigned long) u->fragment_size, - (unsigned long) nfrags, period_size * u->frame_size); + if (period_size*u->frame_size != u->fragment_size || + buffer_size*u->frame_size != u->hwbuf_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)", + (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size, + (unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size)); goto fail; } @@ -860,6 +946,8 @@ static int unsuspend(struct userdata *u) { u->read_count = 0; pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; + u->last_smoother_update = 0; pa_log_info("Resumed successfully..."); @@ -871,7 +959,7 @@ fail: u->pcm_handle = NULL; } - return -1; + return -PA_ERR_IO; } static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { @@ -894,30 +982,34 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { - case PA_SOURCE_SUSPENDED: + case PA_SOURCE_SUSPENDED: { + int r; pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); - if (suspend(u) < 0) - return -1; + if ((r = suspend(u)) < 0) + return r; break; + } case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: + case PA_SOURCE_RUNNING: { + int r; if (u->source->thread_info.state == PA_SOURCE_INIT) { if (build_pollfd(u) < 0) - return -1; + return -PA_ERR_IO; snd_pcm_start(u->pcm_handle); } if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { - if (unsuspend(u) < 0) - return -1; + if ((r = unsuspend(u)) < 0) + return r; } break; + } case PA_SOURCE_UNLINKED: case PA_SOURCE_INIT: @@ -945,7 +1037,7 @@ static int source_set_state_cb(pa_source *s, pa_source_state_t new_state) { reserve_done(u); else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state)) if (reserve_init(u, u->device_name) < 0) - return -1; + return -PA_ERR_BUSY; return 0; } @@ -987,15 +1079,11 @@ static void source_get_volume_cb(pa_source *s) { if (pa_cvolume_equal(&u->hardware_volume, &r)) return; - s->virtual_volume = u->hardware_volume = r; - - if (u->mixer_path->has_dB) { - pa_cvolume reset; + s->volume = u->hardware_volume = r; - /* Hmm, so the hardware volume changed, let's reset our software volume */ - pa_cvolume_reset(&reset, s->sample_spec.channels); - pa_source_set_soft_volume(s, &reset); - } + /* Hmm, so the hardware volume changed, let's reset our software volume */ + if (u->mixer_path->has_dB) + pa_source_set_soft_volume(s, NULL); } static void source_set_volume_cb(pa_source *s) { @@ -1008,7 +1096,7 @@ static void source_set_volume_cb(pa_source *s) { pa_assert(u->mixer_handle); /* Shift up by the base volume */ - pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume); + pa_sw_cvolume_divide_scalar(&r, &s->volume, s->base_volume); if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) return; @@ -1019,13 +1107,26 @@ static void source_set_volume_cb(pa_source *s) { u->hardware_volume = r; if (u->mixer_path->has_dB) { + pa_cvolume new_soft_volume; + pa_bool_t accurate_enough; /* Match exactly what the user requested by software */ - pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume); + pa_sw_cvolume_divide(&new_soft_volume, &s->volume, &u->hardware_volume); - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume)); + /* If the adjustment to do in software is only minimal we + * can skip it. That saves us CPU at the expense of a bit of + * accuracy */ + accurate_enough = + (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); + + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume)); pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); - pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume)); + pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume), + pa_yes_no(accurate_enough)); + + if (!accurate_enough) + s->soft_volume = new_soft_volume; } else { pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); @@ -1033,7 +1134,7 @@ static void source_set_volume_cb(pa_source *s) { /* We can't match exactly what the user requested, hence let's * at least tell the user about it */ - s->virtual_volume = r; + s->volume = r; } } @@ -1132,11 +1233,12 @@ static void thread_func(void *userdata) { if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { int work_done; pa_usec_t sleep_usec = 0; + pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll); if (u->use_mmap) - work_done = mmap_read(u, &sleep_usec, revents & POLLIN); + work_done = mmap_read(u, &sleep_usec, revents & POLLIN, on_timeout); else - work_done = unix_read(u, &sleep_usec, revents & POLLIN); + work_done = unix_read(u, &sleep_usec, revents & POLLIN, on_timeout); if (work_done < 0) goto fail; @@ -1383,8 +1485,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p const char *dev_id = NULL; pa_sample_spec ss, requested_ss; pa_channel_map map; - uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; - snd_pcm_uframes_t period_frames, tsched_frames; + uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames; size_t frame_size; pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; pa_source_new_data data; @@ -1418,8 +1520,10 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } - hwbuf_size = frag_size * nfrags; + buffer_size = nfrags * frag_size; + period_frames = frag_size/frame_size; + buffer_frames = buffer_size/frame_size; tsched_frames = tsched_size/frame_size; if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { @@ -1437,10 +1541,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } - if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); - use_tsched = FALSE; - } + use_tsched = pa_alsa_may_tsched(use_tsched); u = pa_xnew0(struct userdata, 1); u->core = m->core; @@ -1458,6 +1559,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p 5, pa_rtclock_now(), FALSE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; dev_id = pa_modargs_get_value( ma, "device_id", @@ -1484,7 +1586,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, mapping))) goto fail; @@ -1498,7 +1600,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, profile_set, &mapping))) goto fail; @@ -1509,7 +1611,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, FALSE))) goto fail; } @@ -1535,11 +1637,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p u->use_tsched = use_tsched = FALSE; } - if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) { - pa_log_info("Device is not a hardware device, disabling timer-based scheduling."); - u->use_tsched = use_tsched = FALSE; - } - if (u->use_mmap) pa_log_info("Successfully enabled mmap() mode."); @@ -1561,7 +1658,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size)); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); @@ -1602,18 +1699,25 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_source_set_rtpoll(u->source, u->rtpoll); u->frame_size = frame_size; - u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); - u->nfragments = nfrags; - u->hwbuf_size = u->fragment_size * nfrags; - u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec); + u->fragment_size = frag_size = (size_t) (period_frames * frame_size); + u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size); pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels); - pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", - nfrags, (long unsigned) u->fragment_size, + pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)", + (double) u->hwbuf_size / (double) u->fragment_size, + (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC, + (long unsigned) u->hwbuf_size, (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); if (u->use_tsched) { - u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->source->sample_spec); + u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec); + + u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->source->sample_spec); + u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->source->sample_spec); + + u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->source->sample_spec); + u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->source->sample_spec); fix_min_sleep_wakeup(u); fix_tsched_watermark(u); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index a47a8958..b8d13575 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -43,6 +43,7 @@ #include <pulsecore/once.h> #include <pulsecore/thread.h> #include <pulsecore/conf-parser.h> +#include <pulsecore/core-rtclock.h> #include "alsa-util.h" #include "alsa-mixer.h" @@ -93,6 +94,7 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s int ret; pa_assert(pcm_handle); + pa_assert(hwparams); pa_assert(f); if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) @@ -148,33 +150,71 @@ try_auto: return -1; } +static int set_period_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) { + snd_pcm_uframes_t s; + int d, ret; + + pa_assert(pcm_handle); + pa_assert(hwparams); + + s = size; + d = 0; + if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) { + s = size; + d = -1; + if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) { + s = size; + d = 1; + if ((ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d)) < 0) { + pa_log_info("snd_pcm_hw_params_set_period_size_near() failed: %s", pa_alsa_strerror(ret)); + return ret; + } + } + } + + return 0; +} + +static int set_buffer_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) { + int ret; + + pa_assert(pcm_handle); + pa_assert(hwparams); + + if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &size)) < 0) { + pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); + return ret; + } + + return 0; +} + /* Set the hardware parameters of the given ALSA device. Returns the - * selected fragment settings in *period and *period_size */ + * selected fragment settings in *buffer_size and *period_size. If tsched mode can be enabled */ int pa_alsa_set_hw_params( snd_pcm_t *pcm_handle, pa_sample_spec *ss, - uint32_t *periods, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, pa_bool_t require_exact_channel_number) { int ret = -1; + snd_pcm_hw_params_t *hwparams, *hwparams_copy; + int dir; snd_pcm_uframes_t _period_size = period_size ? *period_size : 0; - unsigned int _periods = periods ? *periods : 0; - unsigned int r = ss->rate; - unsigned int c = ss->channels; - pa_sample_format_t f = ss->format; - snd_pcm_hw_params_t *hwparams; + snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0; pa_bool_t _use_mmap = use_mmap && *use_mmap; pa_bool_t _use_tsched = use_tsched && *use_tsched; - int dir; + pa_sample_spec _ss = *ss; pa_assert(pcm_handle); pa_assert(ss); snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_hw_params_alloca(&hwparams_copy); if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) { pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); @@ -208,114 +248,143 @@ int pa_alsa_set_hw_params( if (!_use_mmap) _use_tsched = FALSE; - if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) + if (!pa_alsa_pcm_is_hw(pcm_handle)) + _use_tsched = FALSE; + + if ((ret = set_format(pcm_handle, hwparams, &_ss.format)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) { + if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &_ss.rate, NULL)) < 0) { pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret)); goto finish; } if (require_exact_channel_number) { - if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) { - pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", c, pa_alsa_strerror(ret)); + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); goto finish; } } else { + unsigned int c = _ss.channels; + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) { - pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", c, pa_alsa_strerror(ret)); + pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); goto finish; } - } - if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) { - pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret)); - goto finish; + _ss.channels = c; } - if (_period_size > 0 && tsched_size > 0 && _periods > 0) { - snd_pcm_uframes_t buffer_size; - unsigned int p; + if (_use_tsched && tsched_size > 0) { + _buffer_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * _ss.rate) / ss->rate); + _period_size = _buffer_size; + } else { + _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * _ss.rate) / ss->rate); + _buffer_size = (snd_pcm_uframes_t) (((uint64_t) _buffer_size * _ss.rate) / ss->rate); + } - /* Adjust the buffer sizes, if we didn't get the rate we were asking for */ - _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate); - tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate); + if (_buffer_size > 0 || _period_size > 0) { + snd_pcm_uframes_t max_frames = 0; - if (_use_tsched) { - buffer_size = 0; + if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0) + pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret)); + else + pa_log_debug("Maximum hw buffer size is %lu ms", (long unsigned) (max_frames * PA_MSEC_PER_SEC / _ss.rate)); - if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size)) < 0) - pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret)); - else - pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); + /* Some ALSA drivers really don't like if we set the buffer + * size first and the number of periods second. (which would + * make a lot more sense to me) So, try a few combinations + * before we give up. */ + + if (_buffer_size > 0 && _period_size > 0) { + snd_pcm_hw_params_copy(hwparams_copy, hwparams); + + /* First try: set buffer size first, followed by period size */ + if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && + set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set buffer size first, period size second."); + goto success; + } - _period_size = tsched_size; - _periods = 1; + /* Second try: set period size first, followed by buffer size */ + if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && + set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set period size first, buffer size second."); + goto success; + } } - /* Some ALSA drivers really don't like if we set the buffer - * size first and the number of periods second. (which would - * make a lot more sense to me) So, follow this rule and - * adjust the periods first and the buffer size second */ - - /* First we pass 0 as direction to get exactly what we - * asked for. That this is necessary is presumably a bug - * in ALSA. All in all this is mostly a hint to ALSA, so - * we don't care if this fails. */ - - p = _periods; - dir = 0; - if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir) < 0) { - p = _periods; - dir = 1; - if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir) < 0) { - p = _periods; - dir = -1; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir)) < 0) - pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret)); + if (_buffer_size > 0) { + snd_pcm_hw_params_copy(hwparams_copy, hwparams); + + /* Third try: set only buffer size */ + if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set only buffer size second."); + goto success; } } - /* Now set the buffer size */ - buffer_size = _periods * _period_size; - if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) - pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); + if (_period_size > 0) { + snd_pcm_hw_params_copy(hwparams_copy, hwparams); + + /* Fourth try: set only period size */ + if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set only period size second."); + goto success; + } + } } - if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) + pa_log_debug("Set neither period nor buffer size."); + + /* Last chance, set nothing */ + if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { + pa_log_info("snd_pcm_hw_params failed: %s", pa_alsa_strerror(ret)); goto finish; + } + +success: - if (ss->rate != r) - pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r); + if (ss->rate != _ss.rate) + pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, _ss.rate); - if (ss->channels != c) - pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); + if (ss->channels != _ss.channels) + pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, _ss.channels); - if (ss->format != f) - pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); + if (ss->format != _ss.format) + pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(_ss.format)); if ((ret = snd_pcm_prepare(pcm_handle)) < 0) { pa_log_info("snd_pcm_prepare() failed: %s", pa_alsa_strerror(ret)); goto finish; } + if ((ret = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) { + pa_log_info("snd_pcm_hw_params_current() failed: %s", pa_alsa_strerror(ret)); + goto finish; + } + if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 || - (ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0) { - pa_log_info("snd_pcm_hw_params_get_period{s|_size}() failed: %s", pa_alsa_strerror(ret)); + (ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) { + pa_log_info("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", pa_alsa_strerror(ret)); goto finish; } /* If the sample rate deviates too much, we need to resample */ - if (r < ss->rate*.95 || r > ss->rate*1.05) - ss->rate = r; - ss->channels = (uint8_t) c; - ss->format = f; + if (_ss.rate < ss->rate*.95 || _ss.rate > ss->rate*1.05) + ss->rate = _ss.rate; + ss->channels = _ss.channels; + ss->format = _ss.format; - pa_assert(_periods > 0); pa_assert(_period_size > 0); + pa_assert(_buffer_size > 0); - if (periods) - *periods = _periods; + if (buffer_size) + *buffer_size = _buffer_size; if (period_size) *period_size = _period_size; @@ -335,7 +404,7 @@ finish: return ret; } -int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { +int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, pa_bool_t period_event) { snd_pcm_sw_params_t *swparams; snd_pcm_uframes_t boundary; int err; @@ -349,7 +418,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { return err; } - if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, 0)) < 0) { + if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, period_event)) < 0) { pa_log_warn("Unable to disable period event: %s\n", pa_alsa_strerror(err)); return err; } @@ -393,8 +462,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, @@ -410,8 +479,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( pa_assert(dev); pa_assert(ss); pa_assert(map); - pa_assert(nfrags); - pa_assert(period_size); pa_assert(ps); /* First we try to find a device string with a superset of the @@ -433,8 +500,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( ss, map, mode, - nfrags, period_size, + buffer_size, tsched_size, use_mmap, use_tsched, @@ -460,8 +527,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( ss, map, mode, - nfrags, period_size, + buffer_size, tsched_size, use_mmap, use_tsched, @@ -478,7 +545,18 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( /* OK, we didn't find any good device, so let's try the raw hw: stuff */ d = pa_sprintf_malloc("hw:%s", dev_id); pa_log_debug("Trying %s as last resort...", d); - pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE); + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + period_size, + buffer_size, + tsched_size, + use_mmap, + use_tsched, + FALSE); pa_xfree(d); if (pcm_handle && mapping) @@ -493,8 +571,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping( pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, @@ -508,8 +586,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping( pa_assert(dev); pa_assert(ss); pa_assert(map); - pa_assert(nfrags); - pa_assert(period_size); pa_assert(m); try_ss.channels = m->channel_map.channels; @@ -524,8 +600,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping( &try_ss, &try_map, mode, - nfrags, period_size, + buffer_size, tsched_size, use_mmap, use_tsched, @@ -547,8 +623,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, @@ -579,7 +655,15 @@ snd_pcm_t *pa_alsa_open_by_device_string( pa_log_debug("Managed to open %s", d); - if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) { + if ((err = pa_alsa_set_hw_params( + pcm_handle, + ss, + period_size, + buffer_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number)) < 0) { if (!reformat) { reformat = TRUE; @@ -632,8 +716,8 @@ snd_pcm_t *pa_alsa_open_by_template( pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, @@ -653,8 +737,8 @@ snd_pcm_t *pa_alsa_open_by_template( ss, map, mode, - nfrags, period_size, + buffer_size, tsched_size, use_mmap, use_tsched, @@ -900,7 +984,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { snd_ctl_card_info_alloca(&info); if ((err = snd_ctl_open(&ctl, name, 0)) < 0) { - pa_log_warn("Error opening low-level control device '%s'", name); + pa_log_warn("Error opening low-level control device '%s': %s", name, snd_strerror(err)); return; } @@ -1225,3 +1309,26 @@ const char* pa_alsa_strerror(int errnum) { return translated; } + +pa_bool_t pa_alsa_may_tsched(pa_bool_t want) { + + if (!want) + return FALSE; + + if (!pa_rtclock_hrtimer()) { + /* We cannot depend on being woken up in time when the timers + are inaccurate, so let's fallback to classic IO based playback + then. */ + pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + return FALSE; } + + if (pa_running_in_vm()) { + /* We cannot depend on being woken up when we ask for in a VM, + * so let's fallback to classic IO based playback then. */ + pa_log_notice("Disabling timer-based scheduling because running inside a VM."); + return FALSE; + } + + + return TRUE; +} diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 830a922e..1d1256bd 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -42,8 +42,8 @@ int pa_alsa_set_hw_params( snd_pcm_t *pcm_handle, pa_sample_spec *ss, /* modified at return */ - uint32_t *periods, /* modified at return */ snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, /* modified at return */ pa_bool_t *use_tsched, /* modified at return */ @@ -51,7 +51,8 @@ int pa_alsa_set_hw_params( int pa_alsa_set_sw_params( snd_pcm_t *pcm, - snd_pcm_uframes_t avail_min); + snd_pcm_uframes_t avail_min, + pa_bool_t period_event); /* Picks a working mapping from the profile set based on the specified ss/map */ snd_pcm_t *pa_alsa_open_by_device_id_auto( @@ -60,8 +61,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, /* modified at return */ snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, /* modified at return */ pa_bool_t *use_tsched, /* modified at return */ @@ -75,8 +76,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping( pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, /* modified at return */ snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, /* modified at return */ pa_bool_t *use_tsched, /* modified at return */ @@ -89,8 +90,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, /* modified at return */ snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, /* modified at return */ pa_bool_t *use_tsched, /* modified at return */ @@ -104,8 +105,8 @@ snd_pcm_t *pa_alsa_open_by_template( pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, /* modified at return */ snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, /* modified at return */ pa_bool_t *use_tsched, /* modified at return */ @@ -141,4 +142,6 @@ pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm); const char* pa_alsa_strerror(int errnum); +pa_bool_t pa_alsa_may_tsched(pa_bool_t want); + #endif diff --git a/src/modules/alsa/mixer/paths/analog-input.conf.common b/src/modules/alsa/mixer/paths/analog-input.conf.common index 6728a6ae..951e11fa 100644 --- a/src/modules/alsa/mixer/paths/analog-input.conf.common +++ b/src/modules/alsa/mixer/paths/analog-input.conf.common @@ -78,6 +78,10 @@ priority = 19 name = input-microphone priority = 19 +[Option Input Source:Internal Mic] +name = input-microphone +priority = 19 + [Option Input Source:Line] name = input-linein priority = 18 @@ -90,7 +94,15 @@ priority = 18 name = input-linein priority = 18 -;;; ' Capture Source' +[Option Input Source:Docking-Station] +name = input-docking +priority = 17 + +[Option Input Source:AUX IN] +name = input +priority = 10 + +;;; 'Capture Source' [Element Capture Source] enumeration = select @@ -236,6 +248,31 @@ name = input-docking [Option Capture Source:Dock Mic] name = input-docking-microphone +;;; 'Mic Jack Mode' + +[Element Mic Jack Mode] +enumeration = select + +[Option Mic Jack Mode:Mic In] +name = input-microphone + +[Option Mic Jack Mode:Line In] +name = input-linein + +;;; 'Digital Input Source' + +[Element Digital Input Source] +enumeration = select + +[Option Digital Input Source:Analog Inputs] +name = input + +[Option Digital Input Source:Digital Mic 1] +name = input-microphone + +[Option Digital Input Source:Digital Mic 2] +name = input-microphone + ;;; Various Boosts [Element Capture Boost] diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf new file mode 100644 index 00000000..f2fd31c7 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf @@ -0,0 +1,82 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Path for mixers that have a 'Headphone2' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 89 + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +; This profile path is intended to control the second headphones, not +; the first headphones. But it should not hurt if we leave the +; headphone jack enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Speaker] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Surround] +switch = off +volume = off + +[Element Side] +switch = off +volume = off + +[Element Center] +switch = off +volume = off + +[Element LFE] +switch = off +volume = off + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf index 691cb3f2..2131cfe8 100644 --- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf +++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf @@ -44,6 +44,13 @@ volume = merge override-map.1 = all override-map.2 = all-left,all-right +; This profile path is intended to control the first headphones, not +; the second headphones. But it should not hurt if we leave the second +; headphone jack enabled nonetheless. +[Element Headphone2] +switch = mute +volume = zero + [Element Speaker] switch = off volume = off @@ -56,7 +63,7 @@ volume = off switch = off volume = off -[Element Sourround] +[Element Surround] switch = off volume = off diff --git a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf index 2db976a5..0a43e271 100644 --- a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf +++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf @@ -41,9 +41,16 @@ volume = merge override-map.1 = lfe override-map.2 = lfe,lfe +; This profile path is intended to control the speaker, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. [Element Headphone] -switch = off -volume = off +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero [Element Speaker] switch = mute @@ -59,7 +66,7 @@ volume = off switch = off volume = off -[Element Sourround] +[Element Surround] switch = off volume = off diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf index a58cc970..542edc40 100644 --- a/src/modules/alsa/mixer/paths/analog-output-mono.conf +++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf @@ -38,9 +38,16 @@ volume = merge override-map.1 = all override-map.2 = all-left,all-right +; This profile path is intended to control the speaker, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. [Element Headphone] -switch = off -volume = off +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero [Element Speaker] switch = mute @@ -56,7 +63,7 @@ volume = off switch = off volume = off -[Element Sourround] +[Element Surround] switch = off volume = off diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf new file mode 100644 index 00000000..aea78534 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf @@ -0,0 +1,94 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Path for mixers that have a 'Speaker' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 100 + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +; This profile path is intended to control the speaker, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Front] +switch = mute +volume = merge +override-map.1 = all-front +override-map.2 = front-left,front-right + +[Element Rear] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Surround] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Side] +switch = mute +volume = merge +override-map.1 = all-side +override-map.2 = side-left,side-right + +[Element Center] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,all-center + +[Element LFE] +switch = mute +volume = merge +override-map.1 = lfe +override-map.2 = lfe,lfe + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf index b412a437..d7c1223b 100644 --- a/src/modules/alsa/mixer/paths/analog-output.conf +++ b/src/modules/alsa/mixer/paths/analog-output.conf @@ -14,12 +14,13 @@ # along with PulseAudio; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. -; Intended for the 'default' output +; Intended for the 'default' output. Note that a-o-speaker.conf has a +; higher priority than this ; ; See analog-output.conf.common for an explanation on the directives [General] -priority = 100 +priority = 99 [Element Hardware Master] switch = mute @@ -37,15 +38,20 @@ override-map.2 = all-left,all-right switch = off volume = off +; This profile path is intended to control the default output, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. [Element Headphone] -switch = off -volume = off +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero [Element Speaker] switch = mute -volume = merge -override-map.1 = all -override-map.2 = all-left,all-right +volume = off [Element Front] switch = mute diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common index cc1185f4..fd7f0cfb 100644 --- a/src/modules/alsa/mixer/paths/analog-output.conf.common +++ b/src/modules/alsa/mixer/paths/analog-output.conf.common @@ -104,8 +104,25 @@ switch = select [Option External Amplifier:on] name = output-amplifier-on -priority = 0 +priority = 10 [Option External Amplifier:off] name = output-amplifier-off +priority = 0 + +;;; 'Analog Output' + +[Element Analog Output] +enumeration = select + +[Option Analog Output:Speakers] +name = output-speaker priority = 10 + +[Option Analog Output:Headphones] +name = output-headphones +priority = 9 + +[Option Analog Output:FP Headphones] +name = output-headphones +priority = 8 diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf index ac41a8d3..046938fc 100644 --- a/src/modules/alsa/mixer/profile-sets/default.conf +++ b/src/modules/alsa/mixer/profile-sets/default.conf @@ -62,42 +62,42 @@ auto-profiles = yes [Mapping analog-mono] device-strings = hw:%f channel-map = mono -paths-output = analog-output analog-output-headphones analog-output-mono analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line priority = 1 [Mapping analog-stereo] device-strings = front:%f hw:%f channel-map = left,right -paths-output = analog-output analog-output-headphones analog-output-mono analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line priority = 10 [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right -paths-output = analog-output analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono priority = 7 direction = output [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe -paths-output = analog-output analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono priority = 8 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 analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono priority = 7 direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe -paths-output = analog-output analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono priority = 8 direction = output @@ -105,7 +105,7 @@ direction = output device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 -paths-output = analog-output analog-output-lfe-on-mono +paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono priority = 7 direction = output diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 55f6a6e2..6bea33d7 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -329,7 +329,7 @@ int pa__init(pa_module *m) { if (!u->profile_set) goto fail; - pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec); + pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); pa_card_new_data_init(&data); data.driver = __FILE__; diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index 66e1c31e..47d62005 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -95,6 +95,7 @@ static pa_bluetooth_device* device_new(const char *path) { d->audio_state = PA_BT_AUDIO_STATE_INVALID; d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID; + d->audio_source_state = PA_BT_AUDIO_STATE_INVALID; d->headset_state = PA_BT_AUDIO_STATE_INVALID; return d; @@ -122,9 +123,10 @@ static pa_bool_t device_is_audio(pa_bluetooth_device *d) { return d->device_info_valid && - (d->audio_state != PA_BT_AUDIO_STATE_INVALID || - d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID || - d->headset_state != PA_BT_AUDIO_STATE_INVALID); + (d->audio_state != PA_BT_AUDIO_STATE_INVALID && + (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID || + d->audio_source_state != PA_BT_AUDIO_STATE_INVALID || + d->headset_state != PA_BT_AUDIO_STATE_INVALID)); } static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) { @@ -226,10 +228,6 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device node = uuid_new(value); PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node); - /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */ - pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); - send_and_add_to_pending(y, d, m, get_properties_reply); - /* Vudentz said the interfaces are here when the UUIDs are announced */ if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) { pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties")); @@ -237,8 +235,15 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) { pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties")); send_and_add_to_pending(y, d, m, get_properties_reply); + } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) { + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties")); + send_and_add_to_pending(y, d, m, get_properties_reply); } + /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */ + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); + send_and_add_to_pending(y, d, m, get_properties_reply); + if (!dbus_message_iter_next(&ai)) break; } @@ -278,7 +283,7 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessa dbus_message_iter_recurse(i, &variant_i); -/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */ +/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */ switch (dbus_message_iter_get_arg_type(&variant_i)) { @@ -390,6 +395,9 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) { if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0) goto finish; + } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) { + if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0) + goto finish; } } @@ -440,8 +448,8 @@ static void found_device(pa_bluetooth_discovery *y, const char* path) { pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties")); send_and_add_to_pending(y, d, m, get_properties_reply); - /* Before we read the other properties (Audio, AudioSink, Headset) we wait - * that the UUID is read */ + /* Before we read the other properties (Audio, AudioSink, AudioSource, + * Headset) we wait that the UUID is read */ } static void list_devices_reply(DBusPendingCall *pending, void *userdata) { @@ -616,6 +624,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") || + dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) { pa_bluetooth_device *d; @@ -643,6 +652,9 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us } else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) { if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0) goto fail; + } else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) { + if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0) + goto fail; } run_callback(y, d, FALSE); @@ -650,6 +662,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) { + pa_bluetooth_device *d; + + if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) { + /* Device will disconnect in 2 sec */ + d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED; + + run_callback(y, d, FALSE); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { const char *name, *old_owner, *new_owner; @@ -696,12 +723,14 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_di while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) if (pa_streq(d->address, address)) - return d; + return device_is_audio(d) ? d : NULL; return NULL; } const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) { + pa_bluetooth_device *d; + pa_assert(y); pa_assert(PA_REFCNT_VALUE(y) > 0); pa_assert(path); @@ -709,7 +738,11 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_disco if (!pa_hook_is_firing(&y->hook)) pa_bluetooth_discovery_sync(y); - return pa_hashmap_get(y->devices, path); + if ((d = pa_hashmap_get(y->devices, path))) + if (device_is_audio(d)) + return d; + + return NULL; } static int setup_dbus(pa_bluetooth_discovery *y) { @@ -758,14 +791,16 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { if (pa_dbus_add_matches( pa_dbus_connection_get(y->connection), &err, - "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'", + "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'", "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'", "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) { + "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL) < 0) { pa_log("Failed to add D-Bus matches: %s", err.message); goto fail; } @@ -809,15 +844,17 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { if (y->connection) { pa_dbus_remove_matches(pa_dbus_connection_get(y->connection), - "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'", + "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'", "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'", "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL); + "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL); dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h index 265caf40..e2a0c3d5 100644 --- a/src/modules/bluetooth/bluetooth-util.h +++ b/src/modules/bluetooth/bluetooth-util.h @@ -4,7 +4,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -53,14 +53,13 @@ struct pa_bluetooth_uuid { PA_LLIST_FIELDS(pa_bluetooth_uuid); }; -/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */ +/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */ typedef enum pa_bt_audio_state { PA_BT_AUDIO_STATE_INVALID = -1, PA_BT_AUDIO_STATE_DISCONNECTED, PA_BT_AUDIO_STATE_CONNECTING, PA_BT_AUDIO_STATE_CONNECTED, - PA_BT_AUDIO_STATE_PLAYING, - PA_BT_AUDIO_STATE_LAST + PA_BT_AUDIO_STATE_PLAYING } pa_bt_audio_state_t; struct pa_bluetooth_device { @@ -85,6 +84,9 @@ struct pa_bluetooth_device { /* AudioSink state */ pa_bt_audio_state_t audio_sink_state; + /* AudioSource state */ + pa_bt_audio_state_t audio_source_state; + /* Headset state */ pa_bt_audio_state_t headset_state; }; diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 0560ef32..0ba1421b 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -74,7 +74,8 @@ PA_MODULE_USAGE( "profile=<a2dp|hsp> " "rate=<sample rate> " "channels=<number of channels> " - "path=<device object path>"); + "path=<device object path> " + "auto_connect=<automatically connect?>"); /* #ifdef NOKIA @@ -98,6 +99,7 @@ static const char* const valid_modargs[] = { "rate", "channels", "path", + "auto_connect", #ifdef NOKIA "sco_sink", "sco_source", @@ -129,6 +131,7 @@ struct hsp_info { enum profile { PROFILE_A2DP, + PROFILE_A2DP_SOURCE, PROFILE_HSP, PROFILE_OFF }; @@ -140,6 +143,7 @@ struct userdata { char *address; char *path; pa_bluetooth_discovery *discovery; + pa_bool_t auto_connect; pa_dbus_connection *connection; @@ -178,6 +182,7 @@ struct userdata { }; #define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC) #define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) @@ -219,9 +224,7 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r pa_assert(u); pa_assert(u->service_fd >= 0); pa_assert(msg); - - if (room <= 0) - room = BT_SUGGESTED_BUFFER_SIZE; + pa_assert(room >= sizeof(*msg)); pa_log_debug("Trying to receive message from audio service..."); @@ -234,6 +237,11 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r return -1; } + if (msg->length > room) { + pa_log_error("Not enough room."); + return -1; + } + /* Secondly, read the payload */ if (msg->length > sizeof(*msg)) { @@ -307,7 +315,7 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa pa_log_debug("Payload size is %lu %lu", (unsigned long) bytes_left, (unsigned long) sizeof(*codec)); - if ((u->profile == PROFILE_A2DP && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) || + if (((u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) || (u->profile == PROFILE_HSP && codec->transport != BT_CAPABILITIES_TRANSPORT_SCO)) { pa_log_error("Got capabilities for wrong codec."); return -1; @@ -344,6 +352,26 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa return codec->seid; memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities)); + + } else if (u->profile == PROFILE_A2DP_SOURCE) { + + while (bytes_left > 0) { + if ((codec->type == BT_A2DP_SBC_SOURCE) && !codec->lock) + break; + + bytes_left -= codec->length; + codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length); + } + + if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities)) + return -1; + + pa_assert(codec->type == BT_A2DP_SBC_SOURCE); + + if (codec->configured && seid == 0) + return codec->seid; + + memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities)); } return 0; @@ -368,13 +396,13 @@ static int get_caps(struct userdata *u, uint8_t seid) { msg.getcaps_req.seid = seid; pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object)); - if (u->profile == PROFILE_A2DP) + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP; else { pa_assert(u->profile == PROFILE_HSP); msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_SCO; } - msg.getcaps_req.flags = BT_FLAG_AUTOCONNECT; + msg.getcaps_req.flags = u->auto_connect ? BT_FLAG_AUTOCONNECT : 0; if (service_send(u, &msg.getcaps_req.h) < 0) return -1; @@ -451,7 +479,7 @@ static int setup_a2dp(struct userdata *u) { }; pa_assert(u); - pa_assert(u->profile == PROFILE_A2DP); + pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE); cap = &u->a2dp.sbc_capabilities; @@ -652,8 +680,8 @@ static int set_conf(struct userdata *u) { msg.open_req.h.length = sizeof(msg.open_req); pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object)); - msg.open_req.seid = u->profile == PROFILE_A2DP ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1; - msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK; + msg.open_req.seid = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1; + msg.open_req.lock = (u->profile == PROFILE_A2DP) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK; if (service_send(u, &msg.open_req.h) < 0) return -1; @@ -661,7 +689,7 @@ static int set_conf(struct userdata *u) { if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0) return -1; - if (u->profile == PROFILE_A2DP ) { + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) { u->sample_spec.format = PA_SAMPLE_S16LE; if (setup_a2dp(u) < 0) @@ -679,7 +707,7 @@ static int set_conf(struct userdata *u) { msg.setconf_req.h.name = BT_SET_CONFIGURATION; msg.setconf_req.h.length = sizeof(msg.setconf_req); - if (u->profile == PROFILE_A2DP) { + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) { memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities)); } else { msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; @@ -697,7 +725,7 @@ static int set_conf(struct userdata *u) { u->link_mtu = msg.setconf_rsp.link_mtu; /* setup SBC encoder now we agree on parameters */ - if (u->profile == PROFILE_A2DP) { + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) { setup_sbc(&u->a2dp); u->block_size = @@ -881,7 +909,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; } - *((pa_usec_t*) data) += u->sink->fixed_latency; + *((pa_usec_t*) data) += u->sink->thread_info.fixed_latency; return 0; } } @@ -943,7 +971,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off wi = pa_smoother_get(u->read_smoother, pa_rtclock_now()); ri = pa_bytes_to_usec(u->read_index, &u->sample_spec); - *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->fixed_latency; + *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency; return 0; } @@ -1250,6 +1278,119 @@ static int a2dp_process_render(struct userdata *u) { return ret; } +static int a2dp_process_push(struct userdata *u) { + int ret = 0; + pa_memchunk memchunk; + + pa_assert(u); + pa_assert(u->profile == PROFILE_A2DP_SOURCE); + pa_assert(u->source); + pa_assert(u->read_smoother); + + memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size); + memchunk.index = memchunk.length = 0; + + for (;;) { + pa_bool_t found_tstamp = FALSE; + pa_usec_t tstamp; + struct a2dp_info *a2dp; + struct rtp_header *header; + struct rtp_payload *payload; + const void *p; + void *d; + ssize_t l; + size_t to_write, to_decode; + unsigned frame_count; + + a2dp_prepare_buffer(u); + + a2dp = &u->a2dp; + header = a2dp->buffer; + payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); + + l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type); + + if (l <= 0) { + + if (l < 0 && errno == EINTR) + /* Retry right away if we got interrupted */ + continue; + + else if (l < 0 && errno == EAGAIN) + /* Hmm, apparently the socket was not readable, give up for now. */ + break; + + pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF"); + ret = -1; + break; + } + + pa_assert((size_t) l <= a2dp->buffer_size); + + u->read_index += (uint64_t) l; + + /* TODO: get timestamp from rtp */ + if (!found_tstamp) { + /* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */ + tstamp = pa_rtclock_now(); + } + + pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec)); + pa_smoother_resume(u->read_smoother, tstamp, TRUE); + + p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload); + to_decode = l - sizeof(*header) - sizeof(*payload); + + d = pa_memblock_acquire(memchunk.memblock); + to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock); + + while (PA_LIKELY(to_decode > 0 && to_write > 0)) { + size_t written; + ssize_t decoded; + + decoded = sbc_decode(&a2dp->sbc, + p, to_decode, + d, to_write, + &written); + + if (PA_UNLIKELY(decoded <= 0)) { + pa_log_error("SBC decoding error (%li)", (long) decoded); + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + return -1; + } + +/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */ +/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */ + + pa_assert_fp((size_t) decoded <= to_decode); + pa_assert_fp((size_t) decoded == a2dp->frame_length); + + pa_assert_fp((size_t) written <= to_write); + pa_assert_fp((size_t) written == a2dp->codesize); + + p = (const uint8_t*) p + decoded; + to_decode -= decoded; + + d = (uint8_t*) d + written; + to_write -= written; + + frame_count++; + } + + pa_memblock_release(memchunk.memblock); + + pa_source_post(u->source, &memchunk); + + ret = 1; + break; + } + + pa_memblock_unref(memchunk.memblock); + + return ret; +} + static void thread_func(void *userdata) { struct userdata *u = userdata; unsigned do_write = 0; @@ -1262,11 +1403,11 @@ static void thread_func(void *userdata) { if (u->core->realtime_scheduling) pa_make_realtime(u->core->realtime_priority); + pa_thread_mq_install(&u->thread_mq); + if (start_stream_fd(u) < 0) goto fail; - pa_thread_mq_install(&u->thread_mq); - for (;;) { struct pollfd *pollfd; int ret; @@ -1285,7 +1426,12 @@ static void thread_func(void *userdata) { if (pollfd && (pollfd->revents & POLLIN)) { int n_read; - if ((n_read = hsp_process_push(u)) < 0) + if (u->profile == PROFILE_HSP) + n_read = hsp_process_push(u); + else + n_read = a2dp_process_push(u); + + if (n_read < 0) goto fail; /* We just read something, so we are supposed to write something, too */ @@ -1319,18 +1465,21 @@ static void thread_func(void *userdata) { if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) { pa_usec_t skip_usec; uint64_t skip_bytes; - pa_memchunk tmp; skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC; skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec); - pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream", - (unsigned long long) skip_usec, - (unsigned long long) skip_bytes); + if (skip_bytes > 0) { + pa_memchunk tmp; + + pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream", + (unsigned long long) skip_usec, + (unsigned long long) skip_bytes); - pa_sink_render_full(u->sink, skip_bytes, &tmp); - pa_memblock_unref(tmp.memblock); - u->write_index += skip_bytes; + pa_sink_render_full(u->sink, skip_bytes, &tmp); + pa_memblock_unref(tmp.memblock); + u->write_index += skip_bytes; + } } do_write = 1; @@ -1446,12 +1595,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) { pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); - pa_sink_volume_changed(u->sink, &v, TRUE); + pa_sink_volume_changed(u->sink, &v); } else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) { pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); - pa_source_volume_changed(u->source, &v, TRUE); + pa_source_volume_changed(u->source, &v); } } } @@ -1473,12 +1622,12 @@ static void sink_set_volume_cb(pa_sink *s) { if (u->profile != PROFILE_HSP) return; - gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM; + gain = (pa_cvolume_max(&s->real_volume) * 15) / PA_VOLUME_NORM; if (gain > 15) gain = 15; - pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&s->real_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); @@ -1497,12 +1646,12 @@ static void source_set_volume_cb(pa_source *s) { if (u->profile != PROFILE_HSP) return; - gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM; + gain = (pa_cvolume_max(&s->volume) * 15) / PA_VOLUME_NORM; if (gain > 15) gain = 15; - pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&s->volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); @@ -1684,7 +1833,7 @@ static int add_source(struct userdata *u) { data.driver = __FILE__; data.module = u->module; pa_source_new_data_set_sample_spec(&data, &u->sample_spec); - pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp"); + pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP_SOURCE ? "a2dp_source" : "hsp"); if (u->profile == PROFILE_HSP) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); data.card = u->card; @@ -1709,7 +1858,7 @@ static int add_source(struct userdata *u) { u->source->parent.process_msg = source_process_msg; pa_source_set_fixed_latency(u->source, - (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + + (u->profile == PROFILE_A2DP_SOURCE ? FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) + pa_bytes_to_usec(u->block_size, &u->sample_spec)); } @@ -1736,7 +1885,8 @@ static void shutdown_bt(struct userdata *u) { if (u->service_fd >= 0) { pa_close(u->service_fd); u->service_fd = -1; - u->service_write_type = u->service_write_type = 0; + u->service_write_type = 0; + u->service_read_type = 0; } if (u->write_memchunk.memblock) { @@ -1752,7 +1902,8 @@ static int init_bt(struct userdata *u) { shutdown_bt(u); u->stream_write_type = 0; - u->service_write_type = u->service_write_type = 0; + u->service_write_type = 0; + u->service_read_type = 0; if ((u->service_fd = bt_audio_service_open()) < 0) { pa_log_error("Couldn't connect to bluetooth audio service"); @@ -1804,7 +1955,8 @@ static int init_profile(struct userdata *u) { if (add_sink(u) < 0) r = -1; - if (u->profile == PROFILE_HSP) + if (u->profile == PROFILE_HSP || + u->profile == PROFILE_A2DP_SOURCE) if (add_source(u) < 0) r = -1; @@ -2045,6 +2197,20 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) { pa_hashmap_put(data.profiles, p->name, p); } + if (pa_bluetooth_uuid_has(device->uuids, A2DP_SOURCE_UUID)) { + p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile)); + p->priority = 10; + p->n_sinks = 0; + p->n_sources = 1; + p->max_sink_channels = 0; + p->max_source_channels = 2; + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_A2DP_SOURCE; + + pa_hashmap_put(data.profiles, p->name, p); + } + if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) || pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) { p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); @@ -2200,6 +2366,12 @@ int pa__init(pa_module* m) { goto fail; } + u->auto_connect = TRUE; + if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) { + pa_log("Failed to parse auto_connect= argument"); + goto fail; + } + channels = u->sample_spec.channels; if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels > PA_CHANNELS_MAX) { diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index 788fee00..0085fa8e 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -84,7 +84,7 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const mi = pa_hashmap_get(u->hashmap, d->path); if (!d->dead && - d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) { + d->device_connected > 0 && (d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED || d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED)) { if (!mi) { pa_module *m = NULL; @@ -116,6 +116,9 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const } #endif + if (d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED) + args = pa_sprintf_malloc("%s profile=\"a2dp_source\" auto_connect=no", args); + pa_log_debug("Loading module-bluetooth-device %s", args); m = pa_module_load(u->module->core, "module-bluetooth-device", args); pa_xfree(args); diff --git a/src/modules/dbus/iface-card-profile.c b/src/modules/dbus/iface-card-profile.c new file mode 100644 index 00000000..004e2e88 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.c @@ -0,0 +1,228 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> + +#include "iface-card-profile.h" + +#define OBJECT_NAME "profile" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_card_profile { + uint32_t index; + pa_card_profile *profile; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DESCRIPTION, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_PRIORITY, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "u", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "u", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL }, +}; + +static pa_dbus_interface_info profile_interface_info = { + .name = PA_DBUSIFACE_CARD_PROFILE_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->name); +} + +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->description); +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t sinks = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sinks = p->profile->n_sinks; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sinks); +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t sources = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sources = p->profile->n_sources; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sources); +} + +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->profile->priority; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t sinks = 0; + dbus_uint32_t sources = 0; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sinks = p->profile->n_sinks; + sources = p->profile->n_sources; + priority = p->profile->priority; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->profile->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->profile->description); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_UINT32, &sinks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_UINT32, &sources); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +pa_dbusiface_card_profile *pa_dbusiface_card_profile_new( + pa_dbusiface_card *card, + pa_core *core, + pa_card_profile *profile, + uint32_t idx) { + pa_dbusiface_card_profile *p = NULL; + + pa_assert(card); + pa_assert(core); + pa_assert(profile); + + p = pa_xnew(pa_dbusiface_card_profile, 1); + p->index = idx; + p->profile = profile; + p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_card_get_path(card), OBJECT_NAME, idx); + p->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &profile_interface_info, p) >= 0); + + return p; +} + +void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p) { + pa_assert(p); + + pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, profile_interface_info.name) >= 0); + + pa_dbus_protocol_unref(p->dbus_protocol); + + pa_xfree(p->path); + pa_xfree(p); +} + +const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p) { + pa_assert(p); + + return p->path; +} + +const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p) { + pa_assert(p); + + return p->profile->name; +} diff --git a/src/modules/dbus/iface-card-profile.h b/src/modules/dbus/iface-card-profile.h new file mode 100644 index 00000000..a09767f8 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.h @@ -0,0 +1,50 @@ +#ifndef foodbusifacecardprofilehfoo +#define foodbusifacecardprofilehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.CardProfile. + * + * See http://pulseaudio.org/wiki/DBusInterface for the CardProfile interface + * documentation. + */ + +#include <pulsecore/core-scache.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-card.h" + +#define PA_DBUSIFACE_CARD_PROFILE_INTERFACE PA_DBUS_CORE_INTERFACE ".CardProfile" + +typedef struct pa_dbusiface_card_profile pa_dbusiface_card_profile; + +pa_dbusiface_card_profile *pa_dbusiface_card_profile_new( + pa_dbusiface_card *card, + pa_core *core, + pa_card_profile *profile, + uint32_t idx); +void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p); + +const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p); +const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p); + +#endif diff --git a/src/modules/dbus/iface-card.c b/src/modules/dbus/iface-card.c new file mode 100644 index 00000000..1714df36 --- /dev/null +++ b/src/modules/dbus/iface-card.c @@ -0,0 +1,561 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-card-profile.h" + +#include "iface-card.h" + +#define OBJECT_NAME "card" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_card { + pa_dbusiface_core *core; + + pa_card *card; + char *path; + pa_hashmap *profiles; + uint32_t next_profile_index; + pa_card_profile *active_profile; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_PROFILES, + PROPERTY_HANDLER_ACTIVE_PROFILE, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_PROFILES] = { .property_name = "Profiles", .type = "ao", .get_cb = handle_get_profiles, .set_cb = NULL }, + [PROPERTY_HANDLER_ACTIVE_PROFILE] = { .property_name = "ActiveProfile", .type = "o", .get_cb = handle_get_active_profile, .set_cb = handle_set_active_profile }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_GET_PROFILE_BY_NAME, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_profile_by_name_args[] = { { "name", "s", "in" }, { "profile", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_PROFILE_BY_NAME] = { + .method_name = "GetProfileByName", + .arguments = get_profile_by_name_args, + .n_arguments = sizeof(get_profile_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_profile_by_name } +}; + +enum signal_index { + SIGNAL_ACTIVE_PROFILE_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info active_profile_updated_args[] = { { "profile", "o", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_ACTIVE_PROFILE_UPDATED] = { .name = "ActiveProfileUpdated", .arguments = active_profile_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info card_interface_info = { + .name = PA_DBUSIFACE_CARD_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + dbus_uint32_t idx; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->card->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->name); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *owner_module; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->card->module) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Card %s doesn't have an owner module.", c->card->name); + return; + } + + owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sinks(pa_dbusiface_card *c, unsigned *n) { + const char **sinks = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_sink *sink = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->card->sinks); + + if (*n == 0) + return NULL; + + sinks = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(sink, c->card->sinks, idx) { + sinks[i] = pa_dbusiface_core_get_sink_path(c->core, sink); + ++i; + } + + return sinks; +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **sinks; + unsigned n_sinks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sinks = get_sinks(c, &n_sinks); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + + pa_xfree(sinks); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sources(pa_dbusiface_card *c, unsigned *n) { + const char **sources = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_source *source = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->card->sources); + + if (*n == 0) + return NULL; + + sources = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(source, c->card->sinks, idx) { + sources[i] = pa_dbusiface_core_get_source_path(c->core, source); + ++i; + } + + return sources; +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **sources; + unsigned n_sources; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sources = get_sources(c, &n_sources); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + + pa_xfree(sources); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_profiles(pa_dbusiface_card *c, unsigned *n) { + const char **profiles; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_card_profile *profile; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->profiles); + + if (*n == 0) + return NULL; + + profiles = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(profile, c->profiles, state) + profiles[i++] = pa_dbusiface_card_profile_get_path(profile); + + return profiles; +} + +static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **profiles; + unsigned n_profiles; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + profiles = get_profiles(c, &n_profiles); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles); + + pa_xfree(profiles); +} + +static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *active_profile; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->active_profile) { + pa_assert(pa_hashmap_isempty(c->profiles)); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The card %s has no profiles, and therefore there's no active profile either.", c->card->name); + return; + } + + active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_profile); +} + +static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *new_active_path; + pa_dbusiface_card_profile *new_active; + int r; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->active_profile) { + pa_assert(pa_hashmap_isempty(c->profiles)); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The card %s has no profiles, and therefore there's no active profile either.", + c->card->name); + return; + } + + dbus_message_iter_get_basic(iter, &new_active_path); + + if (!(new_active = pa_hashmap_get(c->profiles, new_active_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile.", new_active_path); + return; + } + + if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_proplist_variant_reply(conn, msg, c->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx; + const char *owner_module = NULL; + const char **sinks = NULL; + unsigned n_sinks = 0; + const char **sources = NULL; + unsigned n_sources = 0; + const char **profiles = NULL; + unsigned n_profiles = 0; + const char *active_profile = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->card->index; + if (c->card->module) + owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module); + sinks = get_sinks(c, &n_sinks); + sources = get_sources(c, &n_sources); + profiles = get_profiles(c, &n_profiles); + if (c->active_profile) + active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &c->card->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->card->driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROFILES].property_name, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles); + + if (active_profile) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PROFILE].property_name, DBUS_TYPE_OBJECT_PATH, &active_profile); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(sinks); + pa_xfree(sources); + pa_xfree(profiles); +} + +static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *profile_name = NULL; + pa_dbusiface_card_profile *profile = NULL; + const char *profile_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &profile_name, DBUS_TYPE_INVALID)); + + if (!(profile = pa_hashmap_get(c->profiles, profile_name))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile on card %s.", profile_name, c->card->name); + return; + } + + profile_path = pa_dbusiface_card_profile_get_path(profile); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &profile_path); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_card *c = userdata; + DBusMessage *signal = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD); + pa_assert(c); + + /* We can't use idx != c->card->index, because the c->card pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->cards, idx) != c->card) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (c->active_profile != c->card->active_profile) { + const char *object_path; + + c->active_profile = c->card->active_profile; + object_path = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_assert_se(signal = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CARD_INTERFACE, + signals[SIGNAL_ACTIVE_PROFILE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + if (!pa_proplist_equal(c->proplist, c->card->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(c->proplist, PA_UPDATE_SET, c->card->proplist); + + pa_assert_se(signal = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CARD_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, c->proplist); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card) { + pa_dbusiface_card *c = NULL; + + pa_assert(core); + pa_assert(card); + + c = pa_xnew0(pa_dbusiface_card, 1); + c->core = core; + c->card = card; + c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, card->index); + c->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->next_profile_index = 0; + c->active_profile = NULL; + c->proplist = pa_proplist_copy(card->proplist); + c->dbus_protocol = pa_dbus_protocol_get(card->core); + c->subscription = pa_subscription_new(card->core, PA_SUBSCRIPTION_MASK_CARD, subscription_cb, c); + + if (card->profiles) { + pa_card_profile *profile; + void *state = NULL; + + PA_HASHMAP_FOREACH(profile, card->profiles, state) { + pa_dbusiface_card_profile *p = pa_dbusiface_card_profile_new(c, card->core, profile, c->next_profile_index++); + pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p); + } + pa_assert_se(c->active_profile = card->active_profile); + } + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &card_interface_info, c) >= 0); + + return c; +} + +static void profile_free_cb(void *p, void *userdata) { + pa_dbusiface_card_profile *profile = p; + + pa_assert(profile); + + pa_dbusiface_card_profile_free(profile); +} + +void pa_dbusiface_card_free(pa_dbusiface_card *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, card_interface_info.name) >= 0); + + pa_hashmap_free(c->profiles, profile_free_cb, NULL); + pa_proplist_free(c->proplist); + pa_dbus_protocol_unref(c->dbus_protocol); + pa_subscription_free(c->subscription); + + pa_xfree(c->path); + pa_xfree(c); +} + +const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c) { + pa_assert(c); + + return c->path; +} diff --git a/src/modules/dbus/iface-card.h b/src/modules/dbus/iface-card.h new file mode 100644 index 00000000..e2c08a3b --- /dev/null +++ b/src/modules/dbus/iface-card.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacecardhfoo +#define foodbusifacecardhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Card. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Card interface + * documentation. + */ + +#include <pulsecore/card.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_CARD_INTERFACE PA_DBUS_CORE_INTERFACE ".Card" + +typedef struct pa_dbusiface_card pa_dbusiface_card; + +pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card); +void pa_dbusiface_card_free(pa_dbusiface_card *c); + +const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c); + +#endif diff --git a/src/modules/dbus/iface-client.c b/src/modules/dbus/iface-client.c new file mode 100644 index 00000000..546370f9 --- /dev/null +++ b/src/modules/dbus/iface-client.c @@ -0,0 +1,459 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.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 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-client.h" + +#define OBJECT_NAME "client" + +struct pa_dbusiface_client { + pa_dbusiface_core *core; + + pa_client *client; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_PLAYBACK_STREAMS, + PROPERTY_HANDLER_RECORD_STREAMS, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_KILL, + METHOD_HANDLER_UPDATE_PROPERTIES, + METHOD_HANDLER_REMOVE_PROPERTIES, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } }; +static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_KILL] = { + .method_name = "Kill", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_kill }, + [METHOD_HANDLER_UPDATE_PROPERTIES] = { + .method_name = "UpdateProperties", + .arguments = update_properties_args, + .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_update_properties }, + [METHOD_HANDLER_REMOVE_PROPERTIES] = { + .method_name = "RemoveProperties", + .arguments = remove_properties_args, + .n_arguments = sizeof(remove_properties_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_remove_properties } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_CLIENT_EVENT, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; +static pa_dbus_arg_info client_event_args[] = { { "name", "s", NULL }, + { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }, + /* ClientEvent is sent from module-dbus-protocol.c. */ + [SIGNAL_CLIENT_EVENT] = { .name = "ClientEvent", .arguments = client_event_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info client_interface_info = { + .name = PA_DBUSIFACE_CLIENT_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->client->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char *owner_module = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->client->module) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Client %d doesn't have an owner module.", c->client->index); + return; + } + + owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_playback_streams(pa_dbusiface_client *c, unsigned *n) { + const char **playback_streams = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_sink_input *sink_input = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->client->sink_inputs); + + if (*n == 0) + return NULL; + + playback_streams = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(sink_input, c->client->sink_inputs, idx) + playback_streams[i++] = pa_dbusiface_core_get_playback_stream_path(c->core, sink_input); + + return playback_streams; +} + +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char **playback_streams = NULL; + unsigned n_playback_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + playback_streams = get_playback_streams(c, &n_playback_streams); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + + pa_xfree(playback_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_record_streams(pa_dbusiface_client *c, unsigned *n) { + const char **record_streams = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_source_output *source_output = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->client->source_outputs); + + if (*n == 0) + return NULL; + + record_streams = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(source_output, c->client->source_outputs, idx) + record_streams[i++] = pa_dbusiface_core_get_record_stream_path(c->core, source_output); + + return record_streams; +} + +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char **record_streams = NULL; + unsigned n_record_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + record_streams = get_record_streams(c, &n_record_streams); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + + pa_xfree(record_streams); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *owner_module = NULL; + const char **playback_streams = NULL; + unsigned n_playback_streams = 0; + const char **record_streams = NULL; + unsigned n_record_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->client->index; + if (c->client->module) + owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module); + playback_streams = get_playback_streams(c, &n_playback_streams); + record_streams = get_record_streams(c, &n_record_streams); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->client->driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->client->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(playback_streams); + pa_xfree(record_streams); +} + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + dbus_connection_ref(conn); + + pa_client_kill(c->client); + + pa_dbus_send_empty_reply(conn, msg); + + dbus_connection_unref(conn); +} + +static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessageIter msg_iter; + pa_proplist *property_list = NULL; + dbus_uint32_t update_mode = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client."); + return; + } + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + dbus_message_iter_get_basic(&msg_iter, &update_mode); + + if (!(update_mode == PA_UPDATE_SET || update_mode == PA_UPDATE_MERGE || update_mode == PA_UPDATE_REPLACE)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid update mode: %u", update_mode); + goto finish; + } + + pa_client_update_proplist(c->client, update_mode, property_list); + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + char **keys = NULL; + int n_keys = 0; + pa_bool_t changed = FALSE; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client."); + return; + } + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID)); + + for (i = 0; i < n_keys; ++i) + changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0; + + pa_dbus_send_empty_reply(conn, msg); + + if (changed) + pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + + dbus_free_string_array(keys); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessage *signal = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CLIENT); + pa_assert(c); + + /* We can't use idx != c->client->index, because the c->client pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->clients, idx) != c->client) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(c->proplist, c->client->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist); + + pa_assert_se(signal = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CLIENT_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, c->proplist); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) { + pa_dbusiface_client *c = NULL; + + pa_assert(core); + pa_assert(client); + + c = pa_xnew(pa_dbusiface_client, 1); + c->core = core; + c->client = client; + c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, client->index); + c->proplist = pa_proplist_copy(client->proplist); + c->dbus_protocol = pa_dbus_protocol_get(client->core); + c->subscription = pa_subscription_new(client->core, PA_SUBSCRIPTION_MASK_CLIENT, subscription_cb, c); + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0); + + return c; +} + +void pa_dbusiface_client_free(pa_dbusiface_client *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0); + + pa_dbus_protocol_unref(c->dbus_protocol); + + pa_xfree(c->path); + pa_xfree(c); +} + +const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) { + pa_assert(c); + + return c->path; +} diff --git a/src/modules/dbus/iface-client.h b/src/modules/dbus/iface-client.h new file mode 100644 index 00000000..e8f151cd --- /dev/null +++ b/src/modules/dbus/iface-client.h @@ -0,0 +1,45 @@ +#ifndef foodbusifaceclienthfoo +#define foodbusifaceclienthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Client. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Client interface + * documentation. + */ + +#include <pulsecore/client.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_CLIENT_INTERFACE PA_DBUS_CORE_INTERFACE ".Client" + +typedef struct pa_dbusiface_client pa_dbusiface_client; + +pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client); +void pa_dbusiface_client_free(pa_dbusiface_client *c); + +const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c); + +#endif diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c new file mode 100644 index 00000000..169e8e55 --- /dev/null +++ b/src/modules/dbus/iface-core.c @@ -0,0 +1,2197 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> + +#include <dbus/dbus.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/strbuf.h> + +#include "iface-card.h" +#include "iface-client.h" +#include "iface-device.h" +#include "iface-memstats.h" +#include "iface-module.h" +#include "iface-sample.h" +#include "iface-stream.h" + +#include "iface-core.h" + +#define INTERFACE_REVISION 0 + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_core { + pa_core *core; + pa_subscription *subscription; + + pa_dbus_protocol *dbus_protocol; + + pa_hashmap *cards; + pa_hashmap *sinks_by_index; + pa_hashmap *sinks_by_path; + pa_hashmap *sources_by_index; + pa_hashmap *sources_by_path; + pa_hashmap *playback_streams; + pa_hashmap *record_streams; + pa_hashmap *samples; + pa_hashmap *modules; + pa_hashmap *clients; + + pa_sink *fallback_sink; + pa_source *fallback_source; + + pa_hook_slot *extension_registered_slot; + pa_hook_slot *extension_unregistered_slot; + + pa_dbusiface_memstats *memstats; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INTERFACE_REVISION, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_VERSION, + PROPERTY_HANDLER_IS_LOCAL, + PROPERTY_HANDLER_USERNAME, + PROPERTY_HANDLER_HOSTNAME, + PROPERTY_HANDLER_DEFAULT_CHANNELS, + PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT, + PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE, + PROPERTY_HANDLER_CARDS, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_FALLBACK_SINK, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_FALLBACK_SOURCE, + PROPERTY_HANDLER_PLAYBACK_STREAMS, + PROPERTY_HANDLER_RECORD_STREAMS, + PROPERTY_HANDLER_SAMPLES, + PROPERTY_HANDLER_MODULES, + PROPERTY_HANDLER_CLIENTS, + PROPERTY_HANDLER_MY_CLIENT, + PROPERTY_HANDLER_EXTENSIONS, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_VERSION] = { .property_name = "Version", .type = "s", .get_cb = handle_get_version, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_LOCAL] = { .property_name = "IsLocal", .type = "b", .get_cb = handle_get_is_local, .set_cb = NULL }, + [PROPERTY_HANDLER_USERNAME] = { .property_name = "Username", .type = "s", .get_cb = handle_get_username, .set_cb = NULL }, + [PROPERTY_HANDLER_HOSTNAME] = { .property_name = "Hostname", .type = "s", .get_cb = handle_get_hostname, .set_cb = NULL }, + [PROPERTY_HANDLER_DEFAULT_CHANNELS] = { .property_name = "DefaultChannels", .type = "au", .get_cb = handle_get_default_channels, .set_cb = handle_set_default_channels }, + [PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT] = { .property_name = "DefaultSampleFormat", .type = "u", .get_cb = handle_get_default_sample_format, .set_cb = handle_set_default_sample_format }, + [PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE] = { .property_name = "DefaultSampleRate", .type = "u", .get_cb = handle_get_default_sample_rate, .set_cb = handle_set_default_sample_rate }, + [PROPERTY_HANDLER_CARDS] = { .property_name = "Cards", .type = "ao", .get_cb = handle_get_cards, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_FALLBACK_SINK] = { .property_name = "FallbackSink", .type = "o", .get_cb = handle_get_fallback_sink, .set_cb = handle_set_fallback_sink }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_FALLBACK_SOURCE] = { .property_name = "FallbackSource", .type = "o", .get_cb = handle_get_fallback_source, .set_cb = handle_set_fallback_source }, + [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLES] = { .property_name = "Samples", .type = "ao", .get_cb = handle_get_samples, .set_cb = NULL }, + [PROPERTY_HANDLER_MODULES] = { .property_name = "Modules", .type = "ao", .get_cb = handle_get_modules, .set_cb = NULL }, + [PROPERTY_HANDLER_CLIENTS] = { .property_name = "Clients", .type = "ao", .get_cb = handle_get_clients, .set_cb = NULL }, + [PROPERTY_HANDLER_MY_CLIENT] = { .property_name = "MyClient", .type = "o", .get_cb = handle_get_my_client, .set_cb = NULL }, + [PROPERTY_HANDLER_EXTENSIONS] = { .property_name = "Extensions", .type = "as", .get_cb = handle_get_extensions, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_GET_CARD_BY_NAME, + METHOD_HANDLER_GET_SINK_BY_NAME, + METHOD_HANDLER_GET_SOURCE_BY_NAME, + METHOD_HANDLER_GET_SAMPLE_BY_NAME, + METHOD_HANDLER_UPLOAD_SAMPLE, + METHOD_HANDLER_LOAD_MODULE, + METHOD_HANDLER_EXIT, + METHOD_HANDLER_LISTEN_FOR_SIGNAL, + METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_card_by_name_args[] = { { "name", "s", "in" }, { "card", "o", "out" } }; +static pa_dbus_arg_info get_sink_by_name_args[] = { { "name", "s", "in" }, { "sink", "o", "out" } }; +static pa_dbus_arg_info get_source_by_name_args[] = { { "name", "s", "in" }, { "source", "o", "out" } }; +static pa_dbus_arg_info get_sample_by_name_args[] = { { "name", "s", "in" }, { "sample", "o", "out" } }; +static pa_dbus_arg_info upload_sample_args[] = { { "name", "s", "in" }, + { "sample_format", "u", "in" }, + { "sample_rate", "u", "in" }, + { "channels", "au", "in" }, + { "default_volume", "au", "in" }, + { "property_list", "a{say}", "in" }, + { "data", "ay", "in" }, + { "sample", "o", "out" } }; +static pa_dbus_arg_info load_module_args[] = { { "name", "s", "in" }, { "arguments", "a{ss}", "in" }, { "module", "o", "out" } }; +static pa_dbus_arg_info listen_for_signal_args[] = { { "signal", "s", "in" }, { "objects", "ao", "in" } }; +static pa_dbus_arg_info stop_listening_for_signal_args[] = { { "signal", "s", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_CARD_BY_NAME] = { + .method_name = "GetCardByName", + .arguments = get_card_by_name_args, + .n_arguments = sizeof(get_card_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_card_by_name }, + [METHOD_HANDLER_GET_SINK_BY_NAME] = { + .method_name = "GetSinkByName", + .arguments = get_sink_by_name_args, + .n_arguments = sizeof(get_sink_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_sink_by_name }, + [METHOD_HANDLER_GET_SOURCE_BY_NAME] = { + .method_name = "GetSourceByName", + .arguments = get_source_by_name_args, + .n_arguments = sizeof(get_source_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_source_by_name }, + [METHOD_HANDLER_GET_SAMPLE_BY_NAME] = { + .method_name = "GetSampleByName", + .arguments = get_sample_by_name_args, + .n_arguments = sizeof(get_sample_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_sample_by_name }, + [METHOD_HANDLER_UPLOAD_SAMPLE] = { + .method_name = "UploadSample", + .arguments = upload_sample_args, + .n_arguments = sizeof(upload_sample_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_upload_sample }, + [METHOD_HANDLER_LOAD_MODULE] = { + .method_name = "LoadModule", + .arguments = load_module_args, + .n_arguments = sizeof(load_module_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_load_module }, + [METHOD_HANDLER_EXIT] = { + .method_name = "Exit", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_exit }, + [METHOD_HANDLER_LISTEN_FOR_SIGNAL] = { + .method_name = "ListenForSignal", + .arguments = listen_for_signal_args, + .n_arguments = sizeof(listen_for_signal_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_listen_for_signal }, + [METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL] = { + .method_name = "StopListeningForSignal", + .arguments = stop_listening_for_signal_args, + .n_arguments = sizeof(stop_listening_for_signal_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_stop_listening_for_signal } +}; + +enum signal_index { + SIGNAL_NEW_CARD, + SIGNAL_CARD_REMOVED, + SIGNAL_NEW_SINK, + SIGNAL_SINK_REMOVED, + SIGNAL_FALLBACK_SINK_UPDATED, + SIGNAL_FALLBACK_SINK_UNSET, + SIGNAL_NEW_SOURCE, + SIGNAL_SOURCE_REMOVED, + SIGNAL_FALLBACK_SOURCE_UPDATED, + SIGNAL_FALLBACK_SOURCE_UNSET, + SIGNAL_NEW_PLAYBACK_STREAM, + SIGNAL_PLAYBACK_STREAM_REMOVED, + SIGNAL_NEW_RECORD_STREAM, + SIGNAL_RECORD_STREAM_REMOVED, + SIGNAL_NEW_SAMPLE, + SIGNAL_SAMPLE_REMOVED, + SIGNAL_NEW_MODULE, + SIGNAL_MODULE_REMOVED, + SIGNAL_NEW_CLIENT, + SIGNAL_CLIENT_REMOVED, + SIGNAL_NEW_EXTENSION, + SIGNAL_EXTENSION_REMOVED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info new_card_args[] = { { "card", "o", NULL } }; +static pa_dbus_arg_info card_removed_args[] = { { "card", "o", NULL } }; +static pa_dbus_arg_info new_sink_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info sink_removed_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info fallback_sink_updated_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info new_source_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info source_removed_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info fallback_source_updated_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info new_playback_stream_args[] = { { "playback_stream", "o", NULL } }; +static pa_dbus_arg_info playback_stream_removed_args[] = { { "playback_stream", "o", NULL } }; +static pa_dbus_arg_info new_record_stream_args[] = { { "record_stream", "o", NULL } }; +static pa_dbus_arg_info record_stream_removed_args[] = { { "record_stream", "o", NULL } }; +static pa_dbus_arg_info new_sample_args[] = { { "sample", "o", NULL } }; +static pa_dbus_arg_info sample_removed_args[] = { { "sample", "o", NULL } }; +static pa_dbus_arg_info new_module_args[] = { { "module", "o", NULL } }; +static pa_dbus_arg_info module_removed_args[] = { { "module", "o", NULL } }; +static pa_dbus_arg_info new_client_args[] = { { "client", "o", NULL } }; +static pa_dbus_arg_info client_removed_args[] = { { "client", "o", NULL } }; +static pa_dbus_arg_info new_extension_args[] = { { "extension", "s", NULL } }; +static pa_dbus_arg_info extension_removed_args[] = { { "extension", "s", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_NEW_CARD] = { .name = "NewCard", .arguments = new_card_args, .n_arguments = 1 }, + [SIGNAL_CARD_REMOVED] = { .name = "CardRemoved", .arguments = card_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_SINK] = { .name = "NewSink", .arguments = new_sink_args, .n_arguments = 1 }, + [SIGNAL_SINK_REMOVED] = { .name = "SinkRemoved", .arguments = sink_removed_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SINK_UPDATED] = { .name = "FallbackSinkUpdated", .arguments = fallback_sink_updated_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SINK_UNSET] = { .name = "FallbackSinkUnset", .arguments = NULL, .n_arguments = 0 }, + [SIGNAL_NEW_SOURCE] = { .name = "NewSource", .arguments = new_source_args, .n_arguments = 1 }, + [SIGNAL_SOURCE_REMOVED] = { .name = "SourceRemoved", .arguments = source_removed_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SOURCE_UPDATED] = { .name = "FallbackSourceUpdated", .arguments = fallback_source_updated_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SOURCE_UNSET] = { .name = "FallbackSourceUnset", .arguments = NULL, .n_arguments = 0 }, + [SIGNAL_NEW_PLAYBACK_STREAM] = { .name = "NewPlaybackStream", .arguments = new_playback_stream_args, .n_arguments = 1 }, + [SIGNAL_PLAYBACK_STREAM_REMOVED] = { .name = "PlaybackStreamRemoved", .arguments = playback_stream_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_RECORD_STREAM] = { .name = "NewRecordStream", .arguments = new_record_stream_args, .n_arguments = 1 }, + [SIGNAL_RECORD_STREAM_REMOVED] = { .name = "RecordStreamRemoved", .arguments = record_stream_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_SAMPLE] = { .name = "NewSample", .arguments = new_sample_args, .n_arguments = 1 }, + [SIGNAL_SAMPLE_REMOVED] = { .name = "SampleRemoved", .arguments = sample_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_MODULE] = { .name = "NewModule", .arguments = new_module_args, .n_arguments = 1 }, + [SIGNAL_MODULE_REMOVED] = { .name = "ModuleRemoved", .arguments = module_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_CLIENT] = { .name = "NewClient", .arguments = new_client_args, .n_arguments = 1 }, + [SIGNAL_CLIENT_REMOVED] = { .name = "ClientRemoved", .arguments = client_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_EXTENSION] = { .name = "NewExtension", .arguments = new_extension_args, .n_arguments = 1 }, + [SIGNAL_EXTENSION_REMOVED] = { .name = "ExtensionRemoved", .arguments = extension_removed_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info core_interface_info = { + .name = PA_DBUS_CORE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_uint32_t interface_revision = INTERFACE_REVISION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *server_name = PACKAGE_NAME; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &server_name); +} + +static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *version = PACKAGE_VERSION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &version); +} + +static dbus_bool_t get_is_local(DBusConnection *conn) { + int conn_fd; + + pa_assert(conn); + + if (!dbus_connection_get_socket(conn, &conn_fd)) + return FALSE; + + return pa_socket_is_local(conn_fd); +} + +static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_bool_t is_local; + + pa_assert(conn); + pa_assert(msg); + + is_local = get_is_local(conn); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_local); +} + +static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *username = NULL; + + pa_assert(conn); + pa_assert(msg); + + username = pa_get_user_name_malloc(); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &username); + + pa_xfree(username); +} + +static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *hostname = NULL; + + pa_assert(conn); + pa_assert(msg); + + hostname = pa_get_host_name_malloc(); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &hostname); + + pa_xfree(hostname); +} + +/* Caller frees the returned array. */ +static dbus_uint32_t *get_default_channels(pa_dbusiface_core *c, unsigned *n) { + dbus_uint32_t *default_channels = NULL; + unsigned i; + + pa_assert(c); + pa_assert(n); + + *n = c->core->default_channel_map.channels; + default_channels = pa_xnew(dbus_uint32_t, *n); + + for (i = 0; i < *n; ++i) + default_channels[i] = c->core->default_channel_map.map[i]; + + return default_channels; +} + +static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t *default_channels = NULL; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_channels = get_default_channels(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_channels, n); + + pa_xfree(default_channels); +} + +static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter array_iter; + pa_channel_map new_channel_map; + const dbus_uint32_t *default_channels; + int n_channels; + unsigned i; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + pa_channel_map_init(&new_channel_map); + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &default_channels, &n_channels); + + if (n_channels <= 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel array."); + return; + } + + if (n_channels > (int) PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too many channels: %i. The maximum number of channels is %u.", n_channels, PA_CHANNELS_MAX); + return; + } + + new_channel_map.channels = n_channels; + + for (i = 0; i < new_channel_map.channels; ++i) { + if (default_channels[i] >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u.", default_channels[i]); + return; + } + + new_channel_map.map[i] = default_channels[i]; + } + + c->core->default_channel_map = new_channel_map; + c->core->default_sample_spec.channels = n_channels; + + pa_dbus_send_empty_reply(conn, msg); +}; + +static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_format; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_sample_format = c->core->default_sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_format); +} + +static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_format; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + dbus_message_iter_get_basic(iter, &default_sample_format); + + if (default_sample_format >= PA_SAMPLE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format."); + return; + } + + c->core->default_sample_spec.format = default_sample_format; + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_rate; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_sample_rate = c->core->default_sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_rate); +} + +static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_rate; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + dbus_message_iter_get_basic(iter, &default_sample_rate); + + if (default_sample_rate <= 0 || default_sample_rate > PA_RATE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate."); + return; + } + + c->core->default_sample_spec.rate = default_sample_rate; + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_cards(pa_dbusiface_core *c, unsigned *n) { + const char **cards; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_card *card; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->cards); + + if (*n == 0) + return NULL; + + cards = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(card, c->cards, state) + cards[i++] = pa_dbusiface_card_get_path(card); + + return cards; +} + +static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **cards; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + cards = get_cards(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, cards, n); + + pa_xfree(cards); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sinks(pa_dbusiface_core *c, unsigned *n) { + const char **sinks; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device *sink; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->sinks_by_index); + + if (*n == 0) + return NULL; + + sinks = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(sink, c->sinks_by_index, state) + sinks[i++] = pa_dbusiface_device_get_path(sink); + + return sinks; +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **sinks; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sinks = get_sinks(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n); + + pa_xfree(sinks); +} + +static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->fallback_sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sinks, and therefore no fallback sink either."); + return; + } + + pa_assert_se((fallback_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)))); + object_path = pa_dbusiface_device_get_path(fallback_sink); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->fallback_sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sinks, and therefore no fallback sink either."); + return; + } + + dbus_message_iter_get_basic(iter, &object_path); + + if (!(fallback_sink = pa_hashmap_get(c->sinks_by_path, object_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", object_path); + return; + } + + pa_namereg_set_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink)); + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sources(pa_dbusiface_core *c, unsigned *n) { + const char **sources; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device *source; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->sources_by_index); + + if (*n == 0) + return NULL; + + sources = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(source, c->sources_by_index, state) + sources[i++] = pa_dbusiface_device_get_path(source); + + return sources; +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **sources; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sources = get_sources(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n); + + pa_xfree(sources); +} + +static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->fallback_source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sources, and therefore no fallback source either."); + return; + } + + pa_assert_se((fallback_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index)))); + object_path = pa_dbusiface_device_get_path(fallback_source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->fallback_source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sources, and therefore no fallback source either."); + return; + } + + dbus_message_iter_get_basic(iter, &object_path); + + if (!(fallback_source = pa_hashmap_get(c->sources_by_path, object_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", object_path); + return; + } + + pa_namereg_set_default_source(c->core, pa_dbusiface_device_get_source(fallback_source)); + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_playback_streams(pa_dbusiface_core *c, unsigned *n) { + const char **streams; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_stream *stream; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->playback_streams); + + if (*n == 0) + return NULL; + + streams = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(stream, c->playback_streams, state) + streams[i++] = pa_dbusiface_stream_get_path(stream); + + return streams; +} + +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **playback_streams; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + playback_streams = get_playback_streams(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n); + + pa_xfree(playback_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_record_streams(pa_dbusiface_core *c, unsigned *n) { + const char **streams; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_stream *stream; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->record_streams); + + if (*n == 0) + return NULL; + + streams = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(stream, c->record_streams, state) + streams[i++] = pa_dbusiface_stream_get_path(stream); + + return streams; +} + +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **record_streams; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + record_streams = get_record_streams(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n); + + pa_xfree(record_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_samples(pa_dbusiface_core *c, unsigned *n) { + const char **samples; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_sample *sample; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->samples); + + if (*n == 0) + return NULL; + + samples = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(sample, c->samples, state) + samples[i++] = pa_dbusiface_sample_get_path(sample); + + return samples; +} + +static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **samples; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + samples = get_samples(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, samples, n); + + pa_xfree(samples); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_modules(pa_dbusiface_core *c, unsigned *n) { + const char **modules; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_module *module; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->modules); + + if (*n == 0) + return NULL; + + modules = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(module, c->modules, state) + modules[i++] = pa_dbusiface_module_get_path(module); + + return modules; +} + +static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **modules; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + modules = get_modules(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, modules, n); + + pa_xfree(modules); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_clients(pa_dbusiface_core *c, unsigned *n) { + const char **clients; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_client *client; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->clients); + + if (*n == 0) + return NULL; + + clients = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(client, c->clients, state) + clients[i++] = pa_dbusiface_client_get_path(client); + + return clients; +} + +static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **clients; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + clients = get_clients(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, clients, n); + + pa_xfree(clients); +} + +static const char *get_my_client(pa_dbusiface_core *c, DBusConnection *conn) { + pa_client *my_client; + + pa_assert(c); + pa_assert(conn); + + pa_assert_se((my_client = pa_dbus_protocol_get_client(c->dbus_protocol, conn))); + + return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(my_client->index))); +} + +static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *my_client; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + my_client = get_my_client(c, conn); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &my_client); +} + +static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **extensions; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, extensions, n); + + pa_xfree(extensions); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t interface_revision; + const char *server_name; + const char *version; + dbus_bool_t is_local; + char *username; + char *hostname; + dbus_uint32_t *default_channels; + unsigned n_default_channels; + dbus_uint32_t default_sample_format; + dbus_uint32_t default_sample_rate; + const char **cards; + unsigned n_cards; + const char **sinks; + unsigned n_sinks; + const char *fallback_sink; + const char **sources; + unsigned n_sources; + const char *fallback_source; + const char **playback_streams; + unsigned n_playback_streams; + const char **record_streams; + unsigned n_record_streams; + const char **samples; + unsigned n_samples; + const char **modules; + unsigned n_modules; + const char **clients; + unsigned n_clients; + const char *my_client; + const char **extensions; + unsigned n_extensions; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + interface_revision = INTERFACE_REVISION; + server_name = PACKAGE_NAME; + version = PACKAGE_VERSION; + is_local = get_is_local(conn); + username = pa_get_user_name_malloc(); + hostname = pa_get_host_name_malloc(); + default_channels = get_default_channels(c, &n_default_channels); + default_sample_format = c->core->default_sample_spec.format; + default_sample_rate = c->core->default_sample_spec.rate; + cards = get_cards(c, &n_cards); + sinks = get_sinks(c, &n_sinks); + fallback_sink = c->fallback_sink + ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index))) + : NULL; + sources = get_sources(c, &n_sources); + fallback_source = c->fallback_source + ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, + PA_UINT32_TO_PTR(c->fallback_source->index))) + : NULL; + playback_streams = get_playback_streams(c, &n_playback_streams); + record_streams = get_record_streams(c, &n_record_streams); + samples = get_samples(c, &n_samples); + modules = get_modules(c, &n_modules); + clients = get_clients(c, &n_clients); + my_client = get_my_client(c, conn); + extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n_extensions); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &server_name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VERSION].property_name, DBUS_TYPE_STRING, &version); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_LOCAL].property_name, DBUS_TYPE_BOOLEAN, &is_local); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_USERNAME].property_name, DBUS_TYPE_STRING, &username); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HOSTNAME].property_name, DBUS_TYPE_STRING, &hostname); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_CHANNELS].property_name, DBUS_TYPE_UINT32, default_channels, n_default_channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &default_sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &default_sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARDS].property_name, DBUS_TYPE_OBJECT_PATH, cards, n_cards); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + + if (fallback_sink) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_sink); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + + if (fallback_source) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_source); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLES].property_name, DBUS_TYPE_OBJECT_PATH, samples, n_samples); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MODULES].property_name, DBUS_TYPE_OBJECT_PATH, modules, n_modules); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENTS].property_name, DBUS_TYPE_OBJECT_PATH, clients, n_clients); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MY_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &my_client); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_EXTENSIONS].property_name, DBUS_TYPE_STRING, extensions, n_extensions); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(username); + pa_xfree(hostname); + pa_xfree(default_channels); + pa_xfree(cards); + pa_xfree(sinks); + pa_xfree(sources); + pa_xfree(playback_streams); + pa_xfree(record_streams); + pa_xfree(samples); + pa_xfree(modules); + pa_xfree(clients); + pa_xfree(extensions); +} + +static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *card_name; + pa_card *card; + pa_dbusiface_card *dbus_card; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &card_name, DBUS_TYPE_INVALID)); + + if (!(card = pa_namereg_get(c->core, card_name, PA_NAMEREG_CARD))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such card."); + return; + } + + pa_assert_se((dbus_card = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index)))); + + object_path = pa_dbusiface_card_get_path(dbus_card); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *sink_name; + pa_sink *sink; + pa_dbusiface_device *dbus_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sink_name, DBUS_TYPE_INVALID)); + + if (!(sink = pa_namereg_get(c->core, sink_name, PA_NAMEREG_SINK))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_name); + return; + } + + pa_assert_se((dbus_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index)))); + + object_path = pa_dbusiface_device_get_path(dbus_sink); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *source_name; + pa_source *source; + pa_dbusiface_device *dbus_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &source_name, DBUS_TYPE_INVALID)); + + if (!(source = pa_namereg_get(c->core, source_name, PA_NAMEREG_SOURCE))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", source_name); + return; + } + + pa_assert_se((dbus_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index)))); + + object_path = pa_dbusiface_device_get_path(dbus_source); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *sample_name; + pa_scache_entry *sample; + pa_dbusiface_sample *dbus_sample; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sample_name, DBUS_TYPE_INVALID)); + + if (!(sample = pa_namereg_get(c->core, sample_name, PA_NAMEREG_SAMPLE))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such sample."); + return; + } + + pa_assert_se((dbus_sample = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(sample->index)))); + + object_path = pa_dbusiface_sample_get_path(dbus_sample); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter msg_iter; + DBusMessageIter array_iter; + const char *name; + dbus_uint32_t sample_format; + dbus_uint32_t sample_rate; + const dbus_uint32_t *channels; + int n_channels; + const dbus_uint32_t *default_volume; + int n_volume_entries; + pa_proplist *property_list; + const uint8_t *data; + int data_length; + int i; + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk chunk; + uint32_t idx; + pa_dbusiface_sample *dbus_sample = NULL; + pa_scache_entry *sample = NULL; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + chunk.memblock = NULL; + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sample_format); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sample_rate); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &channels, &n_channels); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &default_volume, &n_volume_entries); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &data, &data_length); + + if (sample_format >= PA_SAMPLE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format."); + goto finish; + } + + if (sample_rate <= 0 || sample_rate > PA_RATE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate."); + goto finish; + } + + if (n_channels <= 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel map."); + goto finish; + } + + if (n_channels > (int) PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too many channels: %i. The maximum is %u.", n_channels, PA_CHANNELS_MAX); + goto finish; + } + + for (i = 0; i < n_channels; ++i) { + if (channels[i] >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position."); + goto finish; + } + } + + if (n_volume_entries != 0 && n_volume_entries != n_channels) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "The channels and default_volume arguments have different number of elements (%i and %i, resp).", + n_channels, n_volume_entries); + goto finish; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (default_volume[i] > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u.", default_volume[i]); + goto finish; + } + } + + if (data_length == 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty data."); + goto finish; + } + + if (data_length > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too big sample: %i bytes. The maximum sample length is %u bytes.", + data_length, PA_SCACHE_ENTRY_SIZE_MAX); + goto finish; + } + + ss.format = sample_format; + ss.rate = sample_rate; + ss.channels = n_channels; + + pa_assert(pa_sample_spec_valid(&ss)); + + if (!pa_frame_aligned(data_length, &ss)) { + char buf[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "The sample length (%i bytes) doesn't align with the sample format and channels (%s).", + data_length, pa_sample_spec_snprint(buf, sizeof(buf), &ss)); + goto finish; + } + + map.channels = n_channels; + for (i = 0; i < n_channels; ++i) + map.map[i] = channels[i]; + + chunk.memblock = pa_memblock_new(c->core->mempool, data_length); + chunk.index = 0; + chunk.length = data_length; + + memcpy(pa_memblock_acquire(chunk.memblock), data, data_length); + pa_memblock_release(chunk.memblock); + + if (pa_scache_add_item(c->core, name, &ss, &map, &chunk, property_list, &idx) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Adding the sample failed."); + goto finish; + } + + sample = pa_idxset_get_by_index(c->core->scache, idx); + + if (n_volume_entries > 0) { + sample->volume.channels = n_channels; + for (i = 0; i < n_volume_entries; ++i) + sample->volume.values[i] = default_volume[i]; + sample->volume_is_set = TRUE; + } else { + sample->volume_is_set = FALSE; + } + + dbus_sample = pa_dbusiface_sample_new(c, sample); + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), dbus_sample); + + object_path = pa_dbusiface_sample_get_path(dbus_sample); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); + +finish: + if (property_list) + pa_proplist_free(property_list); + + if (chunk.memblock) + pa_memblock_unref(chunk.memblock); +} + +static pa_bool_t contains_space(const char *string) { + const char *p; + + pa_assert(string); + + for (p = string; *p; ++p) { + if (isspace(*p)) + return TRUE; + } + + return FALSE; +} + +static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + char *name = NULL; + const char *key = NULL; + const char *value = NULL; + char *escaped_value = NULL; + pa_strbuf *arg_buffer = NULL; + char *arg_string = NULL; + pa_module *module = NULL; + pa_dbusiface_module *dbus_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (c->core->disallow_module_loading) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module loading."); + return; + } + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + arg_buffer = pa_strbuf_new(); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &dict_iter); + + while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { + if (!pa_strbuf_isempty(arg_buffer)) + pa_strbuf_putc(arg_buffer, ' '); + + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + + dbus_message_iter_get_basic(&dict_entry_iter, &key); + + if (strlen(key) <= 0 || !pa_ascii_valid(key) || contains_space(key)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid module argument name: %s", key); + goto finish; + } + + pa_assert_se(dbus_message_iter_next(&dict_entry_iter)); + dbus_message_iter_get_basic(&dict_entry_iter, &value); + + escaped_value = pa_escape(value, "\""); + pa_strbuf_printf(arg_buffer, "%s=\"%s\"", key, escaped_value); + pa_xfree(escaped_value); + + dbus_message_iter_next(&dict_iter); + } + + arg_string = pa_strbuf_tostring(arg_buffer); + + if (!(module = pa_module_load(c->core, name, arg_string))) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Failed to load module."); + goto finish; + } + + dbus_module = pa_dbusiface_module_new(module); + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(module->index), dbus_module); + + object_path = pa_dbusiface_module_get_path(dbus_module); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); + +finish: + if (arg_buffer) + pa_strbuf_free(arg_buffer); + + pa_xfree(arg_string); +} + +static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (c->core->disallow_exit) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow exiting."); + return; + } + + pa_dbus_send_empty_reply(conn, msg); + + pa_core_exit(c->core, FALSE, 0); +} + +static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *signal; + char **objects = NULL; + int n_objects; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objects, &n_objects, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_add_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL, objects, n_objects); + + pa_dbus_send_empty_reply(conn, msg); + + dbus_free_string_array(objects); +} + +static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *signal; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &signal, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_remove_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_card *card_iface = NULL; + pa_dbusiface_device *device_iface = NULL; + pa_dbusiface_stream *stream_iface = NULL; + pa_dbusiface_sample *sample_iface = NULL; + pa_dbusiface_module *module_iface = NULL; + pa_dbusiface_client *client_iface = NULL; + DBusMessage *signal = NULL; + const char *object_path = NULL; + pa_sink *new_fallback_sink = NULL; + pa_source *new_fallback_source = NULL; + + pa_assert(c); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SERVER: + new_fallback_sink = pa_namereg_get_default_sink(core); + new_fallback_source = pa_namereg_get_default_source(core); + + if (c->fallback_sink != new_fallback_sink) { + if (c->fallback_sink) + pa_sink_unref(c->fallback_sink); + c->fallback_sink = new_fallback_sink ? pa_sink_ref(new_fallback_sink) : NULL; + + if (new_fallback_sink + && (device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(new_fallback_sink->index)))) { + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + } else if (!new_fallback_sink) { + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UNSET].name))); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + + if (c->fallback_source != new_fallback_source) { + if (c->fallback_source) + pa_source_unref(c->fallback_source); + c->fallback_source = new_fallback_source ? pa_source_ref(new_fallback_source) : NULL; + + if (new_fallback_source + && (device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(new_fallback_source->index)))) { + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + } else if (!new_fallback_source) { + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UNSET].name))); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + if (!(card_iface = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(idx)))) { + pa_card *card = NULL; + + if (!(card = pa_idxset_get_by_index(core->cards, idx))) + return; /* The card was removed immediately after creation. */ + + card_iface = pa_dbusiface_card_new(c, card); + pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), card_iface); + } + + object_path = pa_dbusiface_card_get_path(card_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_CARD].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(card_iface = pa_hashmap_remove(c->cards, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_card_get_path(card_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_CARD_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_card_free(card_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_sink *sink = NULL; + + if (!(sink = pa_idxset_get_by_index(core->sinks, idx))) + return; /* The sink was removed immediately after creation. */ + + if (!(device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(idx)))) { + device_iface = pa_dbusiface_device_new_sink(c, sink); + pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device_iface); + pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device_iface), device_iface); + } + + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SINK].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + if (c->fallback_sink && pa_streq(c->fallback_sink->name, sink->name)) { + /* We have got default sink change event, but at that point + * the D-Bus sink object wasn't created yet. Now that the + * object is created, let's send the fallback sink change + * signal. */ + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(device_iface = pa_hashmap_remove(c->sinks_by_index, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_device_get_path(device_iface); + pa_assert_se(pa_hashmap_remove(c->sinks_by_path, object_path)); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SINK_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_device_free(device_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_source *source = pa_idxset_get_by_index(core->sources, idx); + + if (!(source = pa_idxset_get_by_index(core->sources, idx))) + return; /* The source was removed immediately after creation. */ + + if (!(device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(idx)))) { + device_iface = pa_dbusiface_device_new_source(c, source); + pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device_iface); + pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device_iface), device_iface); + } + + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SOURCE].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + if (c->fallback_source && pa_streq(c->fallback_source->name, source->name)) { + /* We have got default source change event, but at that + * point the D-Bus source object wasn't created yet. Now + * that the object is created, let's send the fallback + * source change signal. */ + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(device_iface = pa_hashmap_remove(c->sources_by_index, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_device_get_path(device_iface); + pa_assert_se(pa_hashmap_remove(c->sources_by_path, object_path)); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SOURCE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_device_free(device_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_sink_input *sink_input = NULL; + + if (!(sink_input = pa_idxset_get_by_index(core->sink_inputs, idx))) + return; /* The sink input was removed immediately after creation. */ + + if (!(stream_iface = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(idx)))) { + stream_iface = pa_dbusiface_stream_new_playback(c, sink_input); + pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), stream_iface); + } + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_PLAYBACK_STREAM].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(stream_iface = pa_hashmap_remove(c->playback_streams, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_PLAYBACK_STREAM_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_stream_free(stream_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_source_output *source_output = NULL; + + if (!(source_output = pa_idxset_get_by_index(core->source_outputs, idx))) + return; /* The source output was removed immediately after creation. */ + + if (!(stream_iface = pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(idx)))) { + stream_iface = pa_dbusiface_stream_new_record(c, source_output); + pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), stream_iface); + } + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_RECORD_STREAM].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(stream_iface = pa_hashmap_remove(c->record_streams, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_RECORD_STREAM_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_stream_free(stream_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_scache_entry *sample = NULL; + + if (!(sample = pa_idxset_get_by_index(core->scache, idx))) + return; /* The sample was removed immediately after creation. */ + + if (!(sample_iface = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(idx)))) { + sample_iface = pa_dbusiface_sample_new(c, sample); + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), sample_iface); + } + + object_path = pa_dbusiface_sample_get_path(sample_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SAMPLE].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(sample_iface = pa_hashmap_remove(c->samples, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_sample_get_path(sample_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SAMPLE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_sample_free(sample_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_MODULE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_module *module = NULL; + + if (!(module = pa_idxset_get_by_index(core->modules, idx))) + return; /* The module was removed immediately after creation. */ + + if (!(module_iface = pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(idx)))) { + module_iface = pa_dbusiface_module_new(module); + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), module_iface); + } + + object_path = pa_dbusiface_module_get_path(module_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_MODULE].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(module_iface = pa_hashmap_remove(c->modules, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_module_get_path(module_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_MODULE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_module_free(module_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_client *client = NULL; + + if (!(client = pa_idxset_get_by_index(core->clients, idx))) + return; /* The client was removed immediately after creation. */ + + if (!(client_iface = pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(idx)))) { + client_iface = pa_dbusiface_client_new(c, client); + pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), client_iface); + } + + object_path = pa_dbusiface_client_get_path(client_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_CLIENT].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(client_iface = pa_hashmap_remove(c->clients, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_client_get_path(client_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_CLIENT_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_client_free(client_iface); + } + break; + } + + if (signal) { + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + } +} + +static pa_hook_result_t extension_registered_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + const char *ext_name = call_data; + DBusMessage *signal = NULL; + + pa_assert(c); + pa_assert(ext_name); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_EXTENSION].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + + return PA_HOOK_OK; +} + +static pa_hook_result_t extension_unregistered_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + const char *ext_name = call_data; + DBusMessage *signal = NULL; + + pa_assert(c); + pa_assert(ext_name); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_EXTENSION_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + + return PA_HOOK_OK; +} + +pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core) { + pa_dbusiface_core *c; + pa_card *card; + pa_sink *sink; + pa_source *source; + pa_dbusiface_device *device; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_scache_entry *sample; + pa_module *module; + pa_client *client; + uint32_t idx; + + pa_assert(core); + + c = pa_xnew(pa_dbusiface_core, 1); + c->core = pa_core_ref(core); + c->subscription = pa_subscription_new(core, PA_SUBSCRIPTION_MASK_ALL, subscription_cb, c); + c->dbus_protocol = pa_dbus_protocol_get(core); + c->cards = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sinks_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sinks_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->sources_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sources_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->playback_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->record_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->samples = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->modules = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->fallback_sink = pa_namereg_get_default_sink(core); + c->fallback_source = pa_namereg_get_default_source(core); + c->extension_registered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol, + PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, + PA_HOOK_NORMAL, + extension_registered_cb, + c); + c->extension_unregistered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol, + PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, + PA_HOOK_NORMAL, + extension_unregistered_cb, + c); + c->memstats = pa_dbusiface_memstats_new(c, core); + + if (c->fallback_sink) + pa_sink_ref(c->fallback_sink); + if (c->fallback_source) + pa_source_ref(c->fallback_source); + + PA_IDXSET_FOREACH(card, core->cards, idx) + pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), pa_dbusiface_card_new(c, card)); + + PA_IDXSET_FOREACH(sink, core->sinks, idx) { + device = pa_dbusiface_device_new_sink(c, sink); + pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device); + pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device), device); + } + + PA_IDXSET_FOREACH(source, core->sources, idx) { + device = pa_dbusiface_device_new_source(c, source); + pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device); + pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device), device); + } + + PA_IDXSET_FOREACH(sink_input, core->sink_inputs, idx) + pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_playback(c, sink_input)); + + PA_IDXSET_FOREACH(source_output, core->source_outputs, idx) + pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_record(c, source_output)); + + PA_IDXSET_FOREACH(sample, core->scache, idx) + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), pa_dbusiface_sample_new(c, sample)); + + PA_IDXSET_FOREACH(module, core->modules, idx) + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), pa_dbusiface_module_new(module)); + + PA_IDXSET_FOREACH(client, core->clients, idx) + pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), pa_dbusiface_client_new(c, client)); + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, &core_interface_info, c) >= 0); + + return c; +} + +static void free_card_cb(void *p, void *userdata) { + pa_dbusiface_card *c = p; + + pa_assert(c); + + pa_dbusiface_card_free(c); +} + +static void free_device_cb(void *p, void *userdata) { + pa_dbusiface_device *d = p; + + pa_assert(d); + + pa_dbusiface_device_free(d); +} + +static void free_stream_cb(void *p, void *userdata) { + pa_dbusiface_stream *s = p; + + pa_assert(s); + + pa_dbusiface_stream_free(s); +} + +static void free_sample_cb(void *p, void *userdata) { + pa_dbusiface_sample *s = p; + + pa_assert(s); + + pa_dbusiface_sample_free(s); +} + +static void free_module_cb(void *p, void *userdata) { + pa_dbusiface_module *m = p; + + pa_assert(m); + + pa_dbusiface_module_free(m); +} + +static void free_client_cb(void *p, void *userdata) { + pa_dbusiface_client *c = p; + + pa_assert(c); + + pa_dbusiface_client_free(c); +} + +void pa_dbusiface_core_free(pa_dbusiface_core *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, core_interface_info.name) >= 0); + + pa_subscription_free(c->subscription); + pa_hashmap_free(c->cards, free_card_cb, NULL); + pa_hashmap_free(c->sinks_by_index, free_device_cb, NULL); + pa_hashmap_free(c->sinks_by_path, NULL, NULL); + pa_hashmap_free(c->sources_by_index, free_device_cb, NULL); + pa_hashmap_free(c->sources_by_path, NULL, NULL); + pa_hashmap_free(c->playback_streams, free_stream_cb, NULL); + pa_hashmap_free(c->record_streams, free_stream_cb, NULL); + pa_hashmap_free(c->samples, free_sample_cb, NULL); + pa_hashmap_free(c->modules, free_module_cb, NULL); + pa_hashmap_free(c->clients, free_client_cb, NULL); + pa_hook_slot_free(c->extension_registered_slot); + pa_hook_slot_free(c->extension_unregistered_slot); + pa_dbusiface_memstats_free(c->memstats); + + if (c->fallback_sink) + pa_sink_unref(c->fallback_sink); + if (c->fallback_source) + pa_source_unref(c->fallback_source); + + pa_dbus_protocol_unref(c->dbus_protocol); + pa_core_unref(c->core); + + pa_xfree(c); +} + +const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card) { + pa_assert(c); + pa_assert(card); + + return pa_dbusiface_card_get_path(pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index))); +} + +const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink) { + pa_assert(c); + pa_assert(sink); + + return pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index))); +} + +const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source) { + pa_assert(c); + pa_assert(source); + + return pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index))); +} + +const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input) { + pa_assert(c); + pa_assert(sink_input); + + return pa_dbusiface_stream_get_path(pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(sink_input->index))); +} + +const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output) { + pa_assert(c); + pa_assert(source_output); + + return pa_dbusiface_stream_get_path(pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(source_output->index))); +} + +const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module) { + pa_assert(c); + pa_assert(module); + + return pa_dbusiface_module_get_path(pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(module->index))); +} + +const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client) { + pa_assert(c); + pa_assert(client); + + return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(client->index))); +} + +pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path) { + pa_dbusiface_device *device = NULL; + + pa_assert(c); + pa_assert(object_path); + + device = pa_hashmap_get(c->sinks_by_path, object_path); + + if (device) + return pa_dbusiface_device_get_sink(device); + else + return NULL; +} + +pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path) { + pa_dbusiface_device *device = NULL; + + pa_assert(c); + pa_assert(object_path); + + device = pa_hashmap_get(c->sources_by_path, object_path); + + if (device) + return pa_dbusiface_device_get_source(device); + else + return NULL; +} diff --git a/src/modules/dbus/iface-core.h b/src/modules/dbus/iface-core.h new file mode 100644 index 00000000..900b6d1c --- /dev/null +++ b/src/modules/dbus/iface-core.h @@ -0,0 +1,52 @@ +#ifndef foodbusifacecorehfoo +#define foodbusifacecorehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Core interface + * documentation. + */ + +#include <pulsecore/core.h> + +typedef struct pa_dbusiface_core pa_dbusiface_core; + +pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core); +void pa_dbusiface_core_free(pa_dbusiface_core *c); + +const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card); +const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink); +const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source); +const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input); +const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output); +const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module); +const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client); + +/* Returns NULL if there's no sink with the given path. */ +pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path); + +/* Returns NULL if there's no source with the given path. */ +pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path); + +#endif diff --git a/src/modules/dbus/iface-device-port.c b/src/modules/dbus/iface-device-port.c new file mode 100644 index 00000000..d403b6a2 --- /dev/null +++ b/src/modules/dbus/iface-device-port.c @@ -0,0 +1,190 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> + +#include "iface-device-port.h" + +#define OBJECT_NAME "port" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_device_port { + uint32_t index; + pa_device_port *port; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DESCRIPTION, + PROPERTY_HANDLER_PRIORITY, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL }, + [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL }, +}; + +static pa_dbus_interface_info port_interface_info = { + .name = PA_DBUSIFACE_DEVICE_PORT_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->name); +} + +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->description); +} + +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->port->priority; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->port->priority; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->port->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->port->description); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +pa_dbusiface_device_port *pa_dbusiface_device_port_new( + pa_dbusiface_device *device, + pa_core *core, + pa_device_port *port, + uint32_t idx) { + pa_dbusiface_device_port *p = NULL; + + pa_assert(device); + pa_assert(core); + pa_assert(port); + + p = pa_xnew(pa_dbusiface_device_port, 1); + p->index = idx; + p->port = port; + p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_device_get_path(device), OBJECT_NAME, idx); + p->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &port_interface_info, p) >= 0); + + return p; +} + +void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p) { + pa_assert(p); + + pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, port_interface_info.name) >= 0); + + pa_dbus_protocol_unref(p->dbus_protocol); + + pa_xfree(p->path); + pa_xfree(p); +} + +const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p) { + pa_assert(p); + + return p->path; +} + +const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p) { + pa_assert(p); + + return p->port->name; +} diff --git a/src/modules/dbus/iface-device-port.h b/src/modules/dbus/iface-device-port.h new file mode 100644 index 00000000..0461e2ff --- /dev/null +++ b/src/modules/dbus/iface-device-port.h @@ -0,0 +1,50 @@ +#ifndef foodbusifacedeviceporthfoo +#define foodbusifacedeviceporthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.DevicePort. + * + * See http://pulseaudio.org/wiki/DBusInterface for the DevicePort interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink.h> + +#include "iface-device.h" + +#define PA_DBUSIFACE_DEVICE_PORT_INTERFACE PA_DBUS_CORE_INTERFACE ".DevicePort" + +typedef struct pa_dbusiface_device_port pa_dbusiface_device_port; + +pa_dbusiface_device_port *pa_dbusiface_device_port_new( + pa_dbusiface_device *device, + pa_core *core, + pa_device_port *port, + uint32_t idx); +void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p); + +const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p); +const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p); + +#endif diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c new file mode 100644 index 00000000..3a747a44 --- /dev/null +++ b/src/modules/dbus/iface-device.c @@ -0,0 +1,1316 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-device-port.h" + +#include "iface-device.h" + +#define SINK_OBJECT_NAME "sink" +#define SOURCE_OBJECT_NAME "source" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum device_type { + DEVICE_TYPE_SINK, + DEVICE_TYPE_SOURCE +}; + +struct pa_dbusiface_device { + pa_dbusiface_core *core; + + union { + pa_sink *sink; + pa_source *source; + }; + enum device_type type; + char *path; + pa_cvolume volume; + dbus_bool_t mute; + union { + pa_sink_state_t sink_state; + pa_source_state_t source_state; + }; + pa_hashmap *ports; + uint32_t next_port_index; + pa_device_port *active_port; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_CARD, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_VOLUME, + PROPERTY_HANDLER_HAS_FLAT_VOLUME, + PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME, + PROPERTY_HANDLER_BASE_VOLUME, + PROPERTY_HANDLER_VOLUME_STEPS, + PROPERTY_HANDLER_MUTE, + PROPERTY_HANDLER_HAS_HARDWARE_VOLUME, + PROPERTY_HANDLER_HAS_HARDWARE_MUTE, + PROPERTY_HANDLER_CONFIGURED_LATENCY, + PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY, + PROPERTY_HANDLER_LATENCY, + PROPERTY_HANDLER_IS_HARDWARE_DEVICE, + PROPERTY_HANDLER_IS_NETWORK_DEVICE, + PROPERTY_HANDLER_STATE, + PROPERTY_HANDLER_PORTS, + PROPERTY_HANDLER_ACTIVE_PORT, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +enum sink_property_handler_index { + SINK_PROPERTY_HANDLER_MONITOR_SOURCE, + SINK_PROPERTY_HANDLER_MAX +}; + +enum source_property_handler_index { + SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK, + SOURCE_PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_CARD] = { .property_name = "Card", .type = "o", .get_cb = handle_get_card, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL }, + [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume }, + [PROPERTY_HANDLER_HAS_FLAT_VOLUME] = { .property_name = "HasFlatVolume", .type = "b", .get_cb = handle_get_has_flat_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME] = { .property_name = "HasConvertibleToDecibelVolume", .type = "b", .get_cb = handle_get_has_convertible_to_decibel_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_BASE_VOLUME] = { .property_name = "BaseVolume", .type = "u", .get_cb = handle_get_base_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME_STEPS] = { .property_name = "VolumeSteps", .type = "u", .get_cb = handle_get_volume_steps, .set_cb = NULL }, + [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute }, + [PROPERTY_HANDLER_HAS_HARDWARE_VOLUME] = { .property_name = "HasHardwareVolume", .type = "b", .get_cb = handle_get_has_hardware_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_HARDWARE_MUTE] = { .property_name = "HasHardwareMute", .type = "b", .get_cb = handle_get_has_hardware_mute, .set_cb = NULL }, + [PROPERTY_HANDLER_CONFIGURED_LATENCY] = { .property_name = "ConfiguredLatency", .type = "t", .get_cb = handle_get_configured_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY] = { .property_name = "HasDynamicLatency", .type = "b", .get_cb = handle_get_has_dynamic_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_LATENCY] = { .property_name = "Latency", .type = "t", .get_cb = handle_get_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_HARDWARE_DEVICE] = { .property_name = "IsHardwareDevice", .type = "b", .get_cb = handle_get_is_hardware_device, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_NETWORK_DEVICE] = { .property_name = "IsNetworkDevice", .type = "b", .get_cb = handle_get_is_network_device, .set_cb = NULL }, + [PROPERTY_HANDLER_STATE] = { .property_name = "State", .type = "u", .get_cb = handle_get_state, .set_cb = NULL }, + [PROPERTY_HANDLER_PORTS] = { .property_name = "Ports", .type = "ao", .get_cb = handle_get_ports, .set_cb = NULL }, + [PROPERTY_HANDLER_ACTIVE_PORT] = { .property_name = "ActivePort", .type = "o", .get_cb = handle_get_active_port, .set_cb = handle_set_active_port }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +static pa_dbus_property_handler sink_property_handlers[SINK_PROPERTY_HANDLER_MAX] = { + [SINK_PROPERTY_HANDLER_MONITOR_SOURCE] = { .property_name = "MonitorSource", .type = "o", .get_cb = handle_sink_get_monitor_source, .set_cb = NULL } +}; + +static pa_dbus_property_handler source_property_handlers[SOURCE_PROPERTY_HANDLER_MAX] = { + [SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK] = { .property_name = "MonitorOfSink", .type = "o", .get_cb = handle_source_get_monitor_of_sink, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_SUSPEND, + METHOD_HANDLER_GET_PORT_BY_NAME, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info suspend_args[] = { { "suspend", "b", "in" } }; +static pa_dbus_arg_info get_port_by_name_args[] = { { "name", "s", "in" }, { "port", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_SUSPEND] = { + .method_name = "Suspend", + .arguments = suspend_args, + .n_arguments = sizeof(suspend_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_suspend }, + [METHOD_HANDLER_GET_PORT_BY_NAME] = { + .method_name = "GetPortByName", + .arguments = get_port_by_name_args, + .n_arguments = sizeof(get_port_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_port_by_name } +}; + +enum signal_index { + SIGNAL_VOLUME_UPDATED, + SIGNAL_MUTE_UPDATED, + SIGNAL_STATE_UPDATED, + SIGNAL_ACTIVE_PORT_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } }; +static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } }; +static pa_dbus_arg_info state_updated_args[] = { { "state", "u", NULL } }; +static pa_dbus_arg_info active_port_updated_args[] = { { "port", "o", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 }, + [SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 }, + [SIGNAL_STATE_UPDATED] = { .name = "StateUpdated", .arguments = state_updated_args, .n_arguments = 1 }, + [SIGNAL_ACTIVE_PORT_UPDATED] = { .name = "ActivePortUpdated", .arguments = active_port_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info device_interface_info = { + .name = PA_DBUSIFACE_DEVICE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static pa_dbus_interface_info sink_interface_info = { + .name = PA_DBUSIFACE_SINK_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = sink_property_handlers, + .n_property_handlers = SINK_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_sink_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static pa_dbus_interface_info source_interface_info = { + .name = PA_DBUSIFACE_SOURCE_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = source_property_handlers, + .n_property_handlers = SOURCE_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_source_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + idx = (d->type == DEVICE_TYPE_SINK) ? d->sink->index : d->source->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *name = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + name = (d->type == DEVICE_TYPE_SINK) ? d->sink->name : d->source->name; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &name); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *driver = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + driver = (d->type == DEVICE_TYPE_SINK) ? d->sink->driver : d->source->driver; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_module *owner_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + owner_module = (d->type == DEVICE_TYPE_SINK) ? d->sink->module : d->source->module; + + if (!owner_module) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't have an owner module.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't have an owner module.", d->source->name); + return; + } + + object_path = pa_dbusiface_core_get_module_path(d->core, owner_module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_card *card = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + card = (d->type == DEVICE_TYPE_SINK) ? d->sink->card : d->source->card; + + if (!card) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't belong to any card.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't belong to any card.", d->source->name); + return; + } + + object_path = pa_dbusiface_core_get_card_path(d->core, card); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + sample_format = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.format : d->source->sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format); +} + +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t sample_rate = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + sample_rate = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.rate : d->source->sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + channel_map = (d->type == DEVICE_TYPE_SINK) ? &d->sink->channel_map : &d->source->channel_map; + + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels); +} + +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, d->volume.channels); +} + +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessageIter array_iter; + int device_channels = 0; + dbus_uint32_t *volume = NULL; + int n_volume_entries = 0; + pa_cvolume new_vol; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + pa_cvolume_init(&new_vol); + + device_channels = (d->type == DEVICE_TYPE_SINK) ? d->sink->channel_map.channels : d->source->channel_map.channels; + + new_vol.channels = device_channels; + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries); + + if (n_volume_entries != device_channels) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Expected %u volume entries, got %i.", device_channels, n_volume_entries); + return; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (volume[i] > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]); + return; + } + new_vol.values[i] = volume[i]; + } + + if (d->type == DEVICE_TYPE_SINK) + pa_sink_set_volume(d->sink, &new_vol, TRUE, TRUE); + else + pa_source_set_volume(d->source, &new_vol, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_flat_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_flat_volume = (d->type == DEVICE_TYPE_SINK) ? (d->sink->flags & PA_SINK_FLAT_VOLUME) : FALSE; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_flat_volume); +} + +static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_convertible_to_decibel_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_convertible_to_decibel_volume = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_DECIBEL_VOLUME) + : (d->source->flags & PA_SOURCE_DECIBEL_VOLUME); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume); +} + +static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t base_volume; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + base_volume = (d->type == DEVICE_TYPE_SINK) ? d->sink->base_volume : d->source->base_volume; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &base_volume); +} + +static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t volume_steps; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + volume_steps = (d->type == DEVICE_TYPE_SINK) ? d->sink->n_volume_steps : d->source->n_volume_steps; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &volume_steps); +} + +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &d->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + dbus_message_iter_get_basic(iter, &mute); + + if (d->type == DEVICE_TYPE_SINK) + pa_sink_set_mute(d->sink, mute, TRUE); + else + pa_source_set_mute(d->source, mute, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_hardware_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_hardware_volume = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HW_VOLUME_CTRL) + : (d->source->flags & PA_SOURCE_HW_VOLUME_CTRL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_volume); +} + +static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_hardware_mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_hardware_mute = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HW_MUTE_CTRL) + : (d->source->flags & PA_SOURCE_HW_MUTE_CTRL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_mute); +} + +static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint64_t configured_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + configured_latency = (d->type == DEVICE_TYPE_SINK) + ? pa_sink_get_requested_latency(d->sink) + : pa_source_get_requested_latency(d->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &configured_latency); +} + +static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_dynamic_latency = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_dynamic_latency = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_DYNAMIC_LATENCY) + : (d->source->flags & PA_SOURCE_DYNAMIC_LATENCY); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_dynamic_latency); +} + +static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint64_t latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (d->type == DEVICE_TYPE_SINK && !(d->sink->flags & PA_SINK_LATENCY)) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't support latency querying.", d->sink->name); + else if (d->type == DEVICE_TYPE_SOURCE && !(d->source->flags & PA_SOURCE_LATENCY)) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't support latency querying.", d->source->name); + return; + + latency = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_latency(d->sink) : pa_source_get_latency(d->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &latency); +} + +static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t is_hardware_device = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + is_hardware_device = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HARDWARE) + : (d->source->flags & PA_SOURCE_HARDWARE); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_hardware_device); +} + +static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t is_network_device = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + is_network_device = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_NETWORK) + : (d->source->flags & PA_SOURCE_NETWORK); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_network_device); +} + +static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t state; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &state); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_ports(pa_dbusiface_device *d, unsigned *n) { + const char **ports; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device_port *port = NULL; + + pa_assert(d); + pa_assert(n); + + *n = pa_hashmap_size(d->ports); + + if (*n == 0) + return NULL; + + ports = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(port, d->ports, state) + ports[i++] = pa_dbusiface_device_port_get_path(port); + + return ports; +} + +static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char **ports = NULL; + unsigned n_ports = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + ports = get_ports(d, &n_ports); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, ports, n_ports); + + pa_xfree(ports); +} + +static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *active_port; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (!d->active_port) { + pa_assert(pa_hashmap_isempty(d->ports)); + + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The sink %s has no ports, and therefore there's no active port either.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The source %s has no ports, and therefore there's no active port either.", d->source->name); + return; + } + + active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_port); +} + +static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *new_active_path; + pa_dbusiface_device_port *new_active; + int r; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + if (!d->active_port) { + pa_assert(pa_hashmap_isempty(d->ports)); + + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The sink %s has no ports, and therefore there's no active port either.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The source %s has no ports, and therefore there's no active port either.", d->source->name); + return; + } + + dbus_message_iter_get_basic(iter, &new_active_path); + + if (!(new_active = pa_hashmap_get(d->ports, new_active_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such port: %s", new_active_path); + return; + } + + if (d->type == DEVICE_TYPE_SINK) { + if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_sink_set_port() failed with error code %i.", r); + return; + } + } else { + if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_source_set_port() failed with error code %i.", r); + return; + } + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_dbus_send_proplist_variant_reply(conn, msg, d->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *name = NULL; + const char *driver = NULL; + pa_module *owner_module = NULL; + const char *owner_module_path = NULL; + pa_card *card = NULL; + const char *card_path = NULL; + dbus_uint32_t sample_format = 0; + dbus_uint32_t sample_rate = 0; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_bool_t has_flat_volume = FALSE; + dbus_bool_t has_convertible_to_decibel_volume = FALSE; + dbus_uint32_t base_volume = 0; + dbus_uint32_t volume_steps = 0; + dbus_bool_t has_hardware_volume = FALSE; + dbus_bool_t has_hardware_mute = FALSE; + dbus_uint64_t configured_latency = 0; + dbus_bool_t has_dynamic_latency = FALSE; + dbus_uint64_t latency = 0; + dbus_bool_t is_hardware_device = FALSE; + dbus_bool_t is_network_device = FALSE; + dbus_uint32_t state = 0; + const char **ports = NULL; + unsigned n_ports = 0; + const char *active_port = NULL; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (d->type == DEVICE_TYPE_SINK) { + idx = d->sink->index; + name = d->sink->name; + driver = d->sink->driver; + owner_module = d->sink->module; + card = d->sink->card; + sample_format = d->sink->sample_spec.format; + sample_rate = d->sink->sample_spec.rate; + channel_map = &d->sink->channel_map; + has_flat_volume = d->sink->flags & PA_SINK_FLAT_VOLUME; + has_convertible_to_decibel_volume = d->sink->flags & PA_SINK_DECIBEL_VOLUME; + base_volume = d->sink->base_volume; + volume_steps = d->sink->n_volume_steps; + has_hardware_volume = d->sink->flags & PA_SINK_HW_VOLUME_CTRL; + has_hardware_mute = d->sink->flags & PA_SINK_HW_MUTE_CTRL; + configured_latency = pa_sink_get_requested_latency(d->sink); + has_dynamic_latency = d->sink->flags & PA_SINK_DYNAMIC_LATENCY; + latency = pa_sink_get_latency(d->sink); + is_hardware_device = d->sink->flags & PA_SINK_HARDWARE; + is_network_device = d->sink->flags & PA_SINK_NETWORK; + state = pa_sink_get_state(d->sink); + } else { + idx = d->source->index; + name = d->source->name; + driver = d->source->driver; + owner_module = d->source->module; + card = d->source->card; + sample_format = d->source->sample_spec.format; + sample_rate = d->source->sample_spec.rate; + channel_map = &d->source->channel_map; + has_flat_volume = FALSE; + has_convertible_to_decibel_volume = d->source->flags & PA_SOURCE_DECIBEL_VOLUME; + base_volume = d->source->base_volume; + volume_steps = d->source->n_volume_steps; + has_hardware_volume = d->source->flags & PA_SOURCE_HW_VOLUME_CTRL; + has_hardware_mute = d->source->flags & PA_SOURCE_HW_MUTE_CTRL; + configured_latency = pa_source_get_requested_latency(d->source); + has_dynamic_latency = d->source->flags & PA_SOURCE_DYNAMIC_LATENCY; + latency = pa_source_get_latency(d->source); + is_hardware_device = d->source->flags & PA_SOURCE_HARDWARE; + is_network_device = d->source->flags & PA_SOURCE_NETWORK; + state = pa_source_get_state(d->source); + } + if (owner_module) + owner_module_path = pa_dbusiface_core_get_module_path(d->core, owner_module); + if (card) + card_path = pa_dbusiface_core_get_card_path(d->core, card); + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + ports = get_ports(d, &n_ports); + if (d->active_port) + active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path); + + if (card) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARD].property_name, DBUS_TYPE_OBJECT_PATH, &card_path); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, d->volume.channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_FLAT_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_flat_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BASE_VOLUME].property_name, DBUS_TYPE_UINT32, &base_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME_STEPS].property_name, DBUS_TYPE_UINT32, &volume_steps); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &d->mute); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_MUTE].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_mute); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CONFIGURED_LATENCY].property_name, DBUS_TYPE_UINT64, &configured_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY].property_name, DBUS_TYPE_BOOLEAN, &has_dynamic_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_LATENCY].property_name, DBUS_TYPE_UINT64, &latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_HARDWARE_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_hardware_device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_NETWORK_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_network_device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_STATE].property_name, DBUS_TYPE_UINT32, &state); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PORTS].property_name, DBUS_TYPE_OBJECT_PATH, ports, n_ports); + + if (active_port) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PORT].property_name, DBUS_TYPE_OBJECT_PATH, &active_port); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, d->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(ports); +} + +static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t suspend = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &suspend, DBUS_TYPE_INVALID)); + + if ((d->type == DEVICE_TYPE_SINK) && (pa_sink_suspend(d->sink, suspend, PA_SUSPEND_USER) < 0)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_sink_suspend() failed."); + return; + } else if ((d->type == DEVICE_TYPE_SOURCE) && (pa_source_suspend(d->source, suspend, PA_SUSPEND_USER) < 0)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_source_suspend() failed."); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *port_name = NULL; + pa_dbusiface_device_port *port = NULL; + const char *port_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &port_name, DBUS_TYPE_INVALID)); + + if (!(port = pa_hashmap_get(d->ports, port_name))) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, + "%s: No such port on sink %s.", port_name, d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, + "%s: No such port on source %s.", port_name, d->source->name); + return; + } + + port_path = pa_dbusiface_device_port_get_path(port); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &port_path); +} + +static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *monitor_source = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_source); +} + +static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *monitor_source = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SINK_PROPERTY_HANDLER_MONITOR_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_source); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *monitor_of_sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + if (!d->source->monitor_of) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Source %s is not a monitor source.", d->source->name); + return; + } + + monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink); +} + +static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *monitor_of_sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + if (d->source->monitor_of) + monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + if (monitor_of_sink) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *signal = NULL; + const pa_cvolume *new_volume = NULL; + pa_bool_t new_mute = FALSE; + pa_sink_state_t new_sink_state = 0; + pa_source_state_t new_source_state = 0; + pa_device_port *new_active_port = NULL; + pa_proplist *new_proplist = NULL; + unsigned i = 0; + + pa_assert(c); + pa_assert(d); + + if ((d->type == DEVICE_TYPE_SINK && idx != d->sink->index) || (d->type == DEVICE_TYPE_SOURCE && idx != d->source->index)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + pa_assert(((d->type == DEVICE_TYPE_SINK) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)) + || ((d->type == DEVICE_TYPE_SOURCE) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE))); + + new_volume = (d->type == DEVICE_TYPE_SINK) + ? pa_sink_get_volume(d->sink, FALSE) + : pa_source_get_volume(d->source, FALSE); + + if (!pa_cvolume_equal(&d->volume, new_volume)) { + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint32_t *volume_ptr = volume; + + d->volume = *new_volume; + + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_VOLUME_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, d->volume.channels, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_mute = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_mute(d->sink, FALSE) : pa_source_get_mute(d->source, FALSE); + + if (d->mute != new_mute) { + d->mute = new_mute; + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &d->mute, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + if (d->type == DEVICE_TYPE_SINK) + new_sink_state = pa_sink_get_state(d->sink); + else + new_source_state = pa_source_get_state(d->source); + + if ((d->type == DEVICE_TYPE_SINK && d->sink_state != new_sink_state) + || (d->type == DEVICE_TYPE_SOURCE && d->source_state != new_source_state)) { + dbus_uint32_t state = 0; + + if (d->type == DEVICE_TYPE_SINK) + d->sink_state = new_sink_state; + else + d->source_state = new_source_state; + + state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state; + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_STATE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_active_port = (d->type == DEVICE_TYPE_SINK) ? d->sink->active_port : d->source->active_port; + + if (d->active_port != new_active_port) { + const char *object_path = NULL; + + d->active_port = new_active_port; + object_path = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_ACTIVE_PORT_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_proplist = (d->type == DEVICE_TYPE_SINK) ? d->sink->proplist : d->source->proplist; + + if (!pa_proplist_equal(d->proplist, new_proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(d->proplist, PA_UPDATE_SET, new_proplist); + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, d->proplist); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink) { + pa_dbusiface_device *d = NULL; + + pa_assert(core); + pa_assert(sink); + + d = pa_xnew0(pa_dbusiface_device, 1); + d->core = core; + d->sink = pa_sink_ref(sink); + d->type = DEVICE_TYPE_SINK; + d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SINK_OBJECT_NAME, sink->index); + d->volume = *pa_sink_get_volume(sink, FALSE); + d->mute = pa_sink_get_mute(sink, FALSE); + d->sink_state = pa_sink_get_state(sink); + d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + d->next_port_index = 0; + d->active_port = NULL; + d->proplist = pa_proplist_copy(sink->proplist); + d->dbus_protocol = pa_dbus_protocol_get(sink->core); + d->subscription = pa_subscription_new(sink->core, PA_SUBSCRIPTION_MASK_SINK, subscription_cb, d); + + if (sink->ports) { + pa_device_port *port; + void *state = NULL; + + PA_HASHMAP_FOREACH(port, sink->ports, state) { + pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, sink->core, port, d->next_port_index++); + pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p); + } + pa_assert_se(d->active_port = sink->active_port); + } + + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0); + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &sink_interface_info, d) >= 0); + + return d; +} + +pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source) { + pa_dbusiface_device *d = NULL; + + pa_assert(core); + pa_assert(source); + + d = pa_xnew0(pa_dbusiface_device, 1); + d->core = core; + d->source = pa_source_ref(source); + d->type = DEVICE_TYPE_SOURCE; + d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SOURCE_OBJECT_NAME, source->index); + d->volume = *pa_source_get_volume(source, FALSE); + d->mute = pa_source_get_mute(source, FALSE); + d->source_state = pa_source_get_state(source); + d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + d->next_port_index = 0; + d->active_port = NULL; + d->proplist = pa_proplist_copy(source->proplist); + d->dbus_protocol = pa_dbus_protocol_get(source->core); + d->subscription = pa_subscription_new(source->core, PA_SUBSCRIPTION_MASK_SOURCE, subscription_cb, d); + + if (source->ports) { + pa_device_port *port; + void *state = NULL; + + PA_HASHMAP_FOREACH(port, source->ports, state) { + pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, source->core, port, d->next_port_index++); + pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p); + } + pa_assert_se(d->active_port = source->active_port); + } + + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0); + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &source_interface_info, d) >= 0); + + return d; +} + +static void port_free_cb(void *p, void *userdata) { + pa_dbusiface_device_port *port = p; + + pa_assert(port); + + pa_dbusiface_device_port_free(port); +} + +void pa_dbusiface_device_free(pa_dbusiface_device *d) { + pa_assert(d); + + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, device_interface_info.name) >= 0); + + if (d->type == DEVICE_TYPE_SINK) { + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, sink_interface_info.name) >= 0); + pa_sink_unref(d->sink); + + } else { + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, source_interface_info.name) >= 0); + pa_source_unref(d->source); + } + pa_hashmap_free(d->ports, port_free_cb, NULL); + pa_proplist_free(d->proplist); + pa_dbus_protocol_unref(d->dbus_protocol); + pa_subscription_free(d->subscription); + + pa_xfree(d->path); + pa_xfree(d); +} + +const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d) { + pa_assert(d); + + return d->path; +} + +pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d) { + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + return d->sink; +} + +pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d) { + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + return d->source; +} diff --git a/src/modules/dbus/iface-device.h b/src/modules/dbus/iface-device.h new file mode 100644 index 00000000..62e05e9a --- /dev/null +++ b/src/modules/dbus/iface-device.h @@ -0,0 +1,53 @@ +#ifndef foodbusifacedevicehfoo +#define foodbusifacedevicehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interfaces org.PulseAudio.Core1.Device, + * org.PulseAudio.Core1.Sink and org.PulseAudio.Core1.Source. + * + * See http://pulseaudio.org/wiki/DBusInterface for the interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_DEVICE_INTERFACE PA_DBUS_CORE_INTERFACE ".Device" +#define PA_DBUSIFACE_SINK_INTERFACE PA_DBUS_CORE_INTERFACE ".Sink" +#define PA_DBUSIFACE_SOURCE_INTERFACE PA_DBUS_CORE_INTERFACE ".Source" + +typedef struct pa_dbusiface_device pa_dbusiface_device; + +pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink); +pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source); +void pa_dbusiface_device_free(pa_dbusiface_device *d); + +const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d); + +pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d); +pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d); + +#endif diff --git a/src/modules/dbus/iface-memstats.c b/src/modules/dbus/iface-memstats.c new file mode 100644 index 00000000..73a84be8 --- /dev/null +++ b/src/modules/dbus/iface-memstats.c @@ -0,0 +1,231 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-scache.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-memstats.h" + +#define OBJECT_NAME "memstats" + +static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_memstats { + pa_core *core; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_CURRENT_MEMBLOCKS, + PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE, + PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS, + PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE, + PROPERTY_HANDLER_SAMPLE_CACHE_SIZE, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_CURRENT_MEMBLOCKS] = { .property_name = "CurrentMemblocks", .type = "u", .get_cb = handle_get_current_memblocks, .set_cb = NULL }, + [PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE] = { .property_name = "CurrentMemblocksSize", .type = "u", .get_cb = handle_get_current_memblocks_size, .set_cb = NULL }, + [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS] = { .property_name = "AccumulatedMemblocks", .type = "u", .get_cb = handle_get_accumulated_memblocks, .set_cb = NULL }, + [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE] = { .property_name = "AccumulatedMemblocksSize", .type = "u", .get_cb = handle_get_accumulated_memblocks_size, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_CACHE_SIZE] = { .property_name = "SampleCacheSize", .type = "u", .get_cb = handle_get_sample_cache_size, .set_cb = NULL } +}; + +static pa_dbus_interface_info memstats_interface_info = { + .name = PA_DBUSIFACE_MEMSTATS_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks = pa_atomic_load(&stat->n_allocated); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, ¤t_memblocks); +} + +static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks_size = pa_atomic_load(&stat->allocated_size); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, ¤t_memblocks_size); +} + +static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t accumulated_memblocks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + accumulated_memblocks = pa_atomic_load(&stat->n_accumulated); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks); +} + +static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t accumulated_memblocks_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks_size); +} + +static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + dbus_uint32_t sample_cache_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + sample_cache_size = pa_scache_total_size(m->core); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_cache_size); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks; + dbus_uint32_t current_memblocks_size; + dbus_uint32_t accumulated_memblocks; + dbus_uint32_t accumulated_memblocks_size; + dbus_uint32_t sample_cache_size; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks = pa_atomic_load(&stat->n_allocated); + current_memblocks_size = pa_atomic_load(&stat->allocated_size); + accumulated_memblocks = pa_atomic_load(&stat->n_accumulated); + accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size); + sample_cache_size = pa_scache_total_size(m->core); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, ¤t_memblocks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, ¤t_memblocks_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_CACHE_SIZE].property_name, DBUS_TYPE_UINT32, &sample_cache_size); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core) { + pa_dbusiface_memstats *m; + + pa_assert(dbus_core); + pa_assert(core); + + m = pa_xnew(pa_dbusiface_memstats, 1); + m->core = pa_core_ref(core); + m->path = pa_sprintf_malloc("%s/%s", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME); + m->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &memstats_interface_info, m) >= 0); + + return m; +} + +void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m) { + pa_assert(m); + + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, memstats_interface_info.name) >= 0); + + pa_xfree(m->path); + + pa_dbus_protocol_unref(m->dbus_protocol); + pa_core_unref(m->core); + + pa_xfree(m); +} + +const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m) { + pa_assert(m); + + return m->path; +} diff --git a/src/modules/dbus/iface-memstats.h b/src/modules/dbus/iface-memstats.h new file mode 100644 index 00000000..0820e8fe --- /dev/null +++ b/src/modules/dbus/iface-memstats.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacememstatshfoo +#define foodbusifacememstatshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Memstats. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Memstats interface + * documentation. + */ + +#include <pulsecore/core.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_MEMSTATS_INTERFACE PA_DBUS_CORE_INTERFACE ".Memstats" + +typedef struct pa_dbusiface_memstats pa_dbusiface_memstats; + +pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core); +void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m); + +const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m); + +#endif diff --git a/src/modules/dbus/iface-module.c b/src/modules/dbus/iface-module.c new file mode 100644 index 00000000..e8aea50f --- /dev/null +++ b/src/modules/dbus/iface-module.c @@ -0,0 +1,336 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-module.h" + +#define OBJECT_NAME "module" + +struct pa_dbusiface_module { + pa_module *module; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_ARGUMENTS, + PROPERTY_HANDLER_USAGE_COUNTER, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_ARGUMENTS] = { .property_name = "Arguments", .type = "a{ss}", .get_cb = handle_get_arguments, .set_cb = NULL }, + [PROPERTY_HANDLER_USAGE_COUNTER] = { .property_name = "UsageCounter", .type = "u", .get_cb = handle_get_usage_counter, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_UNLOAD, + METHOD_HANDLER_MAX +}; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_UNLOAD] = { + .method_name = "Unload", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_unload } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info module_interface_info = { + .name = PA_DBUSIFACE_MODULE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + idx = m->module->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &m->module->name); +} + +static void append_modargs_variant(DBusMessageIter *iter, pa_dbusiface_module *m) { + pa_modargs *ma = NULL; + DBusMessageIter variant_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + void *state = NULL; + const char *key = NULL; + const char *value = NULL; + + pa_assert(iter); + pa_assert(m); + + pa_assert_se(ma = pa_modargs_new(m->module->argument, NULL)); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{ss}", &variant_iter)); + pa_assert_se(dbus_message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "{ss}", &dict_iter)); + + for (state = NULL, key = pa_modargs_iterate(ma, &state); key; key = pa_modargs_iterate(ma, &state)) { + pa_assert_se(value = pa_modargs_get_value(ma, key, NULL)); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &value)); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(&variant_iter, &dict_iter)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); + + pa_modargs_free(ma); +} + +static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(reply = dbus_message_new_method_return(msg)); + dbus_message_iter_init_append(reply, &msg_iter); + append_modargs_variant(&msg_iter, m); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + int real_counter_value = -1; + dbus_uint32_t usage_counter = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + if (!m->module->get_n_used || (real_counter_value = m->module->get_n_used(m->module)) < 0) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Module %u (%s) doesn't have a usage counter.", m->module->index, m->module->name); + return; + } + + usage_counter = real_counter_value; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &usage_counter); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_dbus_send_proplist_variant_reply(conn, msg, m->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + dbus_uint32_t idx = 0; + int real_counter_value = -1; + dbus_uint32_t usage_counter = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + idx = m->module->index; + if (m->module->get_n_used && (real_counter_value = m->module->get_n_used(m->module)) >= 0) + usage_counter = real_counter_value; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &m->module->name); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name)); + append_modargs_variant(&dict_entry_iter, m); + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + + if (real_counter_value >= 0) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name, DBUS_TYPE_UINT32, &usage_counter); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, m->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + if (m->module->core->disallow_module_loading) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module unloading."); + return; + } + + pa_module_unload_request(m->module, FALSE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *signal = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_MODULE); + pa_assert(m); + + /* We can't use idx != m->module->index, because the m->module pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->modules, idx) != m->module) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(m->proplist, m->module->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(m->proplist, PA_UPDATE_SET, m->module->proplist); + + pa_assert_se(signal = dbus_message_new_signal(m->path, + PA_DBUSIFACE_MODULE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, m->proplist); + + pa_dbus_protocol_send_signal(m->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module) { + pa_dbusiface_module *m; + + pa_assert(module); + + m = pa_xnew0(pa_dbusiface_module, 1); + m->module = module; + m->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, module->index); + m->proplist = pa_proplist_copy(module->proplist); + m->dbus_protocol = pa_dbus_protocol_get(module->core); + m->subscription = pa_subscription_new(module->core, PA_SUBSCRIPTION_MASK_MODULE, subscription_cb, m); + + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &module_interface_info, m) >= 0); + + return m; +} + +void pa_dbusiface_module_free(pa_dbusiface_module *m) { + pa_assert(m); + + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, module_interface_info.name) >= 0); + + pa_proplist_free(m->proplist); + pa_dbus_protocol_unref(m->dbus_protocol); + pa_subscription_free(m->subscription); + + pa_xfree(m->path); + pa_xfree(m); +} + +const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m) { + pa_assert(m); + + return m->path; +} diff --git a/src/modules/dbus/iface-module.h b/src/modules/dbus/iface-module.h new file mode 100644 index 00000000..68ca1de5 --- /dev/null +++ b/src/modules/dbus/iface-module.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacemodulehfoo +#define foodbusifacemodulehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Module. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Module interface + * documentation. + */ + +#include <pulsecore/module.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_MODULE_INTERFACE PA_DBUS_CORE_INTERFACE ".Module" + +typedef struct pa_dbusiface_module pa_dbusiface_module; + +pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module); +void pa_dbusiface_module_free(pa_dbusiface_module *m); + +const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m); + +#endif diff --git a/src/modules/dbus/iface-sample.c b/src/modules/dbus/iface-sample.c new file mode 100644 index 00000000..b0542a60 --- /dev/null +++ b/src/modules/dbus/iface-sample.c @@ -0,0 +1,519 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-sample.h" + +#define OBJECT_NAME "sample" + +struct pa_dbusiface_sample { + pa_dbusiface_core *core; + + pa_scache_entry *sample; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_DEFAULT_VOLUME, + PROPERTY_HANDLER_DURATION, + PROPERTY_HANDLER_BYTES, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL }, + [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL }, + [PROPERTY_HANDLER_DEFAULT_VOLUME] = { .property_name = "DefaultVolume", .type = "au", .get_cb = handle_get_default_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_DURATION] = { .property_name = "Duration", .type = "t", .get_cb = handle_get_duration, .set_cb = NULL }, + [PROPERTY_HANDLER_BYTES] = { .property_name = "Bytes", .type = "u", .get_cb = handle_get_bytes, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_PLAY, + METHOD_HANDLER_PLAY_TO_SINK, + METHOD_HANDLER_REMOVE, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info play_args[] = { { "volume", "u", "in" }, { "property_list", "a{say}", "in" } }; +static pa_dbus_arg_info play_to_sink_args[] = { { "sink", "o", "in" }, + { "volume", "u", "in" }, + { "property_list", "a{say}", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_PLAY] = { + .method_name = "Play", + .arguments = play_args, + .n_arguments = sizeof(play_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_play }, + [METHOD_HANDLER_PLAY_TO_SINK] = { + .method_name = "PlayToSink", + .arguments = play_to_sink_args, + .n_arguments = sizeof(play_to_sink_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_play_to_sink }, + [METHOD_HANDLER_REMOVE] = { + .method_name = "Remove", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_remove } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info sample_interface_info = { + .name = PA_DBUSIFACE_SAMPLE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = s->sample->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &s->sample->name); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its sample format is unknown.", s->sample->name); + return; + } + + sample_format = s->sample->sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format); +} + +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t sample_rate = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its sample rate is unknown.", s->sample->name); + return; + } + + sample_rate = s->sample->sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its channel map is unknown.", s->sample->name); + return; + } + + for (i = 0; i < s->sample->channel_map.channels; ++i) + channels[i] = s->sample->channel_map.map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels); +} + +static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t default_volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->volume_is_set) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s doesn't have default volume stored.", s->sample->name); + return; + } + + for (i = 0; i < s->sample->volume.channels; ++i) + default_volume[i] = s->sample->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels); +} + +static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint64_t duration = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its duration is unknown.", s->sample->name); + return; + } + + duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &duration); +} + +static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t bytes = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its size is unknown.", s->sample->name); + return; + } + + bytes = s->sample->memchunk.length; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &bytes); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + dbus_uint32_t sample_format = 0; + dbus_uint32_t sample_rate = 0; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t default_volume[PA_CHANNELS_MAX]; + dbus_uint64_t duration = 0; + dbus_uint32_t bytes = 0; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = s->sample->index; + if (s->sample->memchunk.memblock) { + sample_format = s->sample->sample_spec.format; + sample_rate = s->sample->sample_spec.rate; + for (i = 0; i < s->sample->channel_map.channels; ++i) + channels[i] = s->sample->channel_map.map[i]; + duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec); + bytes = s->sample->memchunk.length; + } + if (s->sample->volume_is_set) { + for (i = 0; i < s->sample->volume.channels; ++i) + default_volume[i] = s->sample->volume.values[i]; + } + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &s->sample->name); + + if (s->sample->memchunk.memblock) { + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels); + } + + if (s->sample->volume_is_set) + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_VOLUME].property_name, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels); + + if (s->sample->memchunk.memblock) { + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DURATION].property_name, DBUS_TYPE_UINT64, &duration); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BYTES].property_name, DBUS_TYPE_UINT32, &bytes); + } + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessageIter msg_iter; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; + pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &volume); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + if (volume > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume."); + goto finish; + } + + if (!(sink = pa_namereg_get_default_sink(s->sample->core))) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Can't play sample %s, because there are no sinks available.", s->sample->name); + goto finish; + } + + if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessageIter msg_iter; + const char *sink_path = NULL; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; + pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sink_path); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &volume); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + if (!(sink = pa_dbusiface_core_get_sink(s->core, sink_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_path); + goto finish; + } + + if (volume > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume."); + goto finish; + } + + if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (pa_scache_remove_item(s->sample->core, s->sample->name) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Removing sample %s failed.", s->sample->name); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessage *signal = NULL; + + pa_assert(c); + pa_assert(s); + + /* We can't use idx != s->sample->index, because the s->sample pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(c->scache, idx) != s->sample) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(s->proplist, s->sample->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(s->proplist, PA_UPDATE_SET, s->sample->proplist); + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_SAMPLE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, s->proplist); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample) { + pa_dbusiface_sample *s = NULL; + + pa_assert(core); + pa_assert(sample); + + s = pa_xnew0(pa_dbusiface_sample, 1); + s->core = core; + s->sample = sample; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, sample->index); + s->proplist = pa_proplist_copy(sample->proplist); + s->dbus_protocol = pa_dbus_protocol_get(sample->core); + s->subscription = pa_subscription_new(sample->core, PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, subscription_cb, s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &sample_interface_info, s) >= 0); + + return s; +} + +void pa_dbusiface_sample_free(pa_dbusiface_sample *s) { + pa_assert(s); + + pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, sample_interface_info.name) >= 0); + + pa_proplist_free(s->proplist); + pa_dbus_protocol_unref(s->dbus_protocol); + pa_subscription_free(s->subscription); + + pa_xfree(s->path); + pa_xfree(s); +} + +const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *s) { + pa_assert(s); + + return s->path; +} diff --git a/src/modules/dbus/iface-sample.h b/src/modules/dbus/iface-sample.h new file mode 100644 index 00000000..f1947ce8 --- /dev/null +++ b/src/modules/dbus/iface-sample.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacesamplehfoo +#define foodbusifacesamplehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Sample. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Sample interface + * documentation. + */ + +#include <pulsecore/core-scache.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_SAMPLE_INTERFACE PA_DBUS_CORE_INTERFACE ".Sample" + +typedef struct pa_dbusiface_sample pa_dbusiface_sample; + +pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample); +void pa_dbusiface_sample_free(pa_dbusiface_sample *c); + +const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *c); + +#endif diff --git a/src/modules/dbus/iface-stream.c b/src/modules/dbus/iface-stream.c new file mode 100644 index 00000000..04a45e6c --- /dev/null +++ b/src/modules/dbus/iface-stream.c @@ -0,0 +1,894 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.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 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-stream.h" + +#define PLAYBACK_OBJECT_NAME "playback_stream" +#define RECORD_OBJECT_NAME "record_stream" + +enum stream_type { + STREAM_TYPE_PLAYBACK, + STREAM_TYPE_RECORD +}; + +struct pa_dbusiface_stream { + pa_dbusiface_core *core; + + union { + pa_sink_input *sink_input; + pa_source_output *source_output; + }; + enum stream_type type; + char *path; + union { + pa_sink *sink; + pa_source *source; + }; + uint32_t sample_rate; + pa_cvolume volume; + dbus_bool_t mute; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; + pa_hook_slot *send_event_slot; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_CLIENT, + PROPERTY_HANDLER_DEVICE, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_VOLUME, + PROPERTY_HANDLER_MUTE, + PROPERTY_HANDLER_BUFFER_LATENCY, + PROPERTY_HANDLER_DEVICE_LATENCY, + PROPERTY_HANDLER_RESAMPLE_METHOD, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_CLIENT] = { .property_name = "Client", .type = "o", .get_cb = handle_get_client, .set_cb = NULL }, + [PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "o", .get_cb = handle_get_device, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL }, + [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume }, + [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute }, + [PROPERTY_HANDLER_BUFFER_LATENCY] = { .property_name = "BufferLatency", .type = "t", .get_cb = handle_get_buffer_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_DEVICE_LATENCY] = { .property_name = "DeviceLatency", .type = "t", .get_cb = handle_get_device_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_RESAMPLE_METHOD] = { .property_name = "ResampleMethod", .type = "s", .get_cb = handle_get_resample_method, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_MOVE, + METHOD_HANDLER_KILL, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info move_args[] = { { "device", "o", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_MOVE] = { + .method_name = "Move", + .arguments = move_args, + .n_arguments = sizeof(move_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_move }, + [METHOD_HANDLER_KILL] = { + .method_name = "Kill", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_kill } +}; + +enum signal_index { + SIGNAL_DEVICE_UPDATED, + SIGNAL_SAMPLE_RATE_UPDATED, + SIGNAL_VOLUME_UPDATED, + SIGNAL_MUTE_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_STREAM_EVENT, + SIGNAL_MAX +}; + +static pa_dbus_arg_info device_updated_args[] = { { "device", "o", NULL } }; +static pa_dbus_arg_info sample_rate_updated_args[] = { { "sample_rate", "u", NULL } }; +static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } }; +static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; +static pa_dbus_arg_info stream_event_args[] = { { "name", "s", NULL }, { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = device_updated_args, .n_arguments = 1 }, + [SIGNAL_SAMPLE_RATE_UPDATED] = { .name = "SampleRateUpdated", .arguments = sample_rate_updated_args, .n_arguments = 1 }, + [SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 }, + [SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }, + [SIGNAL_STREAM_EVENT] = { .name = "StreamEvent", .arguments = stream_event_args, .n_arguments = sizeof(stream_event_args) / sizeof(pa_dbus_arg_info) } +}; + +static pa_dbus_interface_info stream_interface_info = { + .name = PA_DBUSIFACE_STREAM_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint32_t idx; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->index : s->source_output->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *driver = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + driver = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->driver : s->source_output->driver; + + if (!driver) { + if (s->type == STREAM_TYPE_PLAYBACK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Playback stream %u doesn't have a driver.", s->sink_input->index); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Record stream %u doesn't have a driver.", s->source_output->index); + return; + } + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_module *owner_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + owner_module = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->module : s->source_output->module; + + if (!owner_module) { + if (s->type == STREAM_TYPE_PLAYBACK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Playback stream %u doesn't have an owner module.", s->sink_input->index); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Record stream %u doesn't have an owner module.", s->source_output->index); + return; + } + + object_path = pa_dbusiface_core_get_module_path(s->core, owner_module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_client *client = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + client = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->client : s->source_output->client; + + if (!client) { + if (s->type == STREAM_TYPE_PLAYBACK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Playback stream %u isn't associated to any client.", s->sink_input->index); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Record stream %u isn't associated to any client.", s->source_output->index); + return; + } + + object_path = pa_dbusiface_core_get_client_path(s->core, client); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *device = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + device = pa_dbusiface_core_get_sink_path(s->core, s->sink); + else + device = pa_dbusiface_core_get_source_path(s->core, s->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &device); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + sample_format = (s->type == STREAM_TYPE_PLAYBACK) + ? s->sink_input->sample_spec.format + : s->source_output->sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format); +} + +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &s->sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + channel_map = (s->type == STREAM_TYPE_PLAYBACK) ? &s->sink_input->channel_map : &s->source_output->channel_map; + + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels); +} + +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume."); + return; + } + + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, s->volume.channels); +} + +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_stream *s = userdata; + DBusMessageIter array_iter; + int stream_channels = 0; + dbus_uint32_t *volume = NULL; + int n_volume_entries = 0; + pa_cvolume new_vol; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(s); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume."); + return; + } + + pa_cvolume_init(&new_vol); + + stream_channels = s->sink_input->channel_map.channels; + + new_vol.channels = stream_channels; + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries); + + if (n_volume_entries != stream_channels) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Expected %u volume entries, got %u.", stream_channels, n_volume_entries); + return; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (volume[i] > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]); + return; + } + new_vol.values[i] = volume[i]; + } + + pa_sink_input_set_volume(s->sink_input, &new_vol, TRUE, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute."); + return; + } + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &s->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_bool_t mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(s); + + dbus_message_iter_get_basic(iter, &mute); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute."); + return; + } + + pa_sink_input_set_mute(s->sink_input, mute, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +}; + +static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint64_t buffer_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + buffer_latency = pa_sink_input_get_latency(s->sink_input, NULL); + else + buffer_latency = pa_source_output_get_latency(s->source_output, NULL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &buffer_latency); +} + +static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint64_t device_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + pa_sink_input_get_latency(s->sink_input, &device_latency); + else + pa_source_output_get_latency(s->source_output, &device_latency); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &device_latency); +} + +static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *resample_method = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method); + else + resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &resample_method); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *driver = NULL; + pa_module *owner_module = NULL; + const char *owner_module_path = NULL; + pa_client *client = NULL; + const char *client_path = NULL; + const char *device = NULL; + dbus_uint32_t sample_format = 0; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint64_t buffer_latency = 0; + dbus_uint64_t device_latency = 0; + const char *resample_method = NULL; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) { + idx = s->sink_input->index; + driver = s->sink_input->driver; + owner_module = s->sink_input->module; + client = s->sink_input->client; + device = pa_dbusiface_core_get_sink_path(s->core, s->sink); + sample_format = s->sink_input->sample_spec.format; + channel_map = &s->sink_input->channel_map; + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + buffer_latency = pa_sink_input_get_latency(s->sink_input, &device_latency); + resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method); + } else { + idx = s->source_output->index; + driver = s->source_output->driver; + owner_module = s->source_output->module; + client = s->source_output->client; + device = pa_dbusiface_core_get_source_path(s->core, s->source); + sample_format = s->source_output->sample_spec.format; + channel_map = &s->source_output->channel_map; + buffer_latency = pa_source_output_get_latency(s->source_output, &device_latency); + resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method); + } + if (owner_module) + owner_module_path = pa_dbusiface_core_get_module_path(s->core, owner_module); + if (client) + client_path = pa_dbusiface_core_get_client_path(s->core, client); + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + + if (driver) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path); + + if (client) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &client_path); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &s->sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, s->volume.channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &s->mute); + } + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BUFFER_LATENCY].property_name, DBUS_TYPE_UINT64, &buffer_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE_LATENCY].property_name, DBUS_TYPE_UINT64, &device_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RESAMPLE_METHOD].property_name, DBUS_TYPE_STRING, &resample_method); + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *device = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID)); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink *sink = pa_dbusiface_core_get_sink(s->core, device); + + if (!sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", device); + return; + } + + if (pa_sink_input_move_to(s->sink_input, sink, TRUE) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Moving playback stream %u to sink %s failed.", s->sink_input->index, sink->name); + return; + } + } else { + pa_source *source = pa_dbusiface_core_get_source(s->core, device); + + if (!source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", device); + return; + } + + if (pa_source_output_move_to(s->source_output, source, TRUE) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Moving record stream %u to source %s failed.", s->source_output->index, source->name); + return; + } + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + pa_sink_input_kill(s->sink_input); + else + pa_source_output_kill(s->source_output); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_stream *s = userdata; + DBusMessage *signal = NULL; + const char *new_device_path = NULL; + uint32_t new_sample_rate = 0; + pa_proplist *new_proplist = NULL; + unsigned i = 0; + + pa_assert(c); + pa_assert(s); + + if ((s->type == STREAM_TYPE_PLAYBACK && idx != s->sink_input->index) + || (s->type == STREAM_TYPE_RECORD && idx != s->source_output->index)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + pa_assert(((s->type == STREAM_TYPE_PLAYBACK) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT)) + || ((s->type == STREAM_TYPE_RECORD) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT))); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink *new_sink = s->sink_input->sink; + + if (s->sink != new_sink) { + pa_sink_unref(s->sink); + s->sink = pa_sink_ref(new_sink); + + new_device_path = pa_dbusiface_core_get_sink_path(s->core, new_sink); + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } else { + pa_source *new_source = s->source_output->source; + + if (s->source != new_source) { + pa_source_unref(s->source); + s->source = pa_source_ref(new_source); + + new_device_path = pa_dbusiface_core_get_source_path(s->core, new_source); + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + + new_sample_rate = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->sample_spec.rate : s->source_output->sample_spec.rate; + + if (s->sample_rate != new_sample_rate) { + s->sample_rate = new_sample_rate; + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_SAMPLE_RATE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &s->sample_rate, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_cvolume new_volume; + pa_bool_t new_mute = FALSE; + + pa_sink_input_get_volume(s->sink_input, &new_volume, TRUE); + + if (!pa_cvolume_equal(&s->volume, &new_volume)) { + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint32_t *volume_ptr = volume; + + s->volume = new_volume; + + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_VOLUME_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, s->volume.channels, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_mute = pa_sink_input_get_mute(s->sink_input); + + if (s->mute != new_mute) { + s->mute = new_mute; + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &s->mute, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + + new_proplist = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->proplist : s->source_output->proplist; + + if (!pa_proplist_equal(s->proplist, new_proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(s->proplist, PA_UPDATE_SET, new_proplist); + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, s->proplist); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +static pa_hook_result_t send_event_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_stream *s = slot_data; + DBusMessage *signal = NULL; + DBusMessageIter msg_iter; + const char *name = NULL; + pa_proplist *property_list = NULL; + + pa_assert(call_data); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink_input_send_event_hook_data *data = call_data; + + if (data->sink_input != s->sink_input) + return PA_HOOK_OK; + + name = data->event; + property_list = data->data; + } else { + pa_source_output_send_event_hook_data *data = call_data; + + if (data->source_output != s->source_output) + return PA_HOOK_OK; + + name = data->event; + property_list = data->data; + } + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_STREAM_EVENT].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name)); + pa_dbus_append_proplist(&msg_iter, property_list); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + + return PA_HOOK_OK; +} + +pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input) { + pa_dbusiface_stream *s; + + pa_assert(core); + pa_assert(sink_input); + + s = pa_xnew(pa_dbusiface_stream, 1); + s->core = core; + s->sink_input = pa_sink_input_ref(sink_input); + s->type = STREAM_TYPE_PLAYBACK; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, PLAYBACK_OBJECT_NAME, sink_input->index); + s->sink = pa_sink_ref(sink_input->sink); + s->sample_rate = sink_input->sample_spec.rate; + pa_sink_input_get_volume(sink_input, &s->volume, TRUE); + s->mute = pa_sink_input_get_mute(sink_input); + s->proplist = pa_proplist_copy(sink_input->proplist); + s->dbus_protocol = pa_dbus_protocol_get(sink_input->core); + s->subscription = pa_subscription_new(sink_input->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscription_cb, s); + s->send_event_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT], + PA_HOOK_NORMAL, + send_event_cb, + s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0); + + return s; +} + +pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output) { + pa_dbusiface_stream *s; + + pa_assert(core); + pa_assert(source_output); + + s = pa_xnew(pa_dbusiface_stream, 1); + s->core = core; + s->source_output = pa_source_output_ref(source_output); + s->type = STREAM_TYPE_RECORD; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, RECORD_OBJECT_NAME, source_output->index); + s->source = pa_source_ref(source_output->source); + s->sample_rate = source_output->sample_spec.rate; + pa_cvolume_init(&s->volume); + s->mute = FALSE; + s->proplist = pa_proplist_copy(source_output->proplist); + s->dbus_protocol = pa_dbus_protocol_get(source_output->core); + s->subscription = pa_subscription_new(source_output->core, PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscription_cb, s); + s->send_event_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT], + PA_HOOK_NORMAL, + send_event_cb, + s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0); + + return s; +} + +void pa_dbusiface_stream_free(pa_dbusiface_stream *s) { + pa_assert(s); + + pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, stream_interface_info.name) >= 0); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink_input_unref(s->sink_input); + pa_sink_unref(s->sink); + } else { + pa_source_output_unref(s->source_output); + pa_source_unref(s->source); + } + + pa_proplist_free(s->proplist); + pa_dbus_protocol_unref(s->dbus_protocol); + pa_subscription_free(s->subscription); + pa_hook_slot_free(s->send_event_slot); + + pa_xfree(s->path); + pa_xfree(s); +} + +const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s) { + pa_assert(s); + + return s->path; +} diff --git a/src/modules/dbus/iface-stream.h b/src/modules/dbus/iface-stream.h new file mode 100644 index 00000000..036b4e7e --- /dev/null +++ b/src/modules/dbus/iface-stream.h @@ -0,0 +1,47 @@ +#ifndef foodbusifacestreamhfoo +#define foodbusifacestreamhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Stream. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Stream interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_STREAM_INTERFACE PA_DBUS_CORE_INTERFACE ".Stream" + +typedef struct pa_dbusiface_stream pa_dbusiface_stream; + +pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input); +pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output); +void pa_dbusiface_stream_free(pa_dbusiface_stream *s); + +const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s); + +#endif diff --git a/src/modules/dbus/module-dbus-protocol.c b/src/modules/dbus/module-dbus-protocol.c new file mode 100644 index 00000000..11064c33 --- /dev/null +++ b/src/modules/dbus/module-dbus-protocol.c @@ -0,0 +1,607 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/mainloop-api.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/client.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/idxset.h> +#include <pulsecore/macro.h> +#include <pulsecore/modargs.h> +#include <pulsecore/module.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-client.h" +#include "iface-core.h" + +#include "module-dbus-protocol-symdef.h" + +PA_MODULE_DESCRIPTION("D-Bus interface"); +PA_MODULE_USAGE( + "access=local|remote|local,remote " + "tcp_port=<port number>"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_AUTHOR("Tanu Kaskinen"); +PA_MODULE_VERSION(PACKAGE_VERSION); + +#define CLEANUP_INTERVAL 10 /* seconds */ + +enum server_type { + SERVER_TYPE_LOCAL, + SERVER_TYPE_TCP +}; + +struct server; +struct connection; + +struct userdata { + pa_module *module; + pa_bool_t local_access; + pa_bool_t remote_access; + uint32_t tcp_port; + + struct server *local_server; + struct server *tcp_server; + + pa_idxset *connections; + + pa_time_event *cleanup_event; + + pa_dbus_protocol *dbus_protocol; + pa_dbusiface_core *core_iface; +}; + +struct server { + struct userdata *userdata; + enum server_type type; + DBusServer *dbus_server; +}; + +struct connection { + struct server *server; + pa_dbus_wrap_connection *wrap_conn; + pa_client *client; +}; + +static const char* const valid_modargs[] = { + "access", + "tcp_port", + NULL +}; + +static void connection_free(struct connection *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_unregister_connection(c->server->userdata->dbus_protocol, pa_dbus_wrap_connection_get(c->wrap_conn)) >= 0); + + pa_client_free(c->client); + pa_assert_se(pa_idxset_remove_by_data(c->server->userdata->connections, c, NULL)); + pa_dbus_wrap_connection_free(c->wrap_conn); + pa_xfree(c); +} + +/* Called from pa_client_kill(). */ +static void client_kill_cb(pa_client *c) { + struct connection *conn; + + pa_assert(c); + pa_assert(c->userdata); + + conn = c->userdata; + connection_free(conn); + c->userdata = NULL; + + pa_log_info("Connection killed."); +} + +/* Called from pa_client_send_event(). */ +static void client_send_event_cb(pa_client *c, const char *name, pa_proplist *data) { + struct connection *conn = NULL; + DBusMessage *signal = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(c->userdata); + + conn = c->userdata; + + pa_assert_se(signal = dbus_message_new_signal(pa_dbusiface_core_get_client_path(conn->server->userdata->core_iface, c), + PA_DBUSIFACE_CLIENT_INTERFACE, + "ClientEvent")); + dbus_message_iter_init_append(signal, &msg_iter); + pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name)); + pa_dbus_append_proplist(&msg_iter, data); + + pa_assert_se(dbus_connection_send(pa_dbus_wrap_connection_get(conn->wrap_conn), signal, NULL)); + dbus_message_unref(signal); +} + +/* Called by D-Bus at the authentication phase. */ +static dbus_bool_t user_check_cb(DBusConnection *connection, unsigned long uid, void *data) { + pa_log_debug("Allowing connection by user %lu.", uid); + + return TRUE; +} + +/* Called by D-Bus when a new client connection is received. */ +static void connection_new_cb(DBusServer *dbus_server, DBusConnection *new_connection, void *data) { + struct server *s = data; + struct connection *c; + pa_client_new_data new_data; + pa_client *client; + + pa_assert(new_connection); + pa_assert(s); + + pa_client_new_data_init(&new_data); + new_data.module = s->userdata->module; + new_data.driver = __FILE__; + pa_proplist_sets(new_data.proplist, PA_PROP_APPLICATION_NAME, "D-Bus client"); + client = pa_client_new(s->userdata->module->core, &new_data); + pa_client_new_data_done(&new_data); + + if (!client) { + dbus_connection_close(new_connection); + return; + } + + if (s->type == SERVER_TYPE_TCP || s->userdata->module->core->server_type == PA_SERVER_TYPE_SYSTEM) { + /* FIXME: Here we allow anyone from anywhere to access the server, + * anonymously. Access control should be configurable. */ + dbus_connection_set_unix_user_function(new_connection, user_check_cb, NULL, NULL); + dbus_connection_set_allow_anonymous(new_connection, TRUE); + } + + c = pa_xnew(struct connection, 1); + c->server = s; + c->wrap_conn = pa_dbus_wrap_connection_new_from_existing(s->userdata->module->core->mainloop, TRUE, new_connection); + c->client = client; + + c->client->kill = client_kill_cb; + c->client->send_event = client_send_event_cb; + c->client->userdata = c; + + pa_idxset_put(s->userdata->connections, c, NULL); + + pa_assert_se(pa_dbus_protocol_register_connection(s->userdata->dbus_protocol, new_connection, c->client) >= 0); +} + +/* Called by PA mainloop when a D-Bus fd watch event needs handling. */ +static void io_event_cb(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + unsigned int flags = 0; + DBusWatch *watch = userdata; + +#if HAVE_DBUS_WATCH_GET_UNIX_FD + pa_assert(fd == dbus_watch_get_unix_fd(watch)); +#else + pa_assert(fd == dbus_watch_get_fd(watch)); +#endif + + if (!dbus_watch_get_enabled(watch)) { + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + + if (events & PA_IO_EVENT_INPUT) + flags |= DBUS_WATCH_READABLE; + if (events & PA_IO_EVENT_OUTPUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & PA_IO_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (events & PA_IO_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); +} + +/* Called by PA mainloop when a D-Bus timer event needs handling. */ +static void time_event_cb(pa_mainloop_api *mainloop, pa_time_event* e, const struct timeval *tv, void *userdata) { + DBusTimeout *timeout = userdata; + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval next = *tv; + dbus_timeout_handle(timeout); + + /* restart it for the next scheduled time */ + pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + mainloop->time_restart(e, &next); + } +} + +/* Translates D-Bus fd watch event flags to PA IO event flags. */ +static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { + unsigned int flags; + pa_io_event_flags_t events = 0; + + pa_assert(watch); + + flags = dbus_watch_get_flags(watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return PA_IO_EVENT_NULL; + + if (flags & DBUS_WATCH_READABLE) + events |= PA_IO_EVENT_INPUT; + if (flags & DBUS_WATCH_WRITABLE) + events |= PA_IO_EVENT_OUTPUT; + + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +} + +/* Called by D-Bus when a D-Bus fd watch event is added. */ +static dbus_bool_t watch_add_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + mainloop = s->userdata->module->core->mainloop; + + ev = mainloop->io_new( + mainloop, +#if HAVE_DBUS_WATCH_GET_UNIX_FD + dbus_watch_get_unix_fd(watch), +#else + dbus_watch_get_fd(watch), +#endif + get_watch_flags(watch), io_event_cb, watch); + + dbus_watch_set_data(watch, ev, NULL); + + return TRUE; +} + +/* Called by D-Bus when a D-Bus fd watch event is removed. */ +static void watch_remove_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + if ((ev = dbus_watch_get_data(watch))) + s->userdata->module->core->mainloop->io_free(ev); +} + +/* Called by D-Bus when a D-Bus fd watch event is toggled. */ +static void watch_toggled_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + pa_assert_se(ev = dbus_watch_get_data(watch)); + + /* get_watch_flags() checks if the watch is enabled */ + s->userdata->module->core->mainloop->io_enable(ev, get_watch_flags(watch)); +} + +/* Called by D-Bus when a D-Bus timer event is added. */ +static dbus_bool_t timeout_add_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_time_event *ev; + struct timeval tv; + + pa_assert(timeout); + pa_assert(s); + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + mainloop = s->userdata->module->core->mainloop; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + ev = mainloop->time_new(mainloop, &tv, time_event_cb, timeout); + + dbus_timeout_set_data(timeout, ev, NULL); + + return TRUE; +} + +/* Called by D-Bus when a D-Bus timer event is removed. */ +static void timeout_remove_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(s); + + if ((ev = dbus_timeout_get_data(timeout))) + s->userdata->module->core->mainloop->time_free(ev); +} + +/* Called by D-Bus when a D-Bus timer event is toggled. */ +static void timeout_toggled_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(s); + + mainloop = s->userdata->module->core->mainloop; + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval tv; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + mainloop->time_restart(ev, &tv); + } else + mainloop->time_restart(ev, NULL); +} + +static void server_free(struct server *s) { + pa_assert(s); + + if (s->dbus_server) { + dbus_server_disconnect(s->dbus_server); + dbus_server_unref(s->dbus_server); + } + + pa_xfree(s); +} + +static struct server *start_server(struct userdata *u, const char *address, enum server_type type) { + /* XXX: We assume that when we unref the DBusServer instance at module + * shutdown, nobody else holds any references to it. If we stop assuming + * that someday, dbus_server_set_new_connection_function, + * dbus_server_set_watch_functions and dbus_server_set_timeout_functions + * calls should probably register free callbacks, instead of providing NULL + * as they do now. */ + + struct server *s = NULL; + DBusError error; + + pa_assert(u); + pa_assert(address); + + dbus_error_init(&error); + + s = pa_xnew0(struct server, 1); + s->userdata = u; + s->dbus_server = dbus_server_listen(address, &error); + + if (dbus_error_is_set(&error)) { + pa_log("dbus_server_listen() failed: %s: %s", error.name, error.message); + goto fail; + } + + dbus_server_set_new_connection_function(s->dbus_server, connection_new_cb, s, NULL); + + if (!dbus_server_set_watch_functions(s->dbus_server, watch_add_cb, watch_remove_cb, watch_toggled_cb, s, NULL)) { + pa_log("dbus_server_set_watch_functions() ran out of memory."); + goto fail; + } + + if (!dbus_server_set_timeout_functions(s->dbus_server, timeout_add_cb, timeout_remove_cb, timeout_toggled_cb, s, NULL)) { + pa_log("dbus_server_set_timeout_functions() ran out of memory."); + goto fail; + } + + return s; + +fail: + if (s) + server_free(s); + + dbus_error_free(&error); + + return NULL; +} + +static struct server *start_local_server(struct userdata *u) { + struct server *s = NULL; + char *address = NULL; + + pa_assert(u); + + address = pa_get_dbus_address_from_server_type(u->module->core->server_type); + + s = start_server(u, address, SERVER_TYPE_LOCAL); /* May return NULL */ + + pa_xfree(address); + + return s; +} + +static struct server *start_tcp_server(struct userdata *u) { + struct server *s = NULL; + char *address = NULL; + + pa_assert(u); + + address = pa_sprintf_malloc("tcp:host=127.0.0.1,port=%u", u->tcp_port); + + s = start_server(u, address, SERVER_TYPE_TCP); /* May return NULL */ + + pa_xfree(address); + + return s; +} + +static int get_access_arg(pa_modargs *ma, pa_bool_t *local_access, pa_bool_t *remote_access) { + const char *value = NULL; + + pa_assert(ma); + pa_assert(local_access); + pa_assert(remote_access); + + if (!(value = pa_modargs_get_value(ma, "access", NULL))) + return 0; + + if (!strcmp(value, "local")) { + *local_access = TRUE; + *remote_access = FALSE; + } else if (!strcmp(value, "remote")) { + *local_access = FALSE; + *remote_access = TRUE; + } else if (!strcmp(value, "local,remote")) { + *local_access = TRUE; + *remote_access = TRUE; + } else + return -1; + + return 0; +} + +/* Frees dead client connections. Called every CLEANUP_INTERVAL seconds. */ +static void cleanup_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct connection *conn = NULL; + uint32_t idx; + struct timeval cleanup_timeval; + unsigned free_count = 0; + + for (conn = pa_idxset_first(u->connections, &idx); conn; conn = pa_idxset_next(u->connections, &idx)) { + if (!dbus_connection_get_is_connected(pa_dbus_wrap_connection_get(conn->wrap_conn))) { + connection_free(conn); + ++free_count; + } + } + + if (free_count > 0) + pa_log_debug("Freed %u dead D-Bus client connections.", free_count); + + pa_gettimeofday(&cleanup_timeval); + cleanup_timeval.tv_sec += CLEANUP_INTERVAL; + u->module->core->mainloop->time_restart(e, &cleanup_timeval); +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + pa_modargs *ma = NULL; + struct timeval cleanup_timeval; + + 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->module = m; + u->local_access = TRUE; + u->remote_access = FALSE; + u->tcp_port = PA_DBUS_DEFAULT_PORT; + + if (get_access_arg(ma, &u->local_access, &u->remote_access) < 0) { + pa_log("Invalid access argument: '%s'", pa_modargs_get_value(ma, "access", NULL)); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "tcp_port", &u->tcp_port) < 0 || u->tcp_port < 1 || u->tcp_port > 49150) { + pa_log("Invalid tcp_port argument: '%s'", pa_modargs_get_value(ma, "tcp_port", NULL)); + goto fail; + } + + if (u->local_access && !(u->local_server = start_local_server(u))) { + pa_log("Starting the local D-Bus server failed."); + goto fail; + } + + if (u->remote_access && !(u->tcp_server = start_tcp_server(u))) { + pa_log("Starting the D-Bus server for remote connections failed."); + goto fail; + } + + u->connections = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + pa_gettimeofday(&cleanup_timeval); + cleanup_timeval.tv_sec += CLEANUP_INTERVAL; + u->cleanup_event = m->core->mainloop->time_new(m->core->mainloop, &cleanup_timeval, cleanup_cb, u); + + u->dbus_protocol = pa_dbus_protocol_get(m->core); + u->core_iface = pa_dbusiface_core_new(m->core); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +/* Called by idxset when the connection set is freed. */ +static void connection_free_cb(void *p, void *userdata) { + struct connection *conn = p; + + pa_assert(conn); + + connection_free(conn); +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->core_iface) + pa_dbusiface_core_free(u->core_iface); + + if (u->cleanup_event) + m->core->mainloop->time_free(u->cleanup_event); + + if (u->connections) + pa_idxset_free(u->connections, connection_free_cb, NULL); + + if (u->tcp_server) + server_free(u->tcp_server); + + if (u->local_server) + server_free(u->local_server); + + if (u->dbus_protocol) + pa_dbus_protocol_unref(u->dbus_protocol); + + pa_xfree(u); + m->userdata = NULL; +} diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index c01ebbf6..5f31d688 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -52,9 +52,6 @@ PA_MODULE_LOAD_ONCE(TRUE); #define MAX_MODULES 10 #define BUF_MAX 2048 -/* #undef PA_GCONF_HELPER */ -/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */ - struct module_item { char *name; char *args; @@ -343,7 +340,11 @@ int pa__init(pa_module*m) { u->io_event = NULL; u->buf_fill = 0; - if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0) + if ((u->fd = pa_start_child_for_read( +#if defined(__linux__) && !defined(__OPTIMIZE__) + pa_run_from_build_tree() ? PA_BUILDDIR "/gconf-helper" : +#endif + PA_GCONF_HELPER, NULL, &u->pid)) < 0) goto fail; u->io_event = m->core->mainloop->io_new( diff --git a/src/modules/hal-util.c b/src/modules/hal-util.c index e2a2d8d7..2d59f51d 100644 --- a/src/modules/hal-util.c +++ b/src/modules/hal-util.c @@ -65,7 +65,7 @@ int pa_hal_get_info(pa_core *core, pa_proplist *p, int card) { goto finish; } - if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error)) < 0) { + if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error))) { pa_log_error("Couldn't find devices: %s: %s", error.name, error.message); goto finish; } diff --git a/src/modules/jack/module-jack-sink.c b/src/modules/jack/module-jack-sink.c index fc976fa7..9f3e071f 100644 --- a/src/modules/jack/module-jack-sink.c +++ b/src/modules/jack/module-jack-sink.c @@ -334,7 +334,7 @@ int pa__init(pa_module*m) { goto fail; } - ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); + ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput); channels = 0; for (p = ports; *p; p++) diff --git a/src/modules/jack/module-jack-source.c b/src/modules/jack/module-jack-source.c index a898e0e5..6c68527b 100644 --- a/src/modules/jack/module-jack-source.c +++ b/src/modules/jack/module-jack-source.c @@ -286,7 +286,7 @@ int pa__init(pa_module*m) { goto fail; } - ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); + ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsOutput); channels = 0; for (p = ports; *p; p++) diff --git a/src/modules/module-always-sink.c b/src/modules/module-always-sink.c index aee1c650..3d7de9c6 100644 --- a/src/modules/module-always-sink.c +++ b/src/modules/module-always-sink.c @@ -24,6 +24,7 @@ #endif #include <pulse/xmalloc.h> +#include <pulse/i18n.h> #include <pulsecore/core.h> #include <pulsecore/sink-input.h> @@ -35,7 +36,7 @@ #include "module-always-sink-symdef.h" PA_MODULE_AUTHOR("Colin Guthrie"); -PA_MODULE_DESCRIPTION("Always keeps at least one sink loaded even if it's a null one"); +PA_MODULE_DESCRIPTION(_("Always keeps at least one sink loaded even if it's a null one")); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( @@ -78,7 +79,8 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata* u->ignore = TRUE; - t = pa_sprintf_malloc("sink_name=%s", u->sink_name); + t = pa_sprintf_malloc("sink_name=%s sink_properties='device.description=\"%s\"'", u->sink_name, + _("Dummy Output")); m = pa_module_load(c, "module-null-sink", t); u->null_module = m ? m->index : PA_INVALID_INDEX; pa_xfree(t); diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c index fd9452b4..c5ff4564 100644 --- a/src/modules/module-cli.c +++ b/src/modules/module-cli.c @@ -25,6 +25,8 @@ #include <stdio.h> #include <unistd.h> +#include <fcntl.h> +#include <errno.h> #include <pulsecore/module.h> #include <pulsecore/iochannel.h> @@ -33,6 +35,8 @@ #include <pulsecore/log.h> #include <pulsecore/modargs.h> #include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> #include "module-cli-symdef.h" @@ -69,6 +73,7 @@ int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; pa_bool_t exit_on_eof = FALSE; + int fd; pa_assert(m); @@ -88,15 +93,28 @@ int pa__init(pa_module*m) { } if (pa_stdio_acquire() < 0) { - pa_log("STDIN/STDUSE already in use."); + pa_log("STDIN/STDOUT already in use."); goto fail; } - io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); - pa_iochannel_set_noclose(io, 1); + /* We try to open the controlling tty anew here. This has the + * benefit of giving us a new fd that doesn't share the O_NDELAY + * flag with fds 0, 1, or 2. Since pa_iochannel_xxx needs O_NDELAY + * on its fd using those fds directly could set O_NDELAY which + * fprintf() doesn't really like, resulting in truncated output + * of log messages, particularly because if stdout and stderr are + * dup'ed they share the same O_NDELAY, too. */ + + if ((fd = pa_open_cloexec("/dev/tty", O_RDWR|O_NONBLOCK, 0)) >= 0) { + io = pa_iochannel_new(m->core->mainloop, fd, fd); + pa_log_debug("Managed to open /dev/tty."); + } else { + io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); + pa_iochannel_set_noclose(io, TRUE); + pa_log_debug("Failed to open /dev/tty, using stdin/stdout fds instead."); + } m->userdata = pa_cli_new(m->core, io, m); - pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m); pa_modargs_free(ma); @@ -114,7 +132,7 @@ fail: void pa__done(pa_module*m) { pa_assert(m); - if (m->core->running_as_daemon == 0) { + if (m->userdata) { pa_cli_free(m->userdata); pa_stdio_release(); } diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 16de6890..a186c899 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -58,7 +58,7 @@ PA_MODULE_USAGE( "sink_name=<name for the sink> " "sink_properties=<properties for the sink> " "slaves=<slave sinks> " - "adjust_time=<seconds> " + "adjust_time=<how often to readjust rates in s> " "resample_method=<method> " "format=<sample format> " "rate=<sample rate> " @@ -69,7 +69,7 @@ PA_MODULE_USAGE( #define MEMBLOCKQ_MAXLENGTH (1024*1024*16) -#define DEFAULT_ADJUST_TIME 10 +#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC) #define BLOCK_USEC (PA_USEC_PER_MSEC * 200) @@ -91,6 +91,7 @@ struct output { pa_sink *sink; pa_sink_input *sink_input; + pa_bool_t ignore_state_change; pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */ *outq; /* Message queue from this sink input to the sink thread */ @@ -99,9 +100,12 @@ struct output { pa_memblockq *memblockq; + /* For communication of the stream latencies to the main thread */ pa_usec_t total_latency; + /* For coomunication of the stream parameters to the sink thread */ pa_atomic_t max_request; + pa_atomic_t requested_latency; PA_LLIST_FIELDS(struct output); }; @@ -116,7 +120,7 @@ struct userdata { pa_rtpoll *rtpoll; pa_time_event *time_event; - uint32_t adjust_time; + pa_usec_t adjust_time; pa_bool_t automatic; pa_bool_t auto_desc; @@ -125,8 +129,6 @@ struct userdata { pa_resample_method_t resample_method; - struct timeval adjust_timestamp; - pa_usec_t block_usec; pa_idxset* outputs; /* managed in main context */ @@ -146,13 +148,16 @@ enum { SINK_MESSAGE_REMOVE_OUTPUT, SINK_MESSAGE_NEED, SINK_MESSAGE_UPDATE_LATENCY, - SINK_MESSAGE_UPDATE_MAX_REQUEST + SINK_MESSAGE_UPDATE_MAX_REQUEST, + SINK_MESSAGE_UPDATE_REQUESTED_LATENCY }; enum { SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, }; +static void output_disable(struct output *o); +static void output_enable(struct output *o); static void output_free(struct output *o); static int output_create_sink_input(struct output *o); @@ -172,7 +177,7 @@ static void adjust_rates(struct userdata *u) { if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))) return; - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + PA_IDXSET_FOREACH(o, u->outputs, idx) { pa_usec_t sink_latency; if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) @@ -189,6 +194,11 @@ static void adjust_rates(struct userdata *u) { avg_total_latency += o->total_latency; n++; + + pa_log_debug("[%s] total=%0.2fms sink=%0.2fms ", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC, (double) sink_latency / PA_USEC_PER_MSEC); + + if (o->total_latency > 10*PA_USEC_PER_SEC) + pa_log_warn("[%s] Total latency of output is very high (%0.2fms), most likely the audio timing in one of your drivers is broken.", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC); } if (min_total_latency == (pa_usec_t) -1) @@ -203,22 +213,22 @@ static void adjust_rates(struct userdata *u) { base_rate = u->sink->sample_spec.rate; - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + PA_IDXSET_FOREACH(o, u->outputs, idx) { uint32_t r = base_rate; if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) continue; if (o->total_latency < target_latency) - r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC); + r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r); else if (o->total_latency > target_latency) - r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC); + r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r); if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) { - pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r); + pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->sink->name, base_rate, r); pa_sink_input_set_rate(o->sink_input, base_rate); } else { - pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency); + pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->sink->name, r, (double) r / base_rate, (float) o->total_latency); pa_sink_input_set_rate(o->sink_input, r); } } @@ -235,7 +245,7 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim adjust_rates(u); - pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC); + pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time); } static void process_render_null(struct userdata *u, pa_usec_t now) { @@ -355,18 +365,15 @@ static void render_memblock(struct userdata *u, struct output *o, size_t length) u->thread_info.counter += chunk.length; /* OK, let's send this data to the other threads */ - for (j = u->thread_info.active_outputs; j; j = j->next) - - /* Send to other outputs, which are not the requesting - * one */ + PA_LLIST_FOREACH(j, u->thread_info.active_outputs) { + if (j == o) + continue; - if (j != o) - pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL); + pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL); + } /* And place it directly into the requesting output's queue */ - if (o) - pa_memblockq_push_align(o->memblockq, &chunk); - + pa_memblockq_push_align(o->memblockq, &chunk); pa_memblock_unref(chunk.memblock); } } @@ -402,10 +409,18 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk /* If necessary, get some new data */ request_memblock(o, nbytes); + /* pa_log("%s q size is %u + %u (%u/%u)", */ + /* i->sink->name, */ + /* pa_memblockq_get_nblocks(o->memblockq), */ + /* pa_memblockq_get_nblocks(i->thread_info.render_memblockq), */ + /* pa_memblockq_get_maxrewind(o->memblockq), */ + /* pa_memblockq_get_maxrewind(i->thread_info.render_memblockq)); */ + if (pa_memblockq_peek(o->memblockq, chunk) < 0) return -1; pa_memblockq_drop(o->memblockq, chunk->length); + return 0; } @@ -440,13 +455,35 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { return; pa_atomic_store(&o->max_request, (int) nbytes); - pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); } +/* Called from thread context */ +static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { + struct output *o; + pa_usec_t c; + + pa_assert(i); + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + c = pa_sink_get_requested_latency_within_thread(i->sink); + + if (c == (pa_usec_t) -1) + c = i->sink->thread_info.max_latency; + + if (pa_atomic_load(&o->requested_latency) == (int) c) + return; + + pa_atomic_store(&o->requested_latency, (int) c); + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +} + /* Called from I/O thread context */ static void sink_input_attach_cb(pa_sink_input *i) { struct output *o; + pa_usec_t c; pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); @@ -455,14 +492,24 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write); o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( - i->sink->rtpoll, + i->sink->thread_info.rtpoll, PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ o->inq); o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( - i->sink->rtpoll, + i->sink->thread_info.rtpoll, PA_RTPOLL_EARLY, o->outq); + + pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); + + pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i)); + + c = pa_sink_get_requested_latency_within_thread(i->sink); + pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c)); + + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); } /* Called from I/O thread context */ @@ -472,14 +519,15 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); - /* Shut down the queue from the sink thread to us */ - pa_assert(o->inq_rtpoll_item_read && o->outq_rtpoll_item_write); - - pa_rtpoll_item_free(o->inq_rtpoll_item_read); - o->inq_rtpoll_item_read = NULL; + if (o->inq_rtpoll_item_read) { + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + o->inq_rtpoll_item_read = NULL; + } - pa_rtpoll_item_free(o->outq_rtpoll_item_write); - o->outq_rtpoll_item_write = NULL; + if (o->outq_rtpoll_item_write) { + pa_rtpoll_item_free(o->outq_rtpoll_item_write); + o->outq_rtpoll_item_write = NULL; + } } /* Called from main context */ @@ -493,20 +541,6 @@ static void sink_input_kill_cb(pa_sink_input *i) { output_free(o); } -/* Called from IO thread context */ -static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* If we are added for the first time, ask for a rewinding so that - * we are heard right-away. */ - if (PA_SINK_INPUT_IS_LINKED(state) && - i->thread_info.state == PA_SINK_INPUT_INIT) - pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); -} - /* Called from thread context */ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct output *o = PA_SINK_INPUT(obj)->userdata; @@ -537,37 +571,6 @@ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64 } /* Called from main context */ -static void disable_output(struct output *o) { - pa_assert(o); - - if (!o->sink_input) - return; - - pa_sink_input_unlink(o->sink_input); - pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); - pa_sink_input_unref(o->sink_input); - o->sink_input = NULL; -} - -/* Called from main context */ -static void enable_output(struct output *o) { - pa_assert(o); - - if (o->sink_input) - return; - - if (output_create_sink_input(o) >= 0) { - - pa_memblockq_flush_write(o->memblockq); - - pa_sink_input_put(o->sink_input); - - if (o->userdata->sink && PA_SINK_IS_LINKED(pa_sink_get_state(o->userdata->sink))) - pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); - } -} - -/* Called from main context */ static void suspend(struct userdata *u) { struct output *o; uint32_t idx; @@ -575,8 +578,8 @@ static void suspend(struct userdata *u) { pa_assert(u); /* Let's suspend by unlinking all streams */ - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - disable_output(o); + PA_IDXSET_FOREACH(o, u->outputs, idx) + output_disable(o); pa_log_info("Device suspended..."); } @@ -589,13 +592,8 @@ static void unsuspend(struct userdata *u) { pa_assert(u); /* Let's resume */ - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - - pa_sink_suspend(o->sink, FALSE, PA_SUSPEND_IDLE); - - if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) - enable_output(o); - } + PA_IDXSET_FOREACH(o, u->outputs, idx) + output_enable(o); pa_log_info("Resumed successfully..."); } @@ -639,7 +637,13 @@ static void update_max_request(struct userdata *u) { size_t max_request = 0; struct output *o; - for (o = u->thread_info.active_outputs; o; o = o->next) { + pa_assert(u); + pa_sink_assert_io_context(u->sink); + + /* Collects the max_request values of all streams and sets the + * largest one locally */ + + PA_LLIST_FOREACH(o, u->thread_info.active_outputs) { size_t mr = (size_t) pa_atomic_load(&o->max_request); if (mr > max_request) @@ -652,6 +656,67 @@ static void update_max_request(struct userdata *u) { pa_sink_set_max_request_within_thread(u->sink, max_request); } +/* Called from IO context */ +static void update_fixed_latency(struct userdata *u) { + pa_usec_t fixed_latency = 0; + struct output *o; + + pa_assert(u); + pa_sink_assert_io_context(u->sink); + + /* Collects the requested_latency values of all streams and sets + * the largest one as fixed_latency locally */ + + PA_LLIST_FOREACH(o, u->thread_info.active_outputs) { + pa_usec_t rl = (size_t) pa_atomic_load(&o->requested_latency); + + if (rl > fixed_latency) + fixed_latency = rl; + } + + if (fixed_latency <= 0) + fixed_latency = u->block_usec; + + pa_sink_set_fixed_latency_within_thread(u->sink, fixed_latency); +} + +/* Called from thread context of the io thread */ +static void output_add_within_thread(struct output *o) { + pa_assert(o); + pa_sink_assert_io_context(o->sink); + + PA_LLIST_PREPEND(struct output, o->userdata->thread_info.active_outputs, o); + + pa_assert(!o->outq_rtpoll_item_read && !o->inq_rtpoll_item_write); + + o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + o->userdata->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + o->outq); + o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + o->userdata->rtpoll, + PA_RTPOLL_EARLY, + o->inq); +} + +/* Called from thread context of the io thread */ +static void output_remove_within_thread(struct output *o) { + pa_assert(o); + pa_sink_assert_io_context(o->sink); + + PA_LLIST_REMOVE(struct output, o->userdata->thread_info.active_outputs, o); + + if (o->outq_rtpoll_item_read) { + pa_rtpoll_item_free(o->outq_rtpoll_item_read); + o->outq_rtpoll_item_read = NULL; + } + + if (o->inq_rtpoll_item_write) { + pa_rtpoll_item_free(o->inq_rtpoll_item_write); + o->inq_rtpoll_item_write = NULL; + } +} + /* Called from thread context of the io thread */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; @@ -684,42 +749,17 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse return 0; } - case SINK_MESSAGE_ADD_OUTPUT: { - struct output *op = data; - - PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op); - - pa_assert(!op->outq_rtpoll_item_read && !op->inq_rtpoll_item_write); - - op->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( - u->rtpoll, - PA_RTPOLL_EARLY-1, /* This item is very important */ - op->outq); - op->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( - u->rtpoll, - PA_RTPOLL_EARLY, - op->inq); - + case SINK_MESSAGE_ADD_OUTPUT: + output_add_within_thread(data); update_max_request(u); + update_fixed_latency(u); return 0; - } - - case SINK_MESSAGE_REMOVE_OUTPUT: { - struct output *op = data; - - PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op); - - pa_assert(op->outq_rtpoll_item_read && op->inq_rtpoll_item_write); - - pa_rtpoll_item_free(op->outq_rtpoll_item_read); - op->outq_rtpoll_item_read = NULL; - - pa_rtpoll_item_free(op->inq_rtpoll_item_write); - op->inq_rtpoll_item_write = NULL; + case SINK_MESSAGE_REMOVE_OUTPUT: + output_remove_within_thread(data); update_max_request(u); + update_fixed_latency(u); return 0; - } case SINK_MESSAGE_NEED: render_memblock(u, (struct output*) data, (size_t) offset); @@ -741,10 +781,13 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse } case SINK_MESSAGE_UPDATE_MAX_REQUEST: - update_max_request(u); break; - } + + case SINK_MESSAGE_UPDATE_REQUESTED_LATENCY: + update_fixed_latency(u); + break; +} return pa_sink_process_msg(o, code, data, offset, chunk); } @@ -767,7 +810,7 @@ static void update_description(struct userdata *u) { t = pa_xstrdup("Simultaneous output to"); - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + PA_IDXSET_FOREACH(o, u->outputs, idx) { char *e; if (first) { @@ -801,8 +844,9 @@ static int output_create_sink_input(struct output *o) { pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map); data.module = o->userdata->module; data.resample_method = o->userdata->resample_method; + data.flags = PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE|PA_SINK_INPUT_NO_CREATE_ON_SUSPEND; - pa_sink_input_new(&o->sink_input, o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE); + pa_sink_input_new(&o->sink_input, o->userdata->core, &data); pa_sink_input_new_data_done(&data); @@ -812,9 +856,9 @@ static int output_create_sink_input(struct output *o) { o->sink_input->parent.process_msg = sink_input_process_msg; o->sink_input->pop = sink_input_pop_cb; o->sink_input->process_rewind = sink_input_process_rewind_cb; - o->sink_input->state_change = sink_input_state_change_cb; o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; o->sink_input->update_max_request = sink_input_update_max_request_cb; + o->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb; o->sink_input->attach = sink_input_attach_cb; o->sink_input->detach = sink_input_detach_cb; o->sink_input->kill = sink_input_kill_cb; @@ -825,22 +869,19 @@ static int output_create_sink_input(struct output *o) { return 0; } +/* Called from main context */ static struct output *output_new(struct userdata *u, pa_sink *sink) { struct output *o; - pa_sink_state_t state; pa_assert(u); pa_assert(sink); pa_assert(u->sink); - o = pa_xnew(struct output, 1); + o = pa_xnew0(struct output, 1); o->userdata = u; o->inq = pa_asyncmsgq_new(0); o->outq = pa_asyncmsgq_new(0); - o->inq_rtpoll_item_write = o->inq_rtpoll_item_read = NULL; - o->outq_rtpoll_item_write = o->outq_rtpoll_item_read = NULL; o->sink = sink; - o->sink_input = NULL; o->memblockq = pa_memblockq_new( 0, MEMBLOCKQ_MAXLENGTH, @@ -850,84 +891,135 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) { 0, 0, NULL); - pa_atomic_store(&o->max_request, 0); - PA_LLIST_INIT(struct output, o); pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); + update_description(u); - state = pa_sink_get_state(u->sink); - - if (state != PA_SINK_INIT) - pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); - else { - /* If the sink is not yet started, we need to do the activation ourselves */ - PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o); - - o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( - u->rtpoll, - PA_RTPOLL_EARLY-1, /* This item is very important */ - o->outq); - o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( - u->rtpoll, - PA_RTPOLL_EARLY, - o->inq); - } + return o; +} - if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { - pa_sink_suspend(sink, FALSE, PA_SUSPEND_IDLE); +/* Called from main context */ +static void output_free(struct output *o) { + pa_assert(o); - if (PA_SINK_IS_OPENED(pa_sink_get_state(sink))) - if (output_create_sink_input(o) < 0) - goto fail; - } + output_disable(o); - update_description(u); + pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); + update_description(o->userdata); - return o; + if (o->inq_rtpoll_item_read) + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + if (o->inq_rtpoll_item_write) + pa_rtpoll_item_free(o->inq_rtpoll_item_write); -fail: + if (o->outq_rtpoll_item_read) + pa_rtpoll_item_free(o->outq_rtpoll_item_read); + if (o->outq_rtpoll_item_write) + pa_rtpoll_item_free(o->outq_rtpoll_item_write); - if (o) { - pa_idxset_remove_by_data(u->outputs, o, NULL); + if (o->inq) + pa_asyncmsgq_unref(o->inq); - if (o->sink_input) { - pa_sink_input_unlink(o->sink_input); - pa_sink_input_unref(o->sink_input); - } + if (o->outq) + pa_asyncmsgq_unref(o->outq); + + if (o->memblockq) + pa_memblockq_free(o->memblockq); + + pa_xfree(o); +} + +/* Called from main context */ +static void output_enable(struct output *o) { + pa_assert(o); + + if (o->sink_input) + return; + + /* This might cause the sink to be resumed. The state change hook + * of the sink might hence be called from here, which might then + * cause us to be called in a loop. Make sure that state changes + * for this output don't cause this loop by setting a flag here */ + o->ignore_state_change = TRUE; + + if (output_create_sink_input(o) >= 0) { - if (o->memblockq) - pa_memblockq_free(o->memblockq); + if (pa_sink_get_state(o->sink) != PA_SINK_INIT) { - if (o->inq) - pa_asyncmsgq_unref(o->inq); + /* First we register the output. That means that the sink + * will start to pass data to this output. */ + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); - if (o->outq) - pa_asyncmsgq_unref(o->outq); + /* Then we enable the sink input. That means that the sink + * is now asked for new data. */ + pa_sink_input_put(o->sink_input); - pa_xfree(o); + } else + /* Hmm the sink is not yet started, do things right here */ + output_add_within_thread(o); } - return NULL; + o->ignore_state_change = FALSE; } +/* Called from main context */ +static void output_disable(struct output *o) { + pa_assert(o); + + if (!o->sink_input) + return; + + /* First we disable the sink input. That means that the sink is + * not asked for new data anymore */ + pa_sink_input_unlink(o->sink_input); + + /* Then we unregister the output. That means that the sink doesn't + * pass any further data to this output */ + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + + /* Now dellocate the stream */ + pa_sink_input_unref(o->sink_input); + o->sink_input = NULL; + + /* Finally, drop all queued data */ + pa_memblockq_flush_write(o->memblockq); + pa_asyncmsgq_flush(o->inq, FALSE); + pa_asyncmsgq_flush(o->outq, FALSE); +} + +/* Called from main context */ +static void output_verify(struct output *o) { + pa_assert(o); + + if (PA_SINK_IS_OPENED(pa_sink_get_state(o->userdata->sink))) + output_enable(o); + else + output_disable(o); +} + +/* Called from main context */ static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) { const char *t; pa_sink_assert_ref(s); + if (s == u->sink) + return FALSE; + if (!(s->flags & PA_SINK_HARDWARE)) return FALSE; - if (s == u->sink) + if (!(s->flags & PA_SINK_LATENCY)) return FALSE; if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS))) - if (strcmp(t, "sound")) + if (!pa_streq(t, "sound")) return FALSE; return TRUE; } +/* Called from main context */ static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; @@ -940,18 +1032,17 @@ static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata return PA_HOOK_OK; pa_log_info("Configuring new sink: %s", s->name); - if (!(o = output_new(u, s))) { pa_log("Failed to create sink input on sink '%s'.", s->name); return PA_HOOK_OK; } - if (o->sink_input) - pa_sink_input_put(o->sink_input); + output_verify(o); return PA_HOOK_OK; } +/* Called from main context */ static struct output* find_output(struct userdata *u, pa_sink *s) { struct output *o; uint32_t idx; @@ -962,13 +1053,14 @@ static struct output* find_output(struct userdata *u, pa_sink *s) { if (u->sink == s) return NULL; - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + PA_IDXSET_FOREACH(o, u->outputs, idx) if (o->sink == s) return o; return NULL; } +/* Called from main context */ static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; @@ -980,26 +1072,25 @@ static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userd return PA_HOOK_OK; pa_log_info("Unconfiguring sink: %s", s->name); - output_free(o); return PA_HOOK_OK; } +/* Called from main context */ static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; - pa_sink_state_t state; if (!(o = find_output(u, s))) return PA_HOOK_OK; - state = pa_sink_get_state(s); - - if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) - enable_output(o); + /* This state change might be triggered because we are creating a + * stream here, in that case we don't want to create it a second + * time here and enter a loop */ + if (o->ignore_state_change) + return PA_HOOK_OK; - if (state == PA_SINK_SUSPENDED && o->sink_input) - disable_output(o); + output_verify(o); return PA_HOOK_OK; } @@ -1014,6 +1105,7 @@ int pa__init(pa_module*m) { struct output *o; uint32_t idx; pa_sink_new_data data; + uint32_t adjust_time_sec; pa_assert(m); @@ -1029,23 +1121,13 @@ int pa__init(pa_module*m) { } } - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; - u->sink = NULL; - u->time_event = NULL; - u->adjust_time = DEFAULT_ADJUST_TIME; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->thread = NULL; u->resample_method = resample_method; u->outputs = pa_idxset_new(NULL, NULL); - memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp)); - u->sink_put_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL; - PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs); - pa_atomic_store(&u->thread_info.running, FALSE); - u->thread_info.in_null_mode = FALSE; - u->thread_info.counter = 0; u->thread_info.smoother = pa_smoother_new( PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, @@ -1055,16 +1137,73 @@ int pa__init(pa_module*m) { 0, FALSE); - if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) { + adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; + if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { pa_log("Failed to parse adjust_time value"); goto fail; } + if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC) + u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC; + else + u->adjust_time = DEFAULT_ADJUST_TIME_USEC; + slaves = pa_modargs_get_value(ma, "slaves", NULL); u->automatic = !slaves; ss = m->core->default_sample_spec; map = m->core->default_channel_map; + + /* Check the specified slave sinks for sample_spec and channel_map to use for the combined sink */ + if (!u->automatic) { + const char*split_state = NULL; + char *n = NULL; + pa_sample_spec slaves_spec; + pa_channel_map slaves_map; + pa_bool_t is_first_slave = TRUE; + + pa_sample_spec_init(&slaves_spec); + + while ((n = pa_split(slaves, ",", &split_state))) { + pa_sink *slave_sink; + + if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK))) { + pa_log("Invalid slave sink '%s'", n); + pa_xfree(n); + goto fail; + } + + pa_xfree(n); + + if (is_first_slave) { + slaves_spec = slave_sink->sample_spec; + slaves_map = slave_sink->channel_map; + is_first_slave = FALSE; + } else { + if (slaves_spec.format != slave_sink->sample_spec.format) + slaves_spec.format = PA_SAMPLE_INVALID; + + if (slaves_spec.rate < slave_sink->sample_spec.rate) + slaves_spec.rate = slave_sink->sample_spec.rate; + + if (!pa_channel_map_equal(&slaves_map, &slave_sink->channel_map)) + slaves_spec.channels = 0; + } + } + + if (!is_first_slave) { + if (slaves_spec.format != PA_SAMPLE_INVALID) + ss.format = slaves_spec.format; + + ss.rate = slaves_spec.rate; + + if (slaves_spec.channels > 0) { + map = slaves_map; + ss.channels = slaves_map.channels; + } + } + } + if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) { pa_log("Invalid sample specification."); goto fail; @@ -1095,7 +1234,6 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output"); } - u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); @@ -1149,7 +1287,7 @@ int pa__init(pa_module*m) { /* We're in automatic mode, we add every sink that matches our needs */ - for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) { + PA_IDXSET_FOREACH(s, m->core->sinks, idx) { if (!is_suitable_sink(u, s)) continue; @@ -1174,12 +1312,11 @@ int pa__init(pa_module*m) { /* Activate the sink and the sink inputs */ pa_sink_put(u->sink); - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - if (o->sink_input) - pa_sink_input_put(o->sink_input); + PA_IDXSET_FOREACH(o, u->outputs, idx) + output_verify(o); if (u->adjust_time > 0) - u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC, time_callback, u); + u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u); pa_modargs_free(ma); @@ -1195,37 +1332,6 @@ fail: return -1; } -static void output_free(struct output *o) { - pa_assert(o); - - disable_output(o); - - pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); - - update_description(o->userdata); - - if (o->inq_rtpoll_item_read) - pa_rtpoll_item_free(o->inq_rtpoll_item_read); - if (o->inq_rtpoll_item_write) - pa_rtpoll_item_free(o->inq_rtpoll_item_write); - - if (o->outq_rtpoll_item_read) - pa_rtpoll_item_free(o->outq_rtpoll_item_read); - if (o->outq_rtpoll_item_write) - pa_rtpoll_item_free(o->outq_rtpoll_item_write); - - if (o->inq) - pa_asyncmsgq_unref(o->inq); - - if (o->outq) - pa_asyncmsgq_unref(o->outq); - - if (o->memblockq) - pa_memblockq_free(o->memblockq); - - pa_xfree(o); -} - void pa__done(pa_module*m) { struct userdata *u; struct output *o; diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c index a666073c..103f5c48 100644 --- a/src/modules/module-console-kit.c +++ b/src/modules/module-console-kit.c @@ -187,7 +187,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo } add_session(u, path); - return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) { @@ -202,7 +201,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo } remove_session(u, path); - return DBUS_HANDLER_RESULT_HANDLED; } finish: diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c index 27ae60e5..94f589e9 100644 --- a/src/modules/module-default-device-restore.c +++ b/src/modules/module-default-device-restore.c @@ -60,7 +60,7 @@ static void load(struct userdata *u) { if (u->core->default_sink) pa_log_info("Manually configured default sink, not overwriting."); - else if ((f = fopen(u->sink_filename, "r"))) { + else if ((f = pa_fopen_cloexec(u->sink_filename, "r"))) { char ln[256] = ""; pa_sink *s; @@ -81,7 +81,7 @@ static void load(struct userdata *u) { if (u->core->default_source) pa_log_info("Manually configured default source, not overwriting."); - else if ((f = fopen(u->source_filename, "r"))) { + else if ((f = pa_fopen_cloexec(u->source_filename, "r"))) { char ln[256] = ""; pa_source *s; @@ -108,7 +108,7 @@ static void save(struct userdata *u) { return; if (u->sink_filename) { - if ((f = fopen(u->sink_filename, "w"))) { + if ((f = pa_fopen_cloexec(u->sink_filename, "w"))) { pa_sink *s = pa_namereg_get_default_sink(u->core); fprintf(f, "%s\n", s ? s->name : ""); fclose(f); @@ -117,7 +117,7 @@ static void save(struct userdata *u) { } if (u->source_filename) { - if ((f = fopen(u->source_filename, "w"))) { + if ((f = pa_fopen_cloexec(u->source_filename, "w"))) { pa_source *s = pa_namereg_get_default_source(u->core); fprintf(f, "%s\n", s ? s->name : ""); fclose(f); diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 18479df3..1fe8eb8b 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -50,7 +50,7 @@ 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>"); -PA_MODULE_DEPRECATED("Please use module-hal-detect instead of module-detect!"); +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-detect!"); static const char* const valid_modargs[] = { "just-one", @@ -63,7 +63,7 @@ static int detect_alsa(pa_core *c, int just_one) { FILE *f; int n = 0, n_sink = 0, n_source = 0; - if (!(f = fopen("/proc/asound/devices", "r"))) { + if (!(f = pa_fopen_cloexec("/proc/asound/devices", "r"))) { if (errno != ENOENT) pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno)); @@ -119,14 +119,14 @@ static int detect_alsa(pa_core *c, int just_one) { } #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT static int detect_oss(pa_core *c, int just_one) { FILE *f; int n = 0, b = 0; - if (!(f = fopen("/dev/sndstat", "r")) && - !(f = fopen("/proc/sndstat", "r")) && - !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) { if (errno != ENOENT) pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); @@ -240,7 +240,7 @@ int pa__init(pa_module*m) { #ifdef HAVE_ALSA if ((n = detect_alsa(m->core, just_one)) <= 0) #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT if ((n = detect_oss(m->core, just_one)) <= 0) #endif #ifdef HAVE_SOLARIS diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c new file mode 100644 index 00000000..3991043d --- /dev/null +++ b/src/modules/module-device-manager.c @@ -0,0 +1,1540 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/rtclock.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-native.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/database.h> + +#include "module-device-manager-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "do_routing=<Automatically route streams based on a priority list (unique per-role)?> " + "on_hotplug=<When new device becomes available, recheck streams?> " + "on_rescue=<When device becomes unavailable, recheck streams?>"); + +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) +#define DUMP_DATABASE + +static const char* const valid_modargs[] = { + "do_routing", + "on_hotplug", + "on_rescue", + NULL +}; + +#define NUM_ROLES 9 +enum { + ROLE_NONE, + ROLE_VIDEO, + ROLE_MUSIC, + ROLE_GAME, + ROLE_EVENT, + ROLE_PHONE, + ROLE_ANIMATION, + ROLE_PRODUCTION, + ROLE_A11Y, +}; + +typedef uint32_t role_indexes_t[NUM_ROLES]; + +static const char* role_names[NUM_ROLES] = { + "none", + "video", + "music", + "game", + "event", + "phone", + "animation", + "production", + "a11y", +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_subscription *subscription; + pa_hook_slot + *sink_new_hook_slot, + *source_new_hook_slot, + *sink_input_new_hook_slot, + *source_output_new_hook_slot, + *sink_put_hook_slot, + *source_put_hook_slot, + *sink_unlink_hook_slot, + *source_unlink_hook_slot, + *connection_unlink_hook_slot; + pa_time_event *save_time_event; + pa_database *database; + + pa_native_protocol *protocol; + pa_idxset *subscribed; + + pa_bool_t on_hotplug; + pa_bool_t on_rescue; + pa_bool_t do_routing; + + role_indexes_t preferred_sinks; + role_indexes_t preferred_sources; +}; + +#define ENTRY_VERSION 1 + +struct entry { + uint8_t version; + char description[PA_NAME_MAX]; + pa_bool_t user_set_description; + char icon[PA_NAME_MAX]; + role_indexes_t priority; +} PA_GCC_PACKED; + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_RENAME, + SUBCOMMAND_DELETE, + SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, + SUBCOMMAND_REORDER, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + + +static struct entry* read_entry(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e; + + pa_assert(u); + pa_assert(name); + + key.data = (char*) name; + key.size = strlen(name); + + pa_zero(data); + + if (!pa_database_get(u->database, &key, &data)) + goto fail; + + if (data.size != sizeof(struct entry)) { + pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); + goto fail; + } + + e = (struct entry*) data.data; + + if (e->version != ENTRY_VERSION) { + pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name); + goto fail; + } + + if (!memchr(e->description, 0, sizeof(e->description))) { + pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name); + goto fail; + } + + if (!memchr(e->icon, 0, sizeof(e->icon))) { + pa_log_warn("Database contains entry for device %s with missing NUL byte in icon", name); + goto fail; + } + + return e; + +fail: + + pa_datum_free(&data); + return NULL; +} + +#ifdef DUMP_DATABASE +static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) { + pa_assert(u); + pa_assert(human); + + if (sink_mode) { + pa_sink *s; + if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No sink specified", human); + } else { + pa_source *s; + if (PA_INVALID_INDEX != u->preferred_sources[role_index] && (s = pa_idxset_get_by_index(u->core->sources, u->preferred_sources[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No source specified", human); + } +} + +static void dump_database(struct userdata *u) { + pa_datum key; + pa_bool_t done; + + pa_assert(u); + + done = !pa_database_first(u->database, &key, NULL); + + pa_log_debug("Dumping database"); + while (!done) { + char *name; + struct entry *e; + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + + if ((e = read_entry(u, name))) { + pa_log_debug(" Got entry: %s", name); + pa_log_debug(" Description: %s", e->description); + pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u", + e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]); + pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u", + e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]); + pa_xfree(e); + } + + pa_xfree(name); + + pa_datum_free(&key); + key = next_key; + } + + if (u->do_routing) { + pa_log_debug(" Highest priority devices per-role:"); + + pa_log_debug(" Sinks:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, TRUE); + } + + pa_log_debug(" Sources:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, FALSE); + } + } + + pa_log_debug("Completed database dump"); +} +#endif + +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(u); + + pa_assert(e == u->save_time_event); + u->core->mainloop->time_free(u->save_time_event); + u->save_time_event = NULL; + + pa_database_sync(u->database); + pa_log_info("Synced."); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif +} + +static void notify_subscribers(struct userdata *u) { + + pa_native_connection *c; + uint32_t idx; + + pa_assert(u); + + for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) { + pa_tagstruct *t; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION); + pa_tagstruct_putu32(t, 0); + pa_tagstruct_putu32(t, u->module->index); + pa_tagstruct_puts(t, u->module->name); + pa_tagstruct_putu32(t, SUBCOMMAND_EVENT); + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t); + } +} + +static void trigger_save(struct userdata *u) { + + pa_assert(u); + + notify_subscribers(u); + + if (u->save_time_event) + return; + + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); +} + +static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { + + pa_assert(a); + pa_assert(b); + + if (strncmp(a->description, b->description, sizeof(a->description)) + || a->user_set_description != b->user_set_description + || strncmp(a->icon, b->icon, sizeof(a->icon))) + return FALSE; + + for (int i=0; i < NUM_ROLES; ++i) + if (a->priority[i] != b->priority[i]) + return FALSE; + + return TRUE; +} + +static char *get_name(const char *key, const char *prefix) { + char *t; + + if (strncmp(key, prefix, strlen(prefix))) + return NULL; + + t = pa_xstrdup(key + strlen(prefix)); + return t; +} + +static inline struct entry *load_or_initialize_entry(struct userdata *u, struct entry *entry, const char *name, const char *prefix) { + struct entry *old; + + pa_assert(u); + pa_assert(entry); + pa_assert(name); + pa_assert(prefix); + + if ((old = read_entry(u, name))) + *entry = *old; + else { + /* This is a new device, so make sure we write it's priority list correctly */ + role_indexes_t max_priority; + pa_datum key; + pa_bool_t done; + + pa_zero(max_priority); + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we calculate the current max priority for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name2; + struct entry *e; + + name2 = pa_xstrndup(key.data, key.size); + + if ((e = read_entry(u, name2))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + max_priority[i] = PA_MAX(max_priority[i], e->priority[i]); + } + + pa_xfree(e); + } + + pa_xfree(name2); + } + pa_datum_free(&key); + key = next_key; + } + + /* Actually initialise our entry now we've calculated it */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + entry->priority[i] = max_priority[i] + 1; + } + entry->user_set_description = FALSE; + } + + return old; +} + +static uint32_t get_role_index(const char* role) { + pa_assert(role); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) + if (strcmp(role, role_names[i]) == 0) + return i; + + return PA_INVALID_INDEX; +} + +static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) { + role_indexes_t *indexes, highest_priority_available; + pa_datum key; + pa_bool_t done, sink_mode; + + pa_assert(u); + pa_assert(prefix); + + sink_mode = (strcmp(prefix, "sink:") == 0); + + if (sink_mode) + indexes = &u->preferred_sinks; + else + indexes = &u->preferred_sources; + + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + (*indexes)[i] = PA_INVALID_INDEX; + } + pa_zero(highest_priority_available); + + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we find the highest priority device for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name, *device_name; + struct entry *e; + + name = pa_xstrndup(key.data, key.size); + device_name = get_name(name, prefix); + + if ((e = read_entry(u, name))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + if (!highest_priority_available[i] || e->priority[i] < highest_priority_available[i]) { + /* We've found a device with a higher priority than that we've currently got, + so see if it is currently available or not and update our list */ + uint32_t idx; + pa_bool_t found = FALSE; + + if (sink_mode) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if ((pa_sink*) ignore_device == sink) + continue; + if (strcmp(sink->name, device_name) == 0) { + found = TRUE; + idx = sink->index; /* Is this needed? */ + break; + } + } + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) { + if ((pa_source*) ignore_device == source) + continue; + if (strcmp(source->name, device_name) == 0) { + found = TRUE; + idx = source->index; /* Is this needed? */ + break; + } + } + } + if (found) { + highest_priority_available[i] = e->priority[i]; + (*indexes)[i] = idx; + } + + } + } + + pa_xfree(e); + } + + pa_xfree(name); + pa_xfree(device_name); + } + + pa_datum_free(&key); + key = next_key; + } +} + + +static void route_sink_input(struct userdata *u, pa_sink_input *si) { + const char *role; + uint32_t role_index, device_index; + pa_sink *sink; + + pa_assert(u); + pa_assert(u->do_routing); + + if (si->save_sink) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!si->sink) + return; + + /* 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 */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + return; + + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index))) + return; + + if (si->sink != sink) + pa_sink_input_move_to(si, sink, FALSE); +} + +static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "sink:", ignore_sink); + + PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { + route_sink_input(u, si); + } + + return PA_HOOK_OK; +} + +static void route_source_output(struct userdata *u, pa_source_output *so) { + const char *role; + uint32_t role_index, device_index; + pa_source *source; + + pa_assert(u); + pa_assert(u->do_routing); + + if (so->save_source) + return; + + if (so->direct_on_input) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!so->source) + return; + + /* 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 */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + return; + + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(source = pa_idxset_get_by_index(u->core->sources, device_index))) + return; + + if (so->source != source) + pa_source_output_move_to(so, source, FALSE); +} + +static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) { + pa_source_output *so; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "source:", ignore_source); + + PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) { + route_source_output(u, so); + } + + return PA_HOOK_OK; +} + +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + struct entry entry, *old = NULL; + char *name = NULL; + pa_datum key, data; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) && + + /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + pa_zero(entry); + entry.version = ENTRY_VERSION; + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { + pa_sink_input *si; + + if (!u->do_routing) + return; + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_sink_input(u, si); + + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { + pa_source_output *so; + + if (!u->do_routing) + return; + if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_source_output(u, so); + + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + pa_sink *sink; + + if (!(sink = pa_idxset_get_by_index(c->sinks, idx))) + return; + + name = pa_sprintf_malloc("sink:%s", sink->name); + + old = load_or_initialize_entry(u, &entry, name, "sink:"); + + if (!entry.user_set_description) + pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)); + else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_sink_set_description(sink, entry.description); + } + + pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + pa_source *source; + + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + + if (!(source = pa_idxset_get_by_index(c->sources, idx))) + return; + + if (source->monitor_of) + return; + + name = pa_sprintf_malloc("source:%s", source->name); + + old = load_or_initialize_entry(u, &entry, name, "source:"); + + if (!entry.user_set_description) + pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)); + else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_source_set_description(source, entry.description); + } + + pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon)); + } + + pa_assert(name); + + if (old) { + + if (entries_equal(old, &entry)) { + pa_xfree(old); + pa_xfree(name); + + return; + } + + pa_xfree(old); + } + + key.data = name; + key.size = strlen(name); + + data.data = &entry; + data.size = sizeof(entry); + + pa_log_info("Storing device %s.", name); + + if (pa_database_set(u->database, &key, &data, TRUE) == 0) + trigger_save(u); + else + pa_log_warn("Could not save device");; + + pa_xfree(name); +} + +static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + name = pa_sprintf_malloc("sink:%s", new_data->name); + + if ((e = read_entry(u, name))) { + if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + pa_log_info("Restoring description for sink %s.", new_data->name); + pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); + } + + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + name = pa_sprintf_malloc("source:%s", new_data->name); + + if ((e = read_entry(u, name))) { + if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */ + pa_log_info("Restoring description for source %s.", new_data->name); + pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); + } + + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + if (new_data->sink) + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX != device_index) { + pa_sink *sink; + + if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) { + new_data->sink = sink; + new_data->save_sink = FALSE; + } + } + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + if (new_data->direct_on_input) + return PA_HOOK_OK; + + if (new_data->source) + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX != device_index) { + pa_source *source; + + if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) { + new_data->source = source; + new_data->save_source = FALSE; + } + } + } + } + + return PA_HOOK_OK; +} + + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) { + pa_assert(c); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_hotplug); + + notify_subscribers(u); + + return route_sink_inputs(u, NULL); +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) { + pa_assert(c); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_hotplug); + + notify_subscribers(u); + + return route_source_outputs(u, NULL); +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + notify_subscribers(u); + + return route_sink_inputs(u, sink); +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + notify_subscribers(u); + + return route_source_outputs(u, source); +} + + +static void apply_entry(struct userdata *u, const char *name, struct entry *e) { + uint32_t idx; + char *n; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + if (!e->user_set_description) + return; + + if ((n = get_name(name, "sink:"))) { + pa_sink *s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (!pa_streq(s->name, n)) { + continue; + } + + pa_log_info("Setting description for sink %s to '%s'", s->name, e->description); + pa_sink_set_description(s, e->description); + } + pa_xfree(n); + } + else if ((n = get_name(name, "source:"))) { + pa_source *s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (!pa_streq(s->name, n)) { + continue; + } + + if (s->monitor_of) { + pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s->name); + continue; + } + + pa_log_info("Setting description for source %s to '%s'", s->name, e->description); + pa_source_set_description(s, e->description); + } + pa_xfree(n); + } +} + + +#define EXT_VERSION 1 + +static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { + struct userdata *u; + uint32_t command; + pa_tagstruct *reply = NULL; + + pa_assert(p); + pa_assert(m); + pa_assert(c); + pa_assert(t); + + u = m->userdata; + + if (pa_tagstruct_getu32(t, &command) < 0) + goto fail; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + + switch (command) { + case SUBCOMMAND_TEST: { + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_putu32(reply, EXT_VERSION); + break; + } + + case SUBCOMMAND_READ: { + pa_datum key; + pa_bool_t done; + + if (!pa_tagstruct_eof(t)) + goto fail; + + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; + struct entry *e; + char *name; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); + + if ((e = read_entry(u, name))) { + uint32_t idx; + char *devname; + uint32_t found_index = PA_INVALID_INDEX; + + if ((devname = get_name(name, "sink:"))) { + pa_sink* s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (strcmp(s->name, devname) == 0) { + found_index = s->index; + break; + } + } + pa_xfree(devname); + } else if ((devname = get_name(name, "source:"))) { + pa_source* s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (strcmp(s->name, devname) == 0) { + found_index = s->index; + break; + } + } + pa_xfree(devname); + } + + pa_tagstruct_puts(reply, name); + pa_tagstruct_puts(reply, e->description); + pa_tagstruct_puts(reply, e->icon); + pa_tagstruct_putu32(reply, found_index); + pa_tagstruct_putu32(reply, NUM_ROLES); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) { + pa_tagstruct_puts(reply, role_names[i]); + pa_tagstruct_putu32(reply, e->priority[i]); + } + + pa_xfree(e); + } + + pa_xfree(name); + + key = next_key; + } + + break; + } + + case SUBCOMMAND_RENAME: { + + struct entry *e; + const char *device, *description; + + if (pa_tagstruct_gets(t, &device) < 0 || + pa_tagstruct_gets(t, &description) < 0) + goto fail; + + if (!device || !*device || !description || !*description) + goto fail; + + if ((e = read_entry(u, device))) { + pa_datum key, data; + + pa_strlcpy(e->description, description, sizeof(e->description)); + e->user_set_description = TRUE; + + key.data = (char *) device; + key.size = strlen(device); + + data.data = e; + data.size = sizeof(*e); + + if (pa_database_set(u->database, &key, &data, TRUE) == 0) { + apply_entry(u, device, e); + + trigger_save(u); + } + else + pa_log_warn("Could not save device"); + + pa_xfree(e); + } + else + pa_log_warn("Could not rename device %s, no entry in database", device); + + break; + } + + case SUBCOMMAND_DELETE: + + while (!pa_tagstruct_eof(t)) { + const char *name; + pa_datum key; + + if (pa_tagstruct_gets(t, &name) < 0) + goto fail; + + key.data = (char*) name; + key.size = strlen(name); + + /** @todo: Reindex the priorities */ + pa_database_unset(u->database, &key); + } + + trigger_save(u); + + break; + + case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: { + + pa_bool_t enable; + + if (pa_tagstruct_get_boolean(t, &enable) < 0) + goto fail; + + if ((u->do_routing = enable)) { + /* Update our caches */ + update_highest_priority_device_indexes(u, "sink:", NULL); + update_highest_priority_device_indexes(u, "source:", NULL); + } + + break; + } + + case SUBCOMMAND_REORDER: { + + const char *role; + struct entry *e; + uint32_t role_index, n_devices; + pa_datum key, data; + pa_bool_t done, sink_mode = TRUE; + struct device_t { uint32_t prio; char *device; }; + struct device_t *device; + struct device_t **devices; + uint32_t i, idx, offset; + pa_hashmap *h; + /*void *state;*/ + pa_bool_t first; + + if (pa_tagstruct_gets(t, &role) < 0 || + pa_tagstruct_getu32(t, &n_devices) < 0 || + n_devices < 1) + goto fail; + + if (PA_INVALID_INDEX == (role_index = get_role_index(role))) + goto fail; + + /* Cycle through the devices given and make sure they exist */ + h = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + first = TRUE; + idx = 0; + for (i = 0; i < n_devices; ++i) { + const char *s; + if (pa_tagstruct_gets(t, &s) < 0) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Protocol error on reorder"); + goto fail; + } + + /* Ensure this is a valid entry */ + if (!(e = read_entry(u, s))) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Client specified an unknown device in it's reorder list."); + goto fail; + } + pa_xfree(e); + + if (first) { + first = FALSE; + sink_mode = (0 == strncmp("sink:", s, 5)); + } else if ((sink_mode && 0 != strncmp("sink:", s, 5)) + || (!sink_mode && 0 != strncmp("source:", s, 7))) + { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Attempted to reorder mixed devices (sinks and sources)"); + goto fail; + } + + /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrdup(s); + if (pa_hashmap_put(h, device->device, device) == 0) { + device->prio = idx; + idx++; + } else { + pa_xfree(device->device); + pa_xfree(device); + } + } + + /*pa_log_debug("Hashmap contents (received from client)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now cycle through our list and add all the devices. + This has the effect of addign in any in our DB, + not specified in the device list (and thus will be + tacked on at the end) */ + offset = idx; + done = !pa_database_first(u->database, &key, NULL); + + while (!done && idx < 256) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrndup(key.data, key.size); + if ((sink_mode && 0 == strncmp("sink:", device->device, 5)) + || (!sink_mode && 0 == strncmp("source:", device->device, 7))) { + + /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + if (pa_hashmap_put(h, device->device, device) == 0 + && (e = read_entry(u, device->device))) { + /* We add offset on to the existing priorirty so that when we order, the + existing entries are always lower priority than the new ones. */ + device->prio = (offset + e->priority[role_index]); + pa_xfree(e); + } + else { + pa_xfree(device->device); + pa_xfree(device); + } + } else { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_datum_free(&key); + + key = next_key; + } + + /*pa_log_debug("Hashmap contents (combined with database)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now we put all the entries in a simple list for sorting it. */ + n_devices = pa_hashmap_size(h); + devices = pa_xnew(struct device_t *, n_devices); + idx = 0; + while ((device = pa_hashmap_steal_first(h))) { + devices[idx++] = device; + } + pa_hashmap_free(h, NULL, NULL); + + /* Simple bubble sort */ + for (i = 0; i < n_devices; ++i) { + for (uint32_t j = i; j < n_devices; ++j) { + if (devices[i]->prio > devices[j]->prio) { + struct device_t *tmp; + tmp = devices[i]; + devices[i] = devices[j]; + devices[j] = tmp; + } + } + } + + /*pa_log_debug("Sorted device list"); + for (i = 0; i < n_devices; ++i) { + pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio); + }*/ + + /* Go through in order and write the new entry and cleanup our own list */ + idx = 1; + first = TRUE; + for (i = 0; i < n_devices; ++i) { + if ((e = read_entry(u, devices[i]->device))) { + if (e->priority[role_index] == idx) + idx++; + else { + e->priority[role_index] = idx; + + key.data = (char *) devices[i]->device; + key.size = strlen(devices[i]->device); + + data.data = e; + data.size = sizeof(*e); + + if (pa_database_set(u->database, &key, &data, TRUE) == 0) { + first = FALSE; + idx++; + } + } + + pa_xfree(e); + } + pa_xfree(devices[i]->device); + pa_xfree(devices[i]); + } + + if (!first) { + trigger_save(u); + + if (sink_mode) + route_sink_inputs(u, NULL); + else + route_source_outputs(u, NULL); + } + + break; + } + + case SUBCOMMAND_SUBSCRIBE: { + + pa_bool_t enabled; + + if (pa_tagstruct_get_boolean(t, &enabled) < 0 || + !pa_tagstruct_eof(t)) + goto fail; + + if (enabled) + pa_idxset_put(u->subscribed, c, NULL); + else + pa_idxset_remove_by_data(u->subscribed, c, NULL); + + break; + } + + default: + goto fail; + } + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); + return 0; + + fail: + + if (reply) + pa_tagstruct_free(reply); + + return -1; +} + +static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) { + pa_assert(p); + pa_assert(c); + pa_assert(u); + + pa_idxset_remove_by_data(u->subscribed, c, NULL); + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + char *fname; + pa_sink *sink; + pa_source *source; + uint32_t idx; + pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "do_routing", &do_routing) < 0 || + pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || + pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { + pa_log("on_hotplug= and on_rescue= expect boolean arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->do_routing = do_routing; + u->on_hotplug = on_hotplug; + u->on_rescue = on_rescue; + u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->protocol = pa_native_protocol_get(m->core); + pa_native_protocol_install_ext(u->protocol, m, extension_cb); + + u->connection_unlink_hook_slot = pa_hook_connect(&pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u); + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); + + /* Used to handle device description management */ + u->sink_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u); + u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u); + + /* The following slots are used to deal with routing */ + /* A little bit later than module-stream-restore, but before module-intended-roles */ + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) sink_input_new_hook_callback, u); + u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) source_output_new_hook_callback, u); + + if (on_hotplug) { + /* A little bit later than module-stream-restore, but before module-intended-roles */ + u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_put_hook_callback, u); + u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) source_put_hook_callback, u); + } + + if (on_rescue) { + /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */ + u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u); + } + + if (!(fname = pa_state_path("device-manager", TRUE))) + goto fail; + + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); + pa_xfree(fname); + goto fail; + } + + pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_xfree(fname); + + /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */ + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + + PA_IDXSET_FOREACH(source, m->core->sources, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u); + + /* Perform the routing (if it's enabled) which will update our priority list cache too */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + u->preferred_sinks[i] = u->preferred_sources[i] = PA_INVALID_INDEX; + } + + route_sink_inputs(u, NULL); + route_source_outputs(u, NULL); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif + + pa_modargs_free(ma); + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_new_hook_slot) + pa_hook_slot_free(u->sink_new_hook_slot); + if (u->source_new_hook_slot) + pa_hook_slot_free(u->source_new_hook_slot); + + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->source_output_new_hook_slot) + pa_hook_slot_free(u->source_output_new_hook_slot); + + if (u->sink_put_hook_slot) + pa_hook_slot_free(u->sink_put_hook_slot); + if (u->source_put_hook_slot) + pa_hook_slot_free(u->source_put_hook_slot); + + if (u->sink_unlink_hook_slot) + pa_hook_slot_free(u->sink_unlink_hook_slot); + if (u->source_unlink_hook_slot) + pa_hook_slot_free(u->source_unlink_hook_slot); + + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + + if (u->database) + pa_database_close(u->database); + + if (u->protocol) { + pa_native_protocol_remove_ext(u->protocol, m); + pa_native_protocol_unref(u->protocol); + } + + if (u->subscribed) + pa_idxset_free(u->subscribed, NULL, NULL); + + pa_xfree(u); +} diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index 120b762c..da6c9666 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -218,7 +218,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (sink->save_volume) { entry.channel_map = sink->channel_map; - entry.volume = *pa_sink_get_volume(sink, FALSE, TRUE); + entry.volume = *pa_sink_get_volume(sink, FALSE); entry.volume_valid = TRUE; } diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c new file mode 100644 index 00000000..7c0ccd3a --- /dev/null +++ b/src/modules/module-equalizer-sink.c @@ -0,0 +1,2161 @@ +/*** +This file is part of PulseAudio. + +This module is based off Lennart Poettering's LADSPA sink and swaps out +LADSPA functionality for a dbus-aware STFT OLA based digital equalizer. +All new work is published under Pulseaudio's original license. +Copyright 2009 Jason Newton <nevion@gmail.com> + +Original Author: +Copyright 2004-2008 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, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <float.h> +#include <math.h> +#include <fftw3.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-rtclock.h> +#include <pulsecore/aupdate.h> +#include <pulsecore/core-error.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/shared.h> +#include <pulsecore/idxset.h> +#include <pulsecore/strlist.h> +#include <pulsecore/database.h> +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/dbus-util.h> + +#include <stdint.h> +#include <time.h> + + +//#undef __SSE2__ +#ifdef __SSE2__ +#include <xmmintrin.h> +#include <emmintrin.h> +#endif + + + +#include "module-equalizer-sink-symdef.h" + +PA_MODULE_AUTHOR("Jason Newton"); +PA_MODULE_DESCRIPTION(_("General Purpose Equalizer")); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE(_("sink=<sink to connect to> ")); + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + + +struct userdata { + pa_module *module; + pa_sink *sink; + pa_sink_input *sink_input; + char *name; + + size_t channels; + size_t fft_size;//length (res) of fft + size_t window_size;/* + *sliding window size + *effectively chooses R + */ + size_t R;/* the hop size between overlapping windows + * the latency of the filter, calculated from window_size + * based on constraints of COLA and window function + */ + //for twiddling with pulseaudio + size_t overlap_size;//window_size-R + size_t samples_gathered; + size_t input_buffer_max; + //message + float *W;//windowing function (time domain) + float *work_buffer, **input, **overlap_accum; + fftwf_complex *output_window; + fftwf_plan forward_plan, inverse_plan; + //size_t samplings; + + float **Xs; + float ***Hs;//thread updatable copies of the freq response filters (magintude based) + pa_aupdate **a_H; + pa_memchunk conv_buffer; + pa_memblockq *input_q; + pa_bool_t first_iteration; + + pa_dbus_protocol *dbus_protocol; + char *dbus_path; + pa_bool_t set_default; + + pa_database *database; + char **base_profiles; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", + "master", + "format", + "rate", + "set_default", + "channels", + "channel_map", + NULL +}; + + +#define v_size 4 +#define SINKLIST "equalized_sinklist" +#define EQDB "equalizer_db" +#define EQ_STATE_DB "equalizer-state" +#define FILTER_SIZE (u->fft_size / 2 + 1) +#define CHANNEL_PROFILE_SIZE (FILTER_SIZE + 1) +#define FILTER_STATE_SIZE (CHANNEL_PROFILE_SIZE * u->channels) +static void dbus_init(struct userdata *u); +static void dbus_done(struct userdata *u); + +static void hanning_window(float *W, size_t window_size){ + //h=.5*(1-cos(2*pi*j/(window_size+1)), COLA for R=(M+1)/2 + for(size_t i=0; i < window_size;++i){ + W[i] = (float).5*(1-cos(2*M_PI*i/(window_size+1))); + } +} + +static void fix_filter(float *H, size_t fft_size){ + //divide out the fft gain + for(size_t i = 0; i < fft_size / 2 + 1; ++i){ + H[i] /= fft_size; + } +} + +static void interpolate(float *signal, size_t length, uint32_t *xs, float *ys, size_t n_points){ + //Note that xs must be monotonically increasing! + float x_range_lower, x_range_upper, c0; + pa_assert_se(n_points>=2); + pa_assert_se(xs[0] == 0); + pa_assert_se(xs[n_points - 1] == length - 1); + for(size_t x = 0, x_range_lower_i = 0; x < length-1; ++x){ + pa_assert(x_range_lower_i < n_points-1); + x_range_lower = (float) (xs[x_range_lower_i]); + x_range_upper = (float) (xs[x_range_lower_i+1]); + pa_assert_se(x_range_lower < x_range_upper); + pa_assert_se(x >= x_range_lower); + pa_assert_se(x <= x_range_upper); + //bilinear-interpolation of coefficients specified + c0 = (x-x_range_lower)/(x_range_upper-x_range_lower); + pa_assert_se(c0 >= 0&&c0 <= 1.0); + signal[x] = ((1.0f - c0) * ys[x_range_lower_i] + c0 * ys[x_range_lower_i + 1]); + while(x >= xs[x_range_lower_i + 1]){ + x_range_lower_i++; + } + } + signal[length-1]=ys[n_points-1]; +} + +static int is_monotonic(const uint32_t *xs,size_t length){ + if(length<2){ + return 1; + } + for(size_t i = 1; i < length; ++i){ + if(xs[i]<=xs[i-1]){ + return 0; + } + } + return 1; +} + +//ensure's memory allocated is a multiple of v_size +//and aligned +static void * alloc(size_t x,size_t s){ + size_t f = PA_ROUND_UP(x*s, sizeof(float)*v_size); + float *t; + pa_assert(f >= x*s); + t = fftwf_malloc(f); + memset(t, 0, f); + return t; +} + +static void alloc_input_buffers(struct userdata *u, size_t min_buffer_length){ + if(min_buffer_length <= u->input_buffer_max){ + return; + } + pa_assert(min_buffer_length >= u->window_size); + for(size_t c = 0; c < u->channels; ++c){ + float *tmp = alloc(min_buffer_length, sizeof(float)); + if(u->input[c]){ + if(!u->first_iteration){ + memcpy(tmp, u->input[c], u->overlap_size * sizeof(float)); + } + free(u->input[c]); + } + u->input[c] = tmp; + } + u->input_buffer_max = min_buffer_length; +} + +/* Called from I/O thread context */ +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + //size_t fs=pa_frame_size(&u->sink->sample_spec); + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); + // pa_bytes_to_usec(u->samples_gathered * fs, &u->sink->sample_spec); + //+ pa_bytes_to_usec(u->latency * fs, ss) + //+ pa_bytes_to_usec(pa_memblockq_get_length(u->input_q), ss); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + + +/* Called from main context */ +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + return 0; +} + +/* Called from I/O thread context */ +static void sink_request_rewind_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes+pa_memblockq_get_length(u->input_q), TRUE, FALSE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from main context */ +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void sink_set_mute_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); +} + +#ifndef __SSE2__ +//reference implementation +static void dsp_logic( + float * restrict dst,//used as a temp array too, needs to be fft_length! + float * restrict src,/*input data w/ overlap at start, + *automatically cycled in routine + */ + float * restrict overlap, + const float X,//multipliar + const float * restrict H,//The freq. magnitude scalers filter + const float * restrict W,//The windowing function + fftwf_complex * restrict output_window,//The transformed window'd src + struct userdata *u){ + //use a linear-phase sliding STFT and overlap-add method (for each channel) + //window the data + for(size_t j = 0; j < u->window_size; ++j){ + dst[j] = X * W[j] * src[j]; + } + //zero padd the the remaining fft window + memset(dst + u->window_size, 0, (u->fft_size - u->window_size) * sizeof(float)); + //Processing is done here! + //do fft + fftwf_execute_dft_r2c(u->forward_plan, dst, output_window); + //perform filtering + for(size_t j = 0; j < FILTER_SIZE; ++j){ + u->output_window[j][0] *= H[j]; + u->output_window[j][1] *= H[j]; + } + //inverse fft + fftwf_execute_dft_c2r(u->inverse_plan, output_window, dst); + ////debug: tests overlaping add + ////and negates ALL PREVIOUS processing + ////yields a perfect reconstruction if COLA is held + //for(size_t j = 0; j < u->window_size; ++j){ + // u->work_buffer[j] = u->W[j] * u->input[c][j]; + //} + + //overlap add and preserve overlap component from this window (linear phase) + for(size_t j = 0; j < u->overlap_size; ++j){ + u->work_buffer[j] += overlap[j]; + overlap[j] = dst[u->R + j]; + } + ////debug: tests if basic buffering works + ////shouldn't modify the signal AT ALL (beyond roundoff) + //for(size_t j = 0; j < u->window_size;++j){ + // u->work_buffer[j] = u->input[c][j]; + //} + + //preseve the needed input for the next window's overlap + memmove(src, src + u->R, + (u->samples_gathered - u->R) * sizeof(float) + ); +} +#else +typedef float v4sf __attribute__ ((__aligned__(v_size * sizeof(float)))); +typedef union float_vector { + float f[v_size]; + v4sf v; + __m128 m; +} float_vector_t; + +//regardless of sse enabled, the loops in here assume +//16 byte aligned addresses and memory allocations divisible by v_size +static void dsp_logic( + float * restrict dst,//used as a temp array too, needs to be fft_length! + float * restrict src,/*input data w/ overlap at start, + *automatically cycled in routine + */ + float * restrict overlap,//The size of the overlap + const float X,//multipliar + const float * restrict H,//The freq. magnitude scalers filter + const float * restrict W,//The windowing function + fftwf_complex * restrict output_window,//The transformed window'd src + struct userdata *u){//Collection of constants + const size_t overlap_size = PA_ROUND_UP(u->overlap_size, v_size); + float_vector_t x; + x.f[0] = x.f[1] = x.f[2] = x.f[3] = X; + + //assert(u->samples_gathered >= u->R); + //use a linear-phase sliding STFT and overlap-add method + for(size_t j = 0; j < u->window_size; j += v_size){ + //dst[j] = W[j] * src[j]; + float_vector_t *d = (float_vector_t*) (dst + j); + float_vector_t *w = (float_vector_t*) (W + j); + float_vector_t *s = (float_vector_t*) (src + j); +//#if __SSE2__ + d->m = _mm_mul_ps(x.m, _mm_mul_ps(w->m, s->m)); +// d->v = x->v * w->v * s->v; +//#endif + } + //zero padd the the remaining fft window + memset(dst + u->window_size, 0, (u->fft_size - u->window_size) * sizeof(float)); + + //Processing is done here! + //do fft + fftwf_execute_dft_r2c(u->forward_plan, dst, output_window); + //perform filtering - purely magnitude based + for(size_t j = 0; j < FILTER_SIZE; j += v_size / 2){ + //output_window[j][0]*=H[j]; + //output_window[j][1]*=H[j]; + float_vector_t *d = (float_vector_t*)( ((float *) output_window) + 2 * j); + float_vector_t h; + h.f[0] = h.f[1] = H[j]; + h.f[2] = h.f[3] = H[j + 1]; +//#if __SSE2__ + d->m = _mm_mul_ps(d->m, h.m); +//#else +// d->v = d->v * h.v; +//#endif + } + + //inverse fft + fftwf_execute_dft_c2r(u->inverse_plan, output_window, dst); + + ////debug: tests overlaping add + ////and negates ALL PREVIOUS processing + ////yields a perfect reconstruction if COLA is held + //for(size_t j = 0; j < u->window_size; ++j){ + // dst[j] = W[j] * src[j]; + //} + + //overlap add and preserve overlap component from this window (linear phase) + for(size_t j = 0; j < overlap_size; j += v_size){ + //dst[j]+=overlap[j]; + //overlap[j]+=dst[j+R]; + float_vector_t *d = (float_vector_t*)(dst + j); + float_vector_t *o = (float_vector_t*)(overlap + j); +//#if __SSE2__ + d->m = _mm_add_ps(d->m, o->m); + o->m = ((float_vector_t*)(dst + u->R + j))->m; +//#else +// d->v = d->v + o->v; +// o->v = ((float_vector_t*)(dst + u->R + j))->v; +//#endif + } + //memcpy(overlap, dst+u->R, u->overlap_size * sizeof(float)); //overlap preserve (debug) + //zero out the bit beyond the real overlap so we don't add garbage next iteration + memset(overlap + u->overlap_size, 0, overlap_size - u->overlap_size); + + ////debug: tests if basic buffering works + ////shouldn't modify the signal AT ALL (beyond roundoff) + //for(size_t j = 0; j < u->window_size; ++j){ + // dst[j] = src[j]; + //} + + //preseve the needed input for the next window's overlap + memmove(src, src + u->R, + (u->samples_gathered - u->R) * sizeof(float) + ); +} +#endif + +static void process_samples(struct userdata *u, pa_memchunk *tchunk){ + size_t fs = pa_frame_size(&(u->sink->sample_spec)); + float *dst; + unsigned a_i; + float *H, X; + size_t iterations, offset; + pa_assert(u->samples_gathered >= u->window_size); + iterations = (u->samples_gathered - u->overlap_size) / u->R; + tchunk->index = 0; + tchunk->length = iterations * u->R * fs; + tchunk->memblock = pa_memblock_new(u->sink->core->mempool, tchunk->length); + dst = ((float*) pa_memblock_acquire(tchunk->memblock)); + for(size_t iter = 0; iter < iterations; ++iter){ + offset = iter * u->R * fs; + for(size_t c = 0;c < u->channels; c++) { + a_i = pa_aupdate_read_begin(u->a_H[c]); + X = u->Xs[c][a_i]; + H = u->Hs[c][a_i]; + dsp_logic( + u->work_buffer, + u->input[c], + u->overlap_accum[c], + X, + H, + u->W, + u->output_window, + u + ); + pa_aupdate_read_end(u->a_H[c]); + if(u->first_iteration){ + /* The windowing function will make the audio ramped in, as a cheap fix we can + * undo the windowing (for non-zero window values) + */ + for(size_t i = 0; i < u->overlap_size; ++i){ + u->work_buffer[i] = u->W[i] <= FLT_EPSILON ? u->work_buffer[i] : u->work_buffer[i] / u->W[i]; + } + } + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, (uint8_t *) (dst + c) + offset, fs, u->work_buffer, sizeof(float), u->R); + } + if(u->first_iteration){ + u->first_iteration = FALSE; + } + u->samples_gathered -= u->R; + } + pa_memblock_release(tchunk->memblock); +} + +static void input_buffer(struct userdata *u, pa_memchunk *in){ + size_t fs = pa_frame_size(&(u->sink->sample_spec)); + size_t samples = in->length/fs; + float *src = (float*) ((uint8_t*) pa_memblock_acquire(in->memblock) + in->index); + pa_assert(u->samples_gathered + samples <= u->input_buffer_max); + for(size_t c = 0; c < u->channels; c++) { + //buffer with an offset after the overlap from previous + //iterations + pa_assert_se( + u->input[c] + u->samples_gathered + samples <= u->input[c] + u->input_buffer_max + ); + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c] + u->samples_gathered, sizeof(float), src + c, fs, samples); + } + u->samples_gathered += samples; + pa_memblock_release(in->memblock); +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + size_t fs, target_samples, mbs; + //struct timeval start, end; + pa_memchunk tchunk; + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(chunk); + pa_assert(u->sink); + fs = pa_frame_size(&(u->sink->sample_spec)); + nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->sink->core->mempool)); + target_samples = PA_ROUND_UP(nbytes / fs, u->R); + mbs = pa_mempool_block_size_max(u->sink->core->mempool); + //pa_log_debug("vanilla mbs = %ld",mbs); + mbs = PA_ROUND_DOWN(mbs / fs, u->R); + mbs = PA_MAX(mbs, u->R); + target_samples = PA_MAX(target_samples, mbs); + //pa_log_debug("target samples: %ld", target_samples); + if(u->first_iteration){ + //allocate request_size + target_samples = PA_MAX(target_samples, u->window_size); + }else{ + //allocate request_size + overlap + target_samples += u->overlap_size; + } + alloc_input_buffers(u, target_samples); + //pa_log_debug("post target samples: %ld", target_samples); + chunk->memblock = NULL; + + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); + + //pa_log_debug("start output-buffered %ld, input-buffered %ld, requested %ld",buffered_samples,u->samples_gathered,samples_requested); + //pa_rtclock_get(&start); + do{ + size_t input_remaining = target_samples - u->samples_gathered; + // pa_log_debug("input remaining %ld samples", input_remaining); + pa_assert(input_remaining > 0); + while(pa_memblockq_peek(u->input_q, &tchunk) < 0){ + //pa_sink_render(u->sink, input_remaining * fs, &tchunk); + pa_sink_render_full(u->sink, input_remaining * fs, &tchunk); + pa_assert(tchunk.memblock); + pa_memblockq_push(u->input_q, &tchunk); + pa_memblock_unref(tchunk.memblock); + } + pa_assert(tchunk.memblock); + tchunk.length = PA_MIN(input_remaining * fs, tchunk.length); + pa_memblockq_drop(u->input_q, tchunk.length); + //pa_log_debug("asked for %ld input samples, got %ld samples",input_remaining,buffer->length/fs); + /* copy new input */ + //pa_rtclock_get(start); + // pa_log_debug("buffering %ld bytes", tchunk.length); + input_buffer(u, &tchunk); + //pa_rtclock_get(&end); + //pa_log_debug("Took %0.5f seconds to setup", pa_timeval_diff(end, start) / (double) PA_USEC_PER_SEC); + pa_memblock_unref(tchunk.memblock); + }while(u->samples_gathered < target_samples); + + //pa_rtclock_get(&end); + //pa_log_debug("Took %0.6f seconds to get data", (double) pa_timeval_diff(&end, &start) / PA_USEC_PER_SEC); + + pa_assert(u->fft_size >= u->window_size); + pa_assert(u->R < u->window_size); + //pa_rtclock_get(&start); + /* process a block */ + process_samples(u, chunk); + //pa_rtclock_get(&end); + //pa_log_debug("Took %0.6f seconds to process", (double) pa_timeval_diff(&end, &start) / PA_USEC_PER_SEC); + + pa_assert(chunk->memblock); + //pa_log_debug("gave %ld", chunk->length/fs); + //pa_log_debug("end pop"); + return 0; +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_volume_changed(u->sink, &i->volume); +} + +/* Called from main context */ +static void sink_input_mute_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_mute_changed(u->sink, i->muted); +} + +static void reset_filter(struct userdata *u){ + size_t fs = pa_frame_size(&u->sink->sample_spec); + size_t max_request; + u->samples_gathered = 0; + for(size_t i = 0; i < u->channels; ++i){ + memset(u->overlap_accum[i], 0, u->overlap_size * sizeof(float)); + } + u->first_iteration = TRUE; + //set buffer size to max request, no overlap copy + max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs , u->R); + max_request = PA_MAX(max_request, u->window_size); + pa_sink_set_max_request_within_thread(u->sink, max_request * fs); +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + size_t amount = 0; + + pa_log_debug("Rewind callback!"); + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t max_rewrite; + + //max_rewrite = nbytes; + max_rewrite = nbytes + pa_memblockq_get_length(u->input_q); + //PA_MIN(pa_memblockq_get_length(u->input_q), nbytes); + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); + u->sink->thread_info.rewind_nbytes = 0; + + if (amount > 0) { + //invalidate the output q + pa_memblockq_seek(u->input_q, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE); + pa_log("Resetting filter"); + //reset_filter(u); //this is the "proper" thing to do... + } + } + + pa_sink_process_rewind(u->sink, amount); + pa_memblockq_rewind(u->input_q, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_maxrewind(u->input_q, nbytes); + pa_sink_set_max_rewind_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + size_t fs; + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + //if(u->first_iteration){ + // return; + //} + fs = pa_frame_size(&(u->sink->sample_spec)); + pa_sink_set_max_request_within_thread(u->sink, PA_ROUND_UP(nbytes / fs, u->R) * fs); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); + + pa_sink_set_rtpoll(u->sink, NULL); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + size_t fs, max_request; + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + fs = pa_frame_size(&u->sink->sample_spec); + //set buffer size to max request, no overlap copy + max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs , u->R); + max_request = PA_MAX(max_request, u->window_size); + pa_sink_set_max_request_within_thread(u->sink, max_request * fs); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); + pa_sink_attach_within_thread(u->sink); + if(u->set_default){ + pa_log_debug("Setting default sink to %s", u->sink->name); + pa_namereg_set_default_sink(u->module->core, u->sink); + } +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ + pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module, TRUE); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); + } +} + +static void pack(char **strs, size_t len, char **packed, size_t *length){ + size_t t_len = 0; + size_t headers = (1+len) * sizeof(uint16_t); + char *p; + for(size_t i = 0; i < len; ++i){ + t_len += strlen(strs[i]); + } + *length = headers + t_len; + p = *packed = pa_xmalloc0(*length); + *((uint16_t *) p) = (uint16_t) len; + p += sizeof(uint16_t); + for(size_t i = 0; i < len; ++i){ + uint16_t l = strlen(strs[i]); + *((uint16_t *) p) = (uint16_t) l; + p += sizeof(uint16_t); + memcpy(p, strs[i], l); + p += l; + } +} +static void unpack(char *str, size_t length, char ***strs, size_t *len){ + char *p = str; + *len = *((uint16_t *) p); + p += sizeof(uint16_t); + *strs = pa_xnew(char *, *len); + + for(size_t i = 0; i < *len; ++i){ + size_t l = *((uint16_t *) p); + p += sizeof(uint16_t); + (*strs)[i] = pa_xnew(char, l + 1); + memcpy((*strs)[i], p, l); + (*strs)[i][l] = '\0'; + p += l; + } +} +static void save_profile(struct userdata *u, size_t channel, char *name){ + unsigned a_i; + const size_t profile_size = CHANNEL_PROFILE_SIZE * sizeof(float); + float *H_n, *profile; + const float *H; + pa_datum key, data; + profile = pa_xnew0(float, profile_size); + a_i = pa_aupdate_read_begin(u->a_H[channel]); + profile[0] = u->Xs[a_i][channel]; + H = u->Hs[channel][a_i]; + H_n = profile + 1; + for(size_t i = 0 ; i <= FILTER_SIZE; ++i){ + H_n[i] = H[i] * u->fft_size; + //H_n[i] = H[i]; + } + pa_aupdate_read_end(u->a_H[channel]); + key.data=name; + key.size = strlen(key.data); + data.data = profile; + data.size = profile_size; + pa_database_set(u->database, &key, &data, TRUE); + pa_database_sync(u->database); + if(u->base_profiles[channel]){ + pa_xfree(u->base_profiles[channel]); + } + u->base_profiles[channel] = pa_xstrdup(name); +} + +static void save_state(struct userdata *u){ + unsigned a_i; + const size_t filter_state_size = FILTER_STATE_SIZE * sizeof(float); + float *H_n, *state; + float *H; + pa_datum key, data; + pa_database *database; + char *dbname; + char *state_name = u->name; + char *packed; + size_t packed_length; + + pack(u->base_profiles, u->channels, &packed, &packed_length); + state = (float *) pa_xmalloc0(filter_state_size + packed_length); + memcpy(state + FILTER_STATE_SIZE, packed, packed_length); + pa_xfree(packed); + + for(size_t c = 0; c < u->channels; ++c){ + a_i = pa_aupdate_read_begin(u->a_H[c]); + state[c * CHANNEL_PROFILE_SIZE] = u->Xs[c][a_i]; + H = u->Hs[c][a_i]; + H_n = &state[c * CHANNEL_PROFILE_SIZE + 1]; + memcpy(H_n, H, FILTER_SIZE * sizeof(float)); + pa_aupdate_read_end(u->a_H[c]); + } + + key.data = state_name; + key.size = strlen(key.data); + data.data = state; + data.size = filter_state_size + packed_length; + //thread safety for 0.9.17? + pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, FALSE)); + pa_assert_se(database = pa_database_open(dbname, TRUE)); + pa_xfree(dbname); + + pa_database_set(database, &key, &data, TRUE); + pa_database_sync(database); + pa_database_close(database); + pa_xfree(state); +} + +static void remove_profile(pa_core *c, char *name){ + pa_datum key; + pa_database *database; + key.data = name; + key.size = strlen(key.data); + pa_assert_se(database = pa_shared_get(c, EQDB)); + pa_database_unset(database, &key); + pa_database_sync(database); +} + +static const char* load_profile(struct userdata *u, size_t channel, char *name){ + unsigned a_i; + pa_datum key, value; + const size_t profile_size = CHANNEL_PROFILE_SIZE * sizeof(float); + key.data = name; + key.size = strlen(key.data); + if(pa_database_get(u->database, &key, &value) != NULL){ + if(value.size == profile_size){ + float *profile = (float *) value.data; + a_i = pa_aupdate_write_begin(u->a_H[channel]); + u->Xs[channel][a_i] = profile[0]; + memcpy(u->Hs[channel][a_i], profile + 1, FILTER_SIZE * sizeof(float)); + fix_filter(u->Hs[channel][a_i], u->fft_size); + pa_aupdate_write_end(u->a_H[channel]); + pa_xfree(u->base_profiles[channel]); + u->base_profiles[channel] = pa_xstrdup(name); + }else{ + return "incompatible size"; + } + pa_datum_free(&value); + }else{ + return "profile doesn't exist"; + } + return NULL; +} + +static void load_state(struct userdata *u){ + unsigned a_i; + float *H; + pa_datum key, value; + pa_database *database; + char *dbname; + char *state_name = u->name; + pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, FALSE)); + database = pa_database_open(dbname, FALSE); + pa_xfree(dbname); + if(!database){ + pa_log("No resume state"); + return; + } + + key.data = state_name; + key.size = strlen(key.data); + + if(pa_database_get(database, &key, &value) != NULL){ + if(value.size > FILTER_STATE_SIZE * sizeof(float) + sizeof(uint16_t)){ + float *state = (float *) value.data; + size_t n_profs; + char **names; + for(size_t c = 0; c < u->channels; ++c){ + a_i = pa_aupdate_write_begin(u->a_H[c]); + H = state + c * CHANNEL_PROFILE_SIZE + 1; + u->Xs[c][a_i] = state[c * CHANNEL_PROFILE_SIZE]; + memcpy(u->Hs[c][a_i], H, FILTER_SIZE * sizeof(float)); + pa_aupdate_write_end(u->a_H[c]); + } + unpack(((char *)value.data) + FILTER_STATE_SIZE * sizeof(float), value.size - FILTER_STATE_SIZE * sizeof(float), &names, &n_profs); + n_profs = PA_MIN(n_profs, u->channels); + for(size_t c = 0; c < n_profs; ++c){ + pa_xfree(u->base_profiles[c]); + u->base_profiles[c] = names[c]; + } + pa_xfree(names); + } + pa_datum_free(&value); + }else{ + pa_log("resume state exists but is wrong size!"); + } + pa_database_close(database); +} + +/* Called from main context */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + return u->sink != dest; +} + +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + 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); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + const char *z; + pa_sink *master; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + size_t fs; + float *H; + unsigned a_i; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK))) { + pa_log("Master sink not found, trying default"); + master = pa_namereg_get_default_sink(m->core); + if(!master){ + pa_log("no default sink found!"); + goto fail; + } + } + + ss = master->sample_spec; + ss.format = PA_SAMPLE_FLOAT32; + map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + fs = pa_frame_size(&ss); + + u = pa_xnew0(struct userdata, 1); + u->module = m; + m->userdata = u; + + u->set_default = TRUE; + pa_modargs_get_value_boolean(ma, "set_default", &u->set_default); + + u->channels = ss.channels; + u->fft_size = pow(2, ceil(log(ss.rate) / log(2)));//probably unstable near corner cases of powers of 2 + pa_log_debug("fft size: %ld", u->fft_size); + u->window_size = 15999; + if(u->window_size % 2 == 0){ + u->window_size--; + } + u->R = (u->window_size + 1) / 2; + u->overlap_size = u->window_size - u->R; + u->samples_gathered = 0; + u->input_buffer_max = 0; + u->a_H = pa_xnew0(pa_aupdate *, u->channels); + u->Xs = pa_xnew0(float *, u->channels); + u->Hs = pa_xnew0(float **, u->channels); + for(size_t c = 0; c < u->channels; ++c){ + u->Xs[c] = pa_xnew0(float, 2); + u->Hs[c] = pa_xnew0(float *, 2); + for(size_t i = 0; i < 2; ++i){ + u->Hs[c][i] = alloc(FILTER_SIZE, sizeof(float)); + } + } + u->W = alloc(u->window_size, sizeof(float)); + u->work_buffer = alloc(u->fft_size, sizeof(float)); + memset(u->work_buffer, 0, u->fft_size*sizeof(float)); + u->input = pa_xnew0(float *, u->channels); + u->overlap_accum = pa_xnew0(float *, u->channels); + for(size_t c = 0; c < u->channels; ++c){ + u->a_H[c] = pa_aupdate_new(); + u->input[c] = NULL; + u->overlap_accum[c] = alloc(u->overlap_size, sizeof(float)); + } + u->output_window = alloc((FILTER_SIZE), sizeof(fftwf_complex)); + u->forward_plan = fftwf_plan_dft_r2c_1d(u->fft_size, u->work_buffer, u->output_window, FFTW_ESTIMATE); + u->inverse_plan = fftwf_plan_dft_c2r_1d(u->fft_size, u->output_window, u->work_buffer, FFTW_ESTIMATE); + + hanning_window(u->W, u->window_size); + u->first_iteration = TRUE; + + u->base_profiles = pa_xnew0(char *, u->channels); + for(size_t c = 0; c < u->channels; ++c){ + u->base_profiles[c] = pa_xstrdup("default"); + } + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.equalizer", master->name); + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &map); + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "FFT based equalizer on %s",z? z: master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + + u->sink = pa_sink_new(m->core, &sink_data, + PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME| + (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + u->name=pa_xstrdup(u->sink->name); + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->userdata = u; + u->input_q = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, fs, 1, 1, 0, &u->sink->silence); + + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + //pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->R*fs, &ss)); + + /* Create sink input */ + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + sink_input_data.sink = master; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Equalized Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->volume_changed = sink_input_volume_changed_cb; + u->sink_input->mute_changed = sink_input_mute_changed_cb; + + u->sink_input->userdata = u; + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + + + dbus_init(u); + + //default filter to these + for(size_t c = 0; c< u->channels; ++c){ + a_i = pa_aupdate_write_begin(u->a_H[c]); + H = u->Hs[c][a_i]; + u->Xs[c][a_i] = 1.0f; + for(size_t i = 0; i < FILTER_SIZE; ++i){ + H[i] = 1.0 / sqrtf(2.0f); + } + fix_filter(H, u->fft_size); + pa_aupdate_write_end(u->a_H[c]); + } + //load old parameters + load_state(u); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + save_state(u); + + dbus_done(u); + + for(size_t c = 0; c < u->channels; ++c){ + pa_xfree(u->base_profiles[c]); + } + pa_xfree(u->base_profiles); + + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ + + if (u->sink_input) + pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) + pa_sink_input_unref(u->sink_input); + + if (u->sink) + pa_sink_unref(u->sink); + + pa_memblockq_free(u->input_q); + + fftwf_destroy_plan(u->inverse_plan); + fftwf_destroy_plan(u->forward_plan); + pa_xfree(u->output_window); + for(size_t c=0; c < u->channels; ++c){ + pa_aupdate_free(u->a_H[c]); + pa_xfree(u->overlap_accum[c]); + pa_xfree(u->input[c]); + } + pa_xfree(u->a_H); + pa_xfree(u->overlap_accum); + pa_xfree(u->input); + pa_xfree(u->work_buffer); + pa_xfree(u->W); + for(size_t c = 0; c < u->channels; ++c){ + pa_xfree(u->Xs[c]); + for(size_t i = 0; i < 2; ++i){ + pa_xfree(u->Hs[c][i]); + } + pa_xfree(u->Hs[c]); + } + pa_xfree(u->Xs); + pa_xfree(u->Hs); + + pa_xfree(u->name); + + pa_xfree(u); +} + +/* + * DBus Routines and Callbacks + */ +#define EXTNAME "org.PulseAudio.Ext.Equalizing1" +#define MANAGER_PATH "/org/pulseaudio/equalizing1" +#define MANAGER_IFACE EXTNAME ".Manager" +#define EQUALIZER_IFACE EXTNAME ".Equalizer" +static void manager_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_get_sinks(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_get_profiles(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_get_all(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_handle_remove_profile(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_filter_rate(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_n_coefs(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_n_channels(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_seed_filter(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_get_filter_points(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_get_filter(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_set_filter(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_save_profile(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_load_profile(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_save_state(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_get_profile_name(DBusConnection *conn, DBusMessage *msg, void *_u); +enum manager_method_index { + MANAGER_METHOD_REMOVE_PROFILE, + MANAGER_METHOD_MAX +}; + +pa_dbus_arg_info remove_profile_args[]={ + {"name", "s","in"}, +}; + +static pa_dbus_method_handler manager_methods[MANAGER_METHOD_MAX]={ + [MANAGER_METHOD_REMOVE_PROFILE]{ + .method_name="RemoveProfile", + .arguments=remove_profile_args, + .n_arguments=sizeof(remove_profile_args)/sizeof(pa_dbus_arg_info), + .receive_cb=manager_handle_remove_profile} +}; + +enum manager_handler_index { + MANAGER_HANDLER_REVISION, + MANAGER_HANDLER_EQUALIZED_SINKS, + MANAGER_HANDLER_PROFILES, + MANAGER_HANDLER_MAX +}; + +static pa_dbus_property_handler manager_handlers[MANAGER_HANDLER_MAX]={ + [MANAGER_HANDLER_REVISION]={.property_name="InterfaceRevision",.type="u",.get_cb=manager_get_revision,.set_cb=NULL}, + [MANAGER_HANDLER_EQUALIZED_SINKS]={.property_name="EqualizedSinks",.type="ao",.get_cb=manager_get_sinks,.set_cb=NULL}, + [MANAGER_HANDLER_PROFILES]={.property_name="Profiles",.type="as",.get_cb=manager_get_profiles,.set_cb=NULL} +}; + +pa_dbus_arg_info sink_args[]={ + {"sink", "o", NULL} +}; + +enum manager_signal_index{ + MANAGER_SIGNAL_SINK_ADDED, + MANAGER_SIGNAL_SINK_REMOVED, + MANAGER_SIGNAL_PROFILES_CHANGED, + MANAGER_SIGNAL_MAX +}; + +static pa_dbus_signal_info manager_signals[MANAGER_SIGNAL_MAX]={ + [MANAGER_SIGNAL_SINK_ADDED]={.name="SinkAdded", .arguments=sink_args, .n_arguments=sizeof(sink_args)/sizeof(pa_dbus_arg_info)}, + [MANAGER_SIGNAL_SINK_REMOVED]={.name="SinkRemoved", .arguments=sink_args, .n_arguments=sizeof(sink_args)/sizeof(pa_dbus_arg_info)}, + [MANAGER_SIGNAL_PROFILES_CHANGED]={.name="ProfilesChanged", .arguments=NULL, .n_arguments=0} +}; + +static pa_dbus_interface_info manager_info={ + .name=MANAGER_IFACE, + .method_handlers=manager_methods, + .n_method_handlers=MANAGER_METHOD_MAX, + .property_handlers=manager_handlers, + .n_property_handlers=MANAGER_HANDLER_MAX, + .get_all_properties_cb=manager_get_all, + .signals=manager_signals, + .n_signals=MANAGER_SIGNAL_MAX +}; + +enum equalizer_method_index { + EQUALIZER_METHOD_FILTER_POINTS, + EQUALIZER_METHOD_SEED_FILTER, + EQUALIZER_METHOD_SAVE_PROFILE, + EQUALIZER_METHOD_LOAD_PROFILE, + EQUALIZER_METHOD_SET_FILTER, + EQUALIZER_METHOD_GET_FILTER, + EQUALIZER_METHOD_SAVE_STATE, + EQUALIZER_METHOD_GET_PROFILE_NAME, + EQUALIZER_METHOD_MAX +}; + +enum equalizer_handler_index { + EQUALIZER_HANDLER_REVISION, + EQUALIZER_HANDLER_SAMPLERATE, + EQUALIZER_HANDLER_FILTERSAMPLERATE, + EQUALIZER_HANDLER_N_COEFS, + EQUALIZER_HANDLER_N_CHANNELS, + EQUALIZER_HANDLER_MAX +}; + +pa_dbus_arg_info filter_points_args[]={ + {"channel", "u","in"}, + {"xs", "au","in"}, + {"ys", "ad","out"}, + {"preamp", "d","out"} +}; +pa_dbus_arg_info seed_filter_args[]={ + {"channel", "u","in"}, + {"xs", "au","in"}, + {"ys", "ad","in"}, + {"preamp", "d","in"} +}; + +pa_dbus_arg_info set_filter_args[]={ + {"channel", "u","in"}, + {"ys", "ad","in"}, + {"preamp", "d","in"} +}; +pa_dbus_arg_info get_filter_args[]={ + {"channel", "u","in"}, + {"ys", "ad","out"}, + {"preamp", "d","out"} +}; + +pa_dbus_arg_info save_profile_args[]={ + {"channel", "u","in"}, + {"name", "s","in"} +}; +pa_dbus_arg_info load_profile_args[]={ + {"channel", "u","in"}, + {"name", "s","in"} +}; +pa_dbus_arg_info base_profile_name_args[]={ + {"channel", "u","in"}, + {"name", "s","out"} +}; + +static pa_dbus_method_handler equalizer_methods[EQUALIZER_METHOD_MAX]={ + [EQUALIZER_METHOD_SEED_FILTER]{ + .method_name="SeedFilter", + .arguments=seed_filter_args, + .n_arguments=sizeof(seed_filter_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_seed_filter}, + [EQUALIZER_METHOD_FILTER_POINTS]{ + .method_name="FilterAtPoints", + .arguments=filter_points_args, + .n_arguments=sizeof(filter_points_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_get_filter_points}, + [EQUALIZER_METHOD_SET_FILTER]{ + .method_name="SetFilter", + .arguments=set_filter_args, + .n_arguments=sizeof(set_filter_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_set_filter}, + [EQUALIZER_METHOD_GET_FILTER]{ + .method_name="GetFilter", + .arguments=get_filter_args, + .n_arguments=sizeof(get_filter_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_get_filter}, + [EQUALIZER_METHOD_SAVE_PROFILE]{ + .method_name="SaveProfile", + .arguments=save_profile_args, + .n_arguments=sizeof(save_profile_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_save_profile}, + [EQUALIZER_METHOD_LOAD_PROFILE]{ + .method_name="LoadProfile", + .arguments=load_profile_args, + .n_arguments=sizeof(load_profile_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_load_profile}, + [EQUALIZER_METHOD_SAVE_STATE]{ + .method_name="SaveState", + .arguments=NULL, + .n_arguments=0, + .receive_cb=equalizer_handle_save_state}, + [EQUALIZER_METHOD_GET_PROFILE_NAME]{ + .method_name="BaseProfile", + .arguments=base_profile_name_args, + .n_arguments=sizeof(base_profile_name_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_get_profile_name} +}; + +static pa_dbus_property_handler equalizer_handlers[EQUALIZER_HANDLER_MAX]={ + [EQUALIZER_HANDLER_REVISION]={.property_name="InterfaceRevision",.type="u",.get_cb=equalizer_get_revision,.set_cb=NULL}, + [EQUALIZER_HANDLER_SAMPLERATE]{.property_name="SampleRate",.type="u",.get_cb=equalizer_get_sample_rate,.set_cb=NULL}, + [EQUALIZER_HANDLER_FILTERSAMPLERATE]{.property_name="FilterSampleRate",.type="u",.get_cb=equalizer_get_filter_rate,.set_cb=NULL}, + [EQUALIZER_HANDLER_N_COEFS]{.property_name="NFilterCoefficients",.type="u",.get_cb=equalizer_get_n_coefs,.set_cb=NULL}, + [EQUALIZER_HANDLER_N_CHANNELS]{.property_name="NChannels",.type="u",.get_cb=equalizer_get_n_channels,.set_cb=NULL}, +}; + +enum equalizer_signal_index{ + EQUALIZER_SIGNAL_FILTER_CHANGED, + EQUALIZER_SIGNAL_SINK_RECONFIGURED, + EQUALIZER_SIGNAL_MAX +}; + +static pa_dbus_signal_info equalizer_signals[EQUALIZER_SIGNAL_MAX]={ + [EQUALIZER_SIGNAL_FILTER_CHANGED]={.name="FilterChanged", .arguments=NULL, .n_arguments=0}, + [EQUALIZER_SIGNAL_SINK_RECONFIGURED]={.name="SinkReconfigured", .arguments=NULL, .n_arguments=0}, +}; + +static pa_dbus_interface_info equalizer_info={ + .name=EQUALIZER_IFACE, + .method_handlers=equalizer_methods, + .n_method_handlers=EQUALIZER_METHOD_MAX, + .property_handlers=equalizer_handlers, + .n_property_handlers=EQUALIZER_HANDLER_MAX, + .get_all_properties_cb=equalizer_get_all, + .signals=equalizer_signals, + .n_signals=EQUALIZER_SIGNAL_MAX +}; + +void dbus_init(struct userdata *u){ + uint32_t dummy; + DBusMessage *signal = NULL; + pa_idxset *sink_list = NULL; + u->dbus_protocol=pa_dbus_protocol_get(u->sink->core); + u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index); + + pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &equalizer_info, u); + sink_list = pa_shared_get(u->sink->core, SINKLIST); + u->database = pa_shared_get(u->sink->core, EQDB); + if(sink_list == NULL){ + char *dbname; + sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func); + pa_shared_set(u->sink->core, SINKLIST, sink_list); + pa_assert_se(dbname = pa_state_path("equalizer-presets", FALSE)); + pa_assert_se(u->database = pa_database_open(dbname, TRUE)); + pa_xfree(dbname); + pa_shared_set(u->sink->core, EQDB, u->database); + pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core); + pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME); + } + pa_idxset_put(sink_list, u, &dummy); + + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_SINK_ADDED].name))); + dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &u->dbus_path, DBUS_TYPE_INVALID); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void dbus_done(struct userdata *u){ + pa_idxset *sink_list; + uint32_t dummy; + + DBusMessage *signal = NULL; + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_SINK_REMOVED].name))); + dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &u->dbus_path, DBUS_TYPE_INVALID); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); + + pa_assert_se(sink_list=pa_shared_get(u->sink->core,SINKLIST)); + pa_idxset_remove_by_data(sink_list,u,&dummy); + if(pa_idxset_size(sink_list)==0){ + pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME); + pa_dbus_protocol_remove_interface(u->dbus_protocol, MANAGER_PATH, manager_info.name); + pa_shared_remove(u->sink->core, EQDB); + pa_database_close(u->database); + pa_shared_remove(u->sink->core, SINKLIST); + pa_xfree(sink_list); + } + pa_dbus_protocol_remove_interface(u->dbus_protocol, u->dbus_path, equalizer_info.name); + pa_xfree(u->dbus_path); + pa_dbus_protocol_unref(u->dbus_protocol); +} + +void manager_handle_remove_profile(DBusConnection *conn, DBusMessage *msg, void *_u) { + DBusError error; + pa_core *c = (pa_core *)_u; + DBusMessage *signal = NULL; + pa_dbus_protocol *dbus_protocol; + char *name; + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + remove_profile(c,name); + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_PROFILES_CHANGED].name))); + dbus_protocol = pa_dbus_protocol_get(c); + pa_dbus_protocol_send_signal(dbus_protocol, signal); + pa_dbus_protocol_unref(dbus_protocol); + dbus_message_unref(signal); +} + +void manager_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u){ + uint32_t rev=1; + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &rev); +} + +static void get_sinks(pa_core *u, char ***names, unsigned *n_sinks){ + void *iter = NULL; + struct userdata *sink_u = NULL; + uint32_t dummy; + pa_idxset *sink_list; + pa_assert(u); + pa_assert(names); + pa_assert(n_sinks); + + pa_assert_se(sink_list = pa_shared_get(u, SINKLIST)); + *n_sinks = (unsigned) pa_idxset_size(sink_list); + *names = *n_sinks > 0 ? pa_xnew0(char *,*n_sinks) : NULL; + for(uint32_t i = 0; i < *n_sinks; ++i){ + sink_u = (struct userdata *) pa_idxset_iterate(sink_list, &iter, &dummy); + (*names)[i] = pa_xstrdup(sink_u->dbus_path); + } +} + +void manager_get_sinks(DBusConnection *conn, DBusMessage *msg, void *_u){ + unsigned n; + char **names = NULL; + pa_assert(conn); + pa_assert(msg); + pa_assert(_u); + + get_sinks((pa_core *) _u, &names, &n); + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); +} + +static void get_profiles(pa_core *c, char ***names, unsigned *n){ + char *name; + pa_database *database; + pa_datum key, next_key; + pa_strlist *head=NULL, *iter; + pa_bool_t done; + pa_assert_se(database = pa_shared_get(c, EQDB)); + + pa_assert(c); + pa_assert(names); + pa_assert(n); + done = !pa_database_first(database, &key, NULL); + *n = 0; + while(!done){ + done = !pa_database_next(database, &key, &next_key, NULL); + name=pa_xmalloc(key.size + 1); + memcpy(name, key.data, key.size); + name[key.size] = '\0'; + pa_datum_free(&key); + head = pa_strlist_prepend(head, name); + pa_xfree(name); + key = next_key; + (*n)++; + } + (*names) = *n > 0 ? pa_xnew0(char *, *n) : NULL; + iter=head; + for(unsigned i = 0; i < *n; ++i){ + (*names)[*n - 1 - i] = pa_xstrdup(pa_strlist_data(iter)); + iter = pa_strlist_next(iter); + } + pa_strlist_free(head); +} + +void manager_get_profiles(DBusConnection *conn, DBusMessage *msg, void *_u){ + char **names; + unsigned n; + pa_assert(conn); + pa_assert(msg); + pa_assert(_u); + + get_profiles((pa_core *)_u, &names, &n); + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); +} + +void manager_get_all(DBusConnection *conn, DBusMessage *msg, void *_u){ + pa_core *c; + char **names = NULL; + unsigned n; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter, dict_iter; + uint32_t rev; + pa_assert(conn); + pa_assert(msg); + pa_assert_se(c = _u); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + rev = 1; + pa_dbus_append_basic_variant_dict_entry(&dict_iter, manager_handlers[MANAGER_HANDLER_REVISION].property_name, DBUS_TYPE_UINT32, &rev); + + get_sinks(c, &names, &n); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter,manager_handlers[MANAGER_HANDLER_EQUALIZED_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); + + get_profiles(c, &names, &n); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, manager_handlers[MANAGER_HANDLER_PROFILES].property_name, DBUS_TYPE_STRING, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +void equalizer_handle_seed_filter(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u=(struct userdata *) _u; + DBusError error; + DBusMessage *signal = NULL; + float *ys; + uint32_t *xs, channel, r_channel; + double *_ys, preamp; + unsigned x_npoints, y_npoints, a_i; + float *H; + pa_bool_t points_good = TRUE; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &xs, &x_npoints, + DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &_ys, &y_npoints, + DBUS_TYPE_DOUBLE, &preamp, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + for(size_t i = 0; i < x_npoints; ++i){ + if(xs[i] >= FILTER_SIZE){ + points_good = FALSE; + break; + } + } + if(!is_monotonic(xs, x_npoints) || !points_good){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs must be monotonic and 0<=x<=%ld", u->fft_size / 2); + dbus_error_free(&error); + return; + }else if(x_npoints != y_npoints || x_npoints < 2 || x_npoints > FILTER_SIZE ){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs and ys must be the same length and 2<=l<=%ld!", FILTER_SIZE); + dbus_error_free(&error); + return; + }else if(xs[0] != 0 || xs[x_npoints - 1] != u->fft_size / 2){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs[0] must be 0 and xs[-1]=fft_size/2"); + dbus_error_free(&error); + return; + } + + ys = pa_xmalloc(x_npoints * sizeof(float)); + for(uint32_t i = 0; i < x_npoints; ++i){ + ys[i] = (float) _ys[i]; + } + r_channel = channel == u->channels ? 0 : channel; + a_i = pa_aupdate_write_begin(u->a_H[r_channel]); + H = u->Hs[r_channel][a_i]; + u->Xs[r_channel][a_i] = preamp; + interpolate(H, FILTER_SIZE, xs, ys, x_npoints); + fix_filter(H, u->fft_size); + if(channel == u->channels){ + for(size_t c = 1; c < u->channels; ++c){ + unsigned b_i = pa_aupdate_write_begin(u->a_H[c]); + float *H_p = u->Hs[c][b_i]; + u->Xs[c][b_i] = preamp; + memcpy(H_p, H, FILTER_SIZE * sizeof(float)); + pa_aupdate_write_end(u->a_H[c]); + } + } + pa_aupdate_write_end(u->a_H[r_channel]); + pa_xfree(ys); + + + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_get_filter_points(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + uint32_t *xs, channel, r_channel; + double *ys, preamp; + unsigned x_npoints, a_i; + float *H; + pa_bool_t points_good=TRUE; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusError error; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &xs, &x_npoints, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + + for(size_t i = 0; i < x_npoints; ++i){ + if(xs[i] >= FILTER_SIZE){ + points_good=FALSE; + break; + } + } + + if(x_npoints > FILTER_SIZE || !points_good){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs indices/length must be <= %ld!", FILTER_SIZE); + dbus_error_free(&error); + return; + } + + r_channel = channel == u->channels ? 0 : channel; + ys = pa_xmalloc(x_npoints * sizeof(double)); + a_i = pa_aupdate_read_begin(u->a_H[r_channel]); + H = u->Hs[r_channel][a_i]; + preamp = u->Xs[r_channel][a_i]; + for(uint32_t i = 0; i < x_npoints; ++i){ + ys[i] = H[xs[i]] * u->fft_size; + } + pa_aupdate_read_end(u->a_H[r_channel]); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + + pa_dbus_append_basic_array(&msg_iter, DBUS_TYPE_DOUBLE, ys, x_npoints); + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_DOUBLE, &preamp); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + pa_xfree(ys); +} + +static void get_filter(struct userdata *u, size_t channel, double **H_, double *preamp){ + float *H; + unsigned a_i; + size_t r_channel = channel == u->channels ? 0 : channel; + *H_ = pa_xnew0(double, FILTER_SIZE); + a_i = pa_aupdate_read_begin(u->a_H[r_channel]); + H = u->Hs[r_channel][a_i]; + for(size_t i = 0;i < FILTER_SIZE; ++i){ + (*H_)[i] = H[i] * u->fft_size; + } + *preamp = u->Xs[r_channel][a_i]; + + pa_aupdate_read_end(u->a_H[r_channel]); +} + +void equalizer_handle_get_filter(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + unsigned n_coefs; + uint32_t channel; + double *H_, preamp; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusError error; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + + n_coefs = CHANNEL_PROFILE_SIZE; + pa_assert(conn); + pa_assert(msg); + get_filter(u, channel, &H_, &preamp); + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + + pa_dbus_append_basic_array(&msg_iter, DBUS_TYPE_DOUBLE, H_, n_coefs); + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_DOUBLE, &preamp); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + pa_xfree(H_); +} + +static void set_filter(struct userdata *u, size_t channel, double *H_, double preamp){ + unsigned a_i; + size_t r_channel = channel == u->channels ? 0 : channel; + float *H; + //all channels + a_i = pa_aupdate_write_begin(u->a_H[r_channel]); + u->Xs[r_channel][a_i] = (float) preamp; + H = u->Hs[r_channel][a_i]; + for(size_t i = 0; i < FILTER_SIZE; ++i){ + H[i] = (float) H_[i]; + } + fix_filter(H, u->fft_size); + if(channel == u->channels){ + for(size_t c = 1; c < u->channels; ++c){ + unsigned b_i = pa_aupdate_write_begin(u->a_H[c]); + u->Xs[c][b_i] = u->Xs[r_channel][a_i]; + memcpy(u->Hs[c][b_i], u->Hs[r_channel][a_i], FILTER_SIZE * sizeof(float)); + pa_aupdate_write_end(u->a_H[c]); + } + } + pa_aupdate_write_end(u->a_H[r_channel]); +} + +void equalizer_handle_set_filter(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + double *H, preamp; + uint32_t channel; + unsigned _n_coefs; + DBusMessage *signal = NULL; + DBusError error; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &H, &_n_coefs, + DBUS_TYPE_DOUBLE, &preamp, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + if(_n_coefs != FILTER_SIZE){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "This filter takes exactly %ld coefficients, you gave %d", FILTER_SIZE, _n_coefs); + return; + } + set_filter(u, channel, H, preamp); + + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_save_profile(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + char *name; + uint32_t channel, r_channel; + DBusMessage *signal = NULL; + DBusError error; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + r_channel = channel == u->channels ? 0 : channel; + save_profile(u, r_channel, name); + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_PROFILES_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_load_profile(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + char *name; + DBusError error; + uint32_t channel, r_channel; + const char *err_msg = NULL; + DBusMessage *signal = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + r_channel = channel == u->channels ? 0 : channel; + + err_msg = load_profile(u, r_channel, name); + if(err_msg != NULL){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "error loading profile %s: %s", name, err_msg); + dbus_error_free(&error); + return; + } + if(channel == u->channels){ + for(uint32_t c = 1; c < u->channels; ++c){ + load_profile(u, c, name); + } + } + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_save_state(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + save_state(u); + pa_dbus_send_empty_reply(conn, msg); +} + +void equalizer_handle_get_profile_name(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u = (struct userdata *) _u; + DBusError error; + uint32_t channel, r_channel; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + r_channel = channel == u->channels ? 0 : channel; + pa_assert(u->base_profiles[r_channel]); + pa_dbus_send_basic_value_reply(conn,msg, DBUS_TYPE_STRING, &u->base_profiles[r_channel]); +} + +void equalizer_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u){ + uint32_t rev=1; + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &rev); +} + +void equalizer_get_n_channels(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t channels; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + channels = (uint32_t) u->channels; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &channels); +} + +void equalizer_get_n_coefs(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t n_coefs; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &n_coefs); +} + +void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t rate; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + rate = (uint32_t) u->sink->sample_spec.rate; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &rate); +} + +void equalizer_get_filter_rate(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t fft_size; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + fft_size = (uint32_t) u->fft_size; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &fft_size); +} + +void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter, dict_iter; + uint32_t rev, n_coefs, rate, fft_size, channels; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(msg); + + rev = 1; + n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE; + rate = (uint32_t) u->sink->sample_spec.rate; + fft_size = (uint32_t) u->fft_size; + channels = (uint32_t) u->channels; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_REVISION].property_name, DBUS_TYPE_UINT32, &rev); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_SAMPLERATE].property_name, DBUS_TYPE_UINT32, &rate); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_FILTERSAMPLERATE].property_name, DBUS_TYPE_UINT32, &fft_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_N_COEFS].property_name, DBUS_TYPE_UINT32, &n_coefs); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_N_CHANNELS].property_name, DBUS_TYPE_UINT32, &channels); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} diff --git a/src/modules/module-hal-detect-compat.c b/src/modules/module-hal-detect-compat.c new file mode 100644 index 00000000..14cf8143 --- /dev/null +++ b/src/modules/module-hal-detect-compat.c @@ -0,0 +1,84 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include "module-hal-detect-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Compatibility module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!"); + +static const char* const valid_modargs[] = { + "api", + "tsched", + "subdevices", + NULL, +}; + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + pa_bool_t tsched = TRUE; + pa_module *n; + char *t; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "tsched", &tsched) < 0) { + pa_log("tsched= expects boolean arguments"); + goto fail; + } + + pa_log_warn("We will now load module-udev-detect. Please make sure to remove module-hal-detect from your configuration."); + + t = pa_sprintf_malloc("tsched=%s", pa_yes_no(tsched)); + n = pa_module_load(m->core, "module-udev-detect", t); + pa_xfree(t); + + if (n) + pa_module_unload_request(m, TRUE); + + pa_modargs_free(ma); + + return n ? 0 : -1; + +fail: + if (ma) + pa_modargs_free(ma); + + return -1; +} diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index 79758b92..18519131 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -55,16 +55,16 @@ PA_MODULE_AUTHOR("Shahms King"); PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -#if defined(HAVE_ALSA) && defined(HAVE_OSS) +#if defined(HAVE_ALSA) && defined(HAVE_OSS_OUTPUT) PA_MODULE_USAGE("api=<alsa or oss> " "tsched=<enable system timer based scheduling mode?>" - "subdevs=<init all subdevices>"); + "subdevices=<init all subdevices>"); #elif defined(HAVE_ALSA) PA_MODULE_USAGE("api=<alsa> " "tsched=<enable system timer based scheduling mode?>"); -#elif defined(HAVE_OSS) +#elif defined(HAVE_OSS_OUTPUT) PA_MODULE_USAGE("api=<oss>" - "subdevs=<init all subdevices>"); + "subdevices=<init all subdevices>"); #endif PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!"); @@ -84,7 +84,7 @@ struct userdata { #ifdef HAVE_ALSA pa_bool_t use_tsched; #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT pa_bool_t init_subdevs; #endif }; @@ -97,8 +97,8 @@ static const char* const valid_modargs[] = { #ifdef HAVE_ALSA "tsched", #endif -#ifdef HAVE_OSS - "subdevs", +#ifdef HAVE_OSS_OUTPUT + "subdevices", #endif NULL }; @@ -270,7 +270,7 @@ fail: #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi, pa_bool_t init_subdevices) { char *class = NULL, *dev = NULL, *e; @@ -402,7 +402,7 @@ static struct device* hal_device_add(struct userdata *u, const char *udi) { if (pa_streq(u->capability, CAPABILITY_ALSA)) r = hal_device_load_alsa(u, udi, d); #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT if (pa_streq(u->capability, CAPABILITY_OSS)) r = hal_device_load_oss(u, udi, d); #endif @@ -435,9 +435,7 @@ static int hal_device_add_all(struct userdata *u) { int i; for (i = 0; i < n; i++) { - struct device *d; - - if ((d = hal_device_add(u, udis[i]))) { + if (hal_device_add(u, udis[i])) { count++; pa_log_debug("Loaded device %s", udis[i]); } else @@ -623,8 +621,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo } - return DBUS_HANDLER_RESULT_HANDLED; - } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) { /* We use this message to avoid a dirty race condition when we get an ACLAdded message before the previously owning PA @@ -668,7 +664,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo /* Yes, we don't check the UDI for validity, but hopefully HAL will */ device_added_cb(u->context, udi); - return DBUS_HANDLER_RESULT_HANDLED; } finish: @@ -761,7 +756,7 @@ int pa__init(pa_module*m) { api = pa_modargs_get_value(ma, "api", "oss"); #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT if (pa_streq(api, "oss")) u->capability = CAPABILITY_OSS; #endif @@ -771,9 +766,9 @@ int pa__init(pa_module*m) { goto fail; } -#ifdef HAVE_OSS - if (pa_modargs_get_value_boolean(ma, "subdevs", &u->init_subdevs) < 0) { - pa_log("Failed to parse subdevs argument."); +#ifdef HAVE_OSS_OUTPUT + if (pa_modargs_get_value_boolean(ma, "subdevices", &u->init_subdevs) < 0) { + pa_log("Failed to parse subdevices= argument."); goto fail; } #endif diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c index c697209a..b9924dfd 100644 --- a/src/modules/module-intended-roles.c +++ b/src/modules/module-intended-roles.c @@ -127,6 +127,9 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n if (s == def) continue; + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s))) + continue; + if (role_match(s->proplist, role)) { new_data->sink = s; new_data->save_sink = FALSE; @@ -173,6 +176,9 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou if (s == def) continue; + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s))) + continue; + if (role_match(s->proplist, role)) { new_data->source = s; new_data->save_source = FALSE; @@ -201,6 +207,17 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct if (si->save_sink) continue; + /* Skip this if it is already in the process of being moved + * anyway */ + if (!si->sink) + continue; + + /* 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 */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + continue; + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) continue; @@ -237,6 +254,17 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, if (so->direct_on_input) continue; + /* Skip this if it is already in the process of being moved + * anyway */ + if (!so->source) + continue; + + /* 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 */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + continue; + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) continue; @@ -275,24 +303,28 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str uint32_t jdx; pa_sink *d; + if (!si->sink) + continue; + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) continue; /* Would the default sink fit? If so, let's use it */ - if (def != sink && role_match(def->proplist, role)) { - pa_sink_input_move_to(si, def, FALSE); - continue; - } + if (def != sink && role_match(def->proplist, role)) + if (pa_sink_input_move_to(si, def, FALSE) >= 0) + continue; /* Try to find some other fitting sink */ PA_IDXSET_FOREACH(d, c->sinks, jdx) { if (d == def || d == sink) continue; - if (role_match(d->proplist, role)) { - pa_sink_input_move_to(si, d, FALSE); - break; - } + if (!PA_SINK_IS_LINKED(pa_sink_get_state(d))) + continue; + + if (role_match(d->proplist, role)) + if (pa_sink_input_move_to(si, d, FALSE) >= 0) + break; } } @@ -325,6 +357,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc if (so->direct_on_input) continue; + if (!so->source) + continue; + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) continue; @@ -339,6 +374,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc if (d == def || d == source) continue; + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(d))) + continue; + if (role_match(d->proplist, role) && !source->monitor_of == !d->monitor_of) { pa_source_output_move_to(so, d, FALSE); break; diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 21f4a8f1..994c778f 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -64,10 +64,9 @@ PA_MODULE_USAGE( #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) struct userdata { - pa_core *core; pa_module *module; - pa_sink *sink, *master; + pa_sink *sink; pa_sink_input *sink_input; const LADSPA_Descriptor *descriptor; @@ -83,6 +82,8 @@ struct userdata { LADSPA_Data control_out; pa_memblockq *memblockq; + + pa_bool_t auto_desc; }; static const char* const valid_modargs[] = { @@ -100,69 +101,111 @@ static const char* const valid_modargs[] = { }; /* Called from I/O thread context */ -static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; switch (code) { - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t usec = 0; + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = - /* Get the latency of the master sink */ - if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - usec = 0; + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + - /* Add the latency internal to our sink input on top */ - usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - *((pa_usec_t*) data) = usec; return 0; - } } return pa_sink_process_msg(o, code, data, offset, chunk); } /* Called from main context */ -static int sink_set_state(pa_sink *s, pa_sink_state_t state) { +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - if (PA_SINK_IS_LINKED(state) && - u->sink_input && - PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) - - pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; } /* Called from I/O thread context */ -static void sink_request_rewind(pa_sink *s) { +static void sink_request_rewind_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + /* Just hand this one over to the master sink */ pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE); } /* Called from I/O thread context */ -static void sink_update_requested_latency(pa_sink *s) { +static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + /* Just hand this one over to the master sink */ pa_sink_input_set_requested_latency_within_thread( u->sink_input, pa_sink_get_requested_latency_within_thread(s)); } +/* Called from main context */ +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void sink_set_mute_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); +} + /* Called from I/O thread context */ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { struct userdata *u; @@ -175,8 +218,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return -1; + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { pa_memchunk nchunk; @@ -225,9 +268,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return; - if (u->sink->thread_info.rewind_nbytes > 0) { size_t max_rewrite; @@ -263,9 +303,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_memblockq_set_maxrewind(u->memblockq, nbytes); pa_sink_set_max_rewind_within_thread(u->sink, nbytes); } @@ -277,9 +314,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_max_request_within_thread(u->sink, nbytes); } @@ -290,24 +324,28 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); } /* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); pa_sink_detach_within_thread(u->sink); - pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); } @@ -318,14 +356,13 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); - pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); - pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); pa_sink_attach_within_thread(u->sink); - - pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); } /* Called from main context */ @@ -335,14 +372,18 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_sink_unlink(u->sink); + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; pa_sink_input_unref(u->sink_input); u->sink_input = NULL; + pa_sink_unref(u->sink); + u->sink = NULL; + pa_module_unload_request(u->module, TRUE); } @@ -372,13 +413,59 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { return u->sink != dest; } +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + 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); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); + + if (u->auto_desc && dest) { + const char *z; + pa_proplist *pl; + + pl = pa_proplist_new(); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", + pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_volume_changed(u->sink, &i->volume); +} + +/* Called from main context */ +static void sink_input_mute_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_mute_changed(u->sink, i->muted); +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; char *t; - const char *z; pa_sink *master; pa_sink_input_new_data sink_input_data; pa_sink_new_data sink_data; @@ -392,7 +479,7 @@ int pa__init(pa_module*m) { pa_assert(m); - pa_assert(sizeof(LADSPA_Data) == sizeof(float)); + pa_assert_cc(sizeof(LADSPA_Data) == sizeof(float)); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments."); @@ -425,12 +512,8 @@ int pa__init(pa_module*m) { cdata = pa_modargs_get_value(ma, "control", NULL); u = pa_xnew0(struct userdata, 1); - u->core = m->core; u->module = m; m->userdata = u; - u->master = master; - u->sink = NULL; - u->sink_input = NULL; u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); if (!(e = getenv("LADSPA_PATH"))) @@ -694,11 +777,8 @@ int pa__init(pa_module*m) { sink_data.module = m; if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name); - sink_data.namereg_fail = FALSE; pa_sink_new_data_set_sample_spec(&sink_data, &ss); pa_sink_new_data_set_channel_map(&sink_data, &map); - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin); @@ -714,7 +794,16 @@ int pa__init(pa_module*m) { goto fail; } - u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); + if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name); + } + + u->sink = pa_sink_new(m->core, &sink_data, + PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME| + (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))); pa_sink_new_data_done(&sink_data); if (!u->sink) { @@ -722,26 +811,27 @@ int pa__init(pa_module*m) { goto fail; } - u->sink->parent.process_msg = sink_process_msg; - u->sink->set_state = sink_set_state; - u->sink->update_requested_latency = sink_update_requested_latency; - u->sink->request_rewind = sink_request_rewind; + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->set_mute = sink_set_mute_cb; u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - pa_sink_set_rtpoll(u->sink, master->rtpoll); /* Create sink input */ pa_sink_input_new_data_init(&sink_input_data); sink_input_data.driver = __FILE__; sink_input_data.module = m; - sink_input_data.sink = u->master; + sink_input_data.sink = master; pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream"); pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); - pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, 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); if (!u->sink_input) @@ -752,11 +842,15 @@ int pa__init(pa_module*m) { u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; u->sink_input->update_max_request = sink_input_update_max_request_cb; u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; u->sink_input->kill = sink_input_kill_cb; u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->volume_changed = sink_input_volume_changed_cb; + u->sink_input->mute_changed = sink_input_mute_changed_cb; u->sink_input->userdata = u; pa_sink_put(u->sink); @@ -797,15 +891,20 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink) { - pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - } + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ - if (u->sink_input) { + if (u->sink_input) pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) pa_sink_input_unref(u->sink_input); - } + + if (u->sink) + pa_sink_unref(u->sink); for (c = 0; c < u->channels; c++) if (u->handle[c]) { diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index 06efeb8f..e9778620 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -45,12 +45,14 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("LIRC volume control"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>"); +PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name> volume_limit=<volume limit> volume_step=<volume change step>"); static const char* const valid_modargs[] = { "config", "sink", "appname", + "volume_limit", + "volume_step", NULL, }; @@ -61,6 +63,8 @@ struct userdata { char *sink_name; pa_module *module; float mute_toggle_save; + pa_volume_t volume_limit; + pa_volume_t volume_step; }; static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { @@ -119,32 +123,17 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) pa_log("Failed to get sink '%s'", u->sink_name); else { - int i; - pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE); - -#define DELTA (PA_VOLUME_NORM/20) + pa_cvolume cv = *pa_sink_get_volume(s, FALSE); switch (volchange) { case UP: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] < PA_VOLUME_MAX - DELTA) - cv.values[i] += DELTA; - else - cv.values[i] = PA_VOLUME_MAX; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE); + pa_cvolume_inc_clamp(&cv, u->volume_step, u->volume_limit); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case DOWN: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] > DELTA) - cv.values[i] -= DELTA; - else - cv.values[i] = PA_VOLUME_MUTED; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE); + pa_cvolume_dec(&cv, u->volume_step); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case MUTE: @@ -156,7 +145,6 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event break; case MUTE_TOGGLE: - pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE); break; @@ -184,6 +172,8 @@ fail: int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; + pa_volume_t volume_limit = PA_VOLUME_NORM*3/2; + pa_volume_t volume_step = PA_VOLUME_NORM/20; pa_assert(m); @@ -192,6 +182,16 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_modargs_get_value_u32(ma, "volume_limit", &volume_limit) < 0) { + pa_log("Failed to parse volume limit"); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "volume_step", &volume_step) < 0) { + pa_log("Failed to parse volume step"); + goto fail; + } + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->io = NULL; @@ -199,6 +199,8 @@ int pa__init(pa_module*m) { u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->lirc_fd = -1; u->mute_toggle_save = 0; + u->volume_limit = volume_limit; + u->volume_step = volume_step; if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "pulseaudio"), 1)) < 0) { pa_log("lirc_init() failed."); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c new file mode 100644 index 00000000..bb0182b0 --- /dev/null +++ b/src/modules/module-loopback.c @@ -0,0 +1,784 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Intel Corporation + Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <math.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> + +#include "module-loopback-symdef.h" + +PA_MODULE_AUTHOR("Pierre-Louis Bossart"); +PA_MODULE_DESCRIPTION("Loopback from source to sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "source=<source to connect to> " + "sink=<sink to connect to> " + "adjust_time=<how often to readjust rates in s> " + "latency_msec=<latency in ms> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map>"); + +#define DEFAULT_LATENCY_MSEC 200 + +#define MEMBLOCKQ_MAXLENGTH (1024*1024*16) + +#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC) + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink_input *sink_input; + pa_source_output *source_output; + + pa_asyncmsgq *asyncmsgq; + pa_memblockq *memblockq; + + pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write; + + pa_time_event *time_event; + pa_usec_t adjust_time; + + int64_t recv_counter; + int64_t send_counter; + + size_t skip; + pa_usec_t latency; + + pa_bool_t in_pop; + size_t min_memblockq_length; + + struct { + int64_t send_counter; + size_t source_output_buffer; + pa_usec_t source_latency; + + int64_t recv_counter; + size_t sink_input_buffer; + pa_usec_t sink_latency; + + size_t min_memblockq_length; + size_t max_request; + } latency_snapshot; +}; + +static const char* const valid_modargs[] = { + "source", + "sink", + "latency_msec", + "format", + "rate", + "channels", + "channel_map", + NULL, +}; + +enum { + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, + SINK_INPUT_MESSAGE_REWIND, + SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, + SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED +}; + +enum { + SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT +}; + +/* Called from main context */ +static void teardown(struct userdata *u) { + pa_assert(u); + pa_assert_ctl_context(); + + if (u->sink_input) + pa_sink_input_unlink(u->sink_input); + + if (u->source_output) + pa_source_output_unlink(u->source_output); + + if (u->sink_input) { + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + } + + if (u->source_output) { + pa_source_output_unref(u->source_output); + u->source_output = NULL; + } +} + +/* Called from main context */ +static void adjust_rates(struct userdata *u) { + size_t buffer, fs; + uint32_t old_rate, base_rate, new_rate; + pa_usec_t buffer_latency; + + pa_assert(u); + pa_assert_ctl_context(); + + pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL); + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL); + + buffer = + u->latency_snapshot.sink_input_buffer + + u->latency_snapshot.source_output_buffer; + + if (u->latency_snapshot.recv_counter <= u->latency_snapshot.send_counter) + buffer += (size_t) (u->latency_snapshot.send_counter - u->latency_snapshot.recv_counter); + else + buffer += PA_CLIP_SUB(buffer, (size_t) (u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter)); + + buffer_latency = pa_bytes_to_usec(buffer, &u->sink_input->sample_spec); + + pa_log_info("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms", + (double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC, + (double) buffer_latency / PA_USEC_PER_MSEC, + (double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC, + ((double) u->latency_snapshot.sink_latency + buffer_latency + u->latency_snapshot.source_latency) / PA_USEC_PER_MSEC); + + pa_log_info("Should buffer %zu bytes, buffered at minimum %zu bytes", + u->latency_snapshot.max_request*2, + u->latency_snapshot.min_memblockq_length); + + fs = pa_frame_size(&u->sink_input->sample_spec); + old_rate = u->sink_input->sample_spec.rate; + base_rate = u->source_output->sample_spec.rate; + + if (u->latency_snapshot.min_memblockq_length < u->latency_snapshot.max_request*2) + new_rate = base_rate - (((u->latency_snapshot.max_request*2 - u->latency_snapshot.min_memblockq_length) / fs) *PA_USEC_PER_SEC)/u->adjust_time; + else + new_rate = base_rate + (((u->latency_snapshot.min_memblockq_length - u->latency_snapshot.max_request*2) / fs) *PA_USEC_PER_SEC)/u->adjust_time; + + pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate); + + pa_sink_input_set_rate(u->sink_input, new_rate); + + pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time); +} + +/* Called from main context */ +static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); + + adjust_rates(u); +} + +/* Called from input thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct userdata *u; + pa_memchunk copy; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (u->skip > chunk->length) { + u->skip -= chunk->length; + return; + } + + if (u->skip > 0) { + copy = *chunk; + copy.index += u->skip; + copy.length -= u->skip; + u->skip = 0; + + chunk = © + } + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, chunk, NULL); + u->send_counter += (int64_t) chunk->length; +} + +/* Called from input thread context */ +static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL); + u->send_counter -= (int64_t) nbytes; +} + +/* Called from output thread context */ +static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata; + + switch (code) { + + case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t length; + + length = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq); + + u->latency_snapshot.send_counter = u->send_counter; + u->latency_snapshot.source_output_buffer = u->source_output->thread_info.resampler ? pa_resampler_result(u->source_output->thread_info.resampler, length) : length; + u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source); + + return 0; + } + } + + return pa_source_output_process_msg(obj, code, data, offset, chunk); +} + +/* Called from output thread context */ +static void source_output_attach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + o->source->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); +} + +/* Called from output thread context */ +static void source_output_detach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (u->rtpoll_item_write) { + pa_rtpoll_item_free(u->rtpoll_item_write); + u->rtpoll_item_write = NULL; + } +} + +/* Called from output thread context */ +static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT) { + + u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source), + u->latency), + &o->sample_spec); + + pa_log_info("Skipping %lu bytes", (unsigned long) u->skip); + } +} + +/* Called from main thread */ +static void source_output_kill_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + teardown(u); + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main thread */ +static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + return dest != u->sink_input->sink->monitor_source; +} + +/* 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; + + pa_source_output_assert_ref(o); + 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))); + + 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); +} + +/* Called from output thread context */ +static void update_min_memblockq_length(struct userdata *u) { + size_t length; + + pa_assert(u); + pa_sink_input_assert_io_context(u->sink_input); + + length = pa_memblockq_get_length(u->memblockq); + + if (u->min_memblockq_length == (size_t) -1 || + length < u->min_memblockq_length) + u->min_memblockq_length = length; +} + +/* Called from output thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + pa_assert(chunk); + + u->in_pop = TRUE; + while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0) + ; + u->in_pop = FALSE; + + if (pa_memblockq_peek(u->memblockq, chunk) < 0) { + pa_log_info("Coud not peek into queue"); + return -1; + } + + chunk->length = PA_MIN(chunk->length, nbytes); + pa_memblockq_drop(u->memblockq, chunk->length); + + update_min_memblockq_length(u); + + return 0; +} + +/* Called from output thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +/* Called from output thread context */ +static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(obj)->userdata; + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = data; + + pa_sink_input_assert_io_context(u->sink_input); + + *r = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_POST: + + pa_sink_input_assert_io_context(u->sink_input); + + if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state)) + pa_memblockq_push_align(u->memblockq, chunk); + else + pa_memblockq_flush_write(u->memblockq); + + update_min_memblockq_length(u); + + /* Is this the end of an underrun? Then let's start things + * right-away */ + if (!u->in_pop && + u->sink_input->thread_info.underrun_for > 0 && + pa_memblockq_is_readable(u->memblockq)) { + + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(u->sink_input, + (size_t) (u->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : u->sink_input->thread_info.underrun_for), + FALSE, TRUE, FALSE); + } + + u->recv_counter += (int64_t) chunk->length; + + return 0; + + case SINK_INPUT_MESSAGE_REWIND: + + pa_sink_input_assert_io_context(u->sink_input); + + if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state)) + pa_memblockq_seek(u->memblockq, -offset, PA_SEEK_RELATIVE, TRUE); + else + pa_memblockq_flush_write(u->memblockq); + + u->recv_counter -= offset; + + update_min_memblockq_length(u); + + return 0; + + case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t length; + + update_min_memblockq_length(u); + + length = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq); + + u->latency_snapshot.recv_counter = u->recv_counter; + u->latency_snapshot.sink_input_buffer = + pa_memblockq_get_length(u->memblockq) + + (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, length) : length); + u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink); + + u->latency_snapshot.max_request = pa_sink_input_get_max_request(u->sink_input); + + u->latency_snapshot.min_memblockq_length = u->min_memblockq_length; + u->min_memblockq_length = (size_t) -1; + + return 0; + } + + case SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED: { + /* This message is sent from the IO thread to the main + * thread! So don't be confused. All the user cases above + * are executed in thread context, but this one is not! */ + + pa_assert_ctl_context(); + + adjust_rates(u); + return 0; + } + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); +} + +/* Called from output thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + i->sink->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); + + pa_memblockq_set_prebuf(u->memblockq, pa_sink_input_get_max_request(i)*2); + pa_memblockq_set_maxrewind(u->memblockq, pa_sink_input_get_max_rewind(i)); + + u->min_memblockq_length = (size_t) -1; +} + +/* Called from output thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + if (u->rtpoll_item_read) { + pa_rtpoll_item_free(u->rtpoll_item_read); + u->rtpoll_item_read = NULL; + } +} + +/* Called from output thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); +} + +/* Called from output thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_prebuf(u->memblockq, nbytes*2); + pa_log_info("Max request changed"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED, NULL, 0, NULL, NULL); +} + +/* Called from main thread */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + teardown(u); + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main thread */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + pa_proplist *p; + const char *n; + + pa_sink_input_assert_ref(i); + 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))); + + 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); +} + +/* Called from main thread */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + if (!u->source_output->source->monitor_of) + return TRUE; + + return dest != u->source_output->source->monitor_of; +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u; + pa_sink *sink; + pa_sink_input_new_data sink_input_data; + pa_source *source; + pa_source_output_new_data source_output_data; + uint32_t latency_msec; + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk silence; + uint32_t adjust_time_sec; + const char *n; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!(source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE))) { + pa_log("No such source."); + goto fail; + } + + if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK))) { + pa_log("No such sink."); + goto fail; + } + + ss = sink->sample_spec; + map = sink->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + latency_msec = DEFAULT_LATENCY_MSEC; + if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec < 1 || latency_msec > 2000) { + pa_log("Invalid latency specification"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC; + + adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; + if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { + pa_log("Failed to parse adjust_time value"); + goto fail; + } + + if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC) + u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC; + else + u->adjust_time = DEFAULT_ADJUST_TIME_USEC; + + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + sink_input_data.sink = sink; + + pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Loopback of %s", + pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + if ((n = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ICON_NAME, n); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE; + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->userdata = u; + + pa_sink_input_set_requested_latency(u->sink_input, u->latency/3); + + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + source_output_data.source = source; + pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Loopback to %s", + pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + if ((n = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ICON_NAME, n); + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + + pa_source_output_new(&u->source_output, m->core, &source_output_data); + pa_source_output_new_data_done(&source_output_data); + + if (!u->source_output) + goto fail; + + u->source_output->parent.process_msg = source_output_process_msg_cb; + u->source_output->push = source_output_push_cb; + u->source_output->process_rewind = source_output_process_rewind_cb; + u->source_output->kill = source_output_kill_cb; + u->source_output->attach = source_output_attach_cb; + u->source_output->detach = source_output_detach_cb; + u->source_output->state_change = source_output_state_change_cb; + u->source_output->may_move_to = source_output_may_move_to_cb; + u->source_output->moving = source_output_moving_cb; + u->source_output->userdata = u; + + pa_source_output_set_requested_latency(u->source_output, u->latency/3); + + pa_sink_input_get_silence(u->sink_input, &silence); + u->memblockq = pa_memblockq_new( + 0, /* idx */ + MEMBLOCKQ_MAXLENGTH, /* maxlength */ + MEMBLOCKQ_MAXLENGTH, /* tlength */ + pa_frame_size(&ss), /* base */ + 0, /* prebuf */ + 0, /* minreq */ + 0, /* maxrewind */ + &silence); /* silence frame */ + pa_memblock_unref(silence.memblock); + + u->asyncmsgq = pa_asyncmsgq_new(0); + + pa_sink_input_put(u->sink_input); + pa_source_output_put(u->source_output); + + if (u->adjust_time > 0) + u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u); + + pa_modargs_free(ma); + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + teardown(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if (u->asyncmsgq) + pa_asyncmsgq_unref(u->asyncmsgq); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + pa_xfree(u); +} diff --git a/src/modules/module-match.c b/src/modules/module-match.c index 625f2a8b..b1693f18 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -85,7 +85,7 @@ static int load_rules(struct userdata *u, const char *filename) { pa_assert(u); if (filename) - f = fopen(fn = pa_xstrdup(filename), "r"); + f = pa_fopen_cloexec(fn = pa_xstrdup(filename), "r"); else f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn); @@ -216,7 +216,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v pa_cvolume cv; pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume); pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); - pa_sink_input_set_volume(si, &cv, TRUE, TRUE); + pa_sink_input_set_volume(si, &cv, TRUE, FALSE); } } } @@ -243,6 +243,9 @@ int pa__init(pa_module*m) { if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0) goto fail; + /* FIXME: Doing this asynchronously is just broken. This needs to + * use a hook! */ + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); pa_modargs_free(ma); diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index b30fae51..193c1f40 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -48,13 +48,15 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); -PA_MODULE_USAGE("device=<evdev device> sink=<sink name>"); +PA_MODULE_USAGE("device=<evdev device> sink=<sink name> volume_limit=<volume limit> volume_step=<volume change step>"); #define DEFAULT_DEVICE "/dev/input/event0" static const char* const valid_modargs[] = { "device", "sink", + "volume_limit", + "volume_step", NULL, }; @@ -63,6 +65,8 @@ struct userdata { pa_io_event *io; char *sink_name; pa_module *module; + pa_volume_t volume_limit; + pa_volume_t volume_step; }; static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { @@ -85,14 +89,27 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event } if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) { - enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID; + enum { + INVALID, + UP, + DOWN, + MUTE_TOGGLE + } volchange = INVALID; pa_log_debug("Key code=%u, value=%u", ev.code, ev.value); switch (ev.code) { - case KEY_VOLUMEDOWN: volchange = DOWN; break; - case KEY_VOLUMEUP: volchange = UP; break; - case KEY_MUTE: volchange = MUTE_TOGGLE; break; + case KEY_VOLUMEDOWN: + volchange = DOWN; + break; + + case KEY_VOLUMEUP: + volchange = UP; + break; + + case KEY_MUTE: + volchange = MUTE_TOGGLE; + break; } if (volchange != INVALID) { @@ -101,36 +118,20 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) pa_log("Failed to get sink '%s'", u->sink_name); else { - int i; - pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE); - -#define DELTA (PA_VOLUME_NORM/20) + pa_cvolume cv = *pa_sink_get_volume(s, FALSE); switch (volchange) { case UP: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] < PA_VOLUME_MAX - DELTA) - cv.values[i] += DELTA; - else - cv.values[i] = PA_VOLUME_MAX; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE); + pa_cvolume_inc_clamp(&cv, u->volume_step, u->volume_limit); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case DOWN: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] > DELTA) - cv.values[i] -= DELTA; - else - cv.values[i] = PA_VOLUME_MUTED; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE); + pa_cvolume_dec(&cv, u->volume_step); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case MUTE_TOGGLE: - pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE); break; @@ -161,6 +162,8 @@ int pa__init(pa_module*m) { struct input_id input_id; char name[256]; uint8_t evtype_bitmask[EV_MAX/8 + 1]; + pa_volume_t volume_limit = PA_VOLUME_NORM*3/2; + pa_volume_t volume_step = PA_VOLUME_NORM/20; pa_assert(m); @@ -169,14 +172,26 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_modargs_get_value_u32(ma, "volume_limit", &volume_limit) < 0) { + pa_log("Failed to parse volume limit"); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "volume_step", &volume_step) < 0) { + pa_log("Failed to parse volume step"); + goto fail; + } + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->io = NULL; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->fd = -1; u->fd_type = 0; + u->volume_limit = volume_limit; + u->volume_step = volume_step; - if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY|O_NOCTTY)) < 0) { + if ((u->fd = pa_open_cloexec(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY, 0)) < 0) { pa_log("Failed to open evdev device: %s", pa_cstrerror(errno)); goto fail; } diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index 36c50b05..74a2ebb1 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -35,6 +35,7 @@ #include <pulse/rtclock.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> +#include <pulse/i18n.h> #include <pulsecore/macro.h> #include <pulsecore/sink.h> @@ -51,7 +52,7 @@ #include "module-null-sink-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Clocked NULL sink"); +PA_MODULE_DESCRIPTION(_("Clocked NULL sink")); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( @@ -287,7 +288,7 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", _("Null Output"))); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index 9c169327..10cc3415 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -253,12 +253,11 @@ int pa__init(pa_module*m) { u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); mkfifo(u->filename, 0666); - if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + if ((u->fd = pa_open_cloexec(u->filename, O_RDWR, 0)) < 0) { pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(u->fd); pa_make_fd_nonblock(u->fd); if (fstat(u->fd, &st) < 0) { diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index 49104f8d..de680933 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -238,12 +238,11 @@ int pa__init(pa_module*m) { u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); mkfifo(u->filename, 0666); - if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + if ((u->fd = pa_open_cloexec(u->filename, O_RDWR, 0)) < 0) { pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(u->fd); pa_make_fd_nonblock(u->fd); if (fstat(u->fd, &st) < 0) { diff --git a/src/modules/module-position-event-sounds.c b/src/modules/module-position-event-sounds.c index e191ec33..ee4c8c88 100644 --- a/src/modules/module-position-event-sounds.c +++ b/src/modules/module-position-event-sounds.c @@ -57,35 +57,85 @@ struct userdata { pa_hook_slot *sink_input_fixate_hook_slot; }; +static int parse_pos(const char *pos, double *f) { + + if (pa_atod(pos, f) < 0) { + pa_log_warn("Failed to parse hpos/vpos property '%s'.", pos); + return -1; + } + + if (*f < 0.0 || *f > 1.0) { + pa_log_debug("Property hpos/vpos out of range %0.2f", *f); + + *f = PA_CLAMP(*f, 0.0, 1.0); + } + + return 0; +} + static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) { - const char *hpos; + const char *hpos, *vpos, *role, *id; double f; char t[PA_CVOLUME_SNPRINT_MAX]; pa_cvolume v; pa_assert(data); - if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS))) + if (!(role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE))) return PA_HOOK_OK; - if (pa_atod(hpos, &f) < 0) { - pa_log_warn("Failed to parse "PA_PROP_EVENT_MOUSE_HPOS" property '%s'.", hpos); + if (!pa_streq(role, "event")) return PA_HOOK_OK; + + if ((id = pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID))) { + + /* The test sounds should never be positioned in space, since + * they might be trigered themselves to configure the speakers + * in space, which we don't want to mess up. */ + + if (pa_startswith(id, "audio-channel-")) + return PA_HOOK_OK; + + if (pa_streq(id, "audio-volume-change")) + return PA_HOOK_OK; + + if (pa_streq(id, "audio-test-signal")) + return PA_HOOK_OK; } - if (f < 0.0 || f > 1.0) { - pa_log_warn("Property "PA_PROP_EVENT_MOUSE_HPOS" out of range %0.2f", f); + if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS))) + hpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_HPOS); + + if (!(vpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_VPOS))) + vpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_VPOS); + + if (!hpos && !vpos) return PA_HOOK_OK; + + pa_cvolume_reset(&v, data->sink->sample_spec.channels); + + if (hpos) { + if (parse_pos(hpos, &f) < 0) + return PA_HOOK_OK; + + if (pa_channel_map_can_balance(&data->sink->channel_map)) { + pa_log_debug("Positioning event sound '%s' horizontally at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + pa_cvolume_set_balance(&v, &data->sink->channel_map, f*2.0-1.0); + } } - pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + if (vpos) { + if (parse_pos(vpos, &f) < 0) + return PA_HOOK_OK; - pa_cvolume_reset(&v, data->sample_spec.channels); - pa_cvolume_set_balance(&v, &data->channel_map, f*2.0-1.0); + if (pa_channel_map_can_fade(&data->sink->channel_map)) { + pa_log_debug("Positioning event sound '%s' vertically at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + pa_cvolume_set_fade(&v, &data->sink->channel_map, f*2.0-1.0); + } + } pa_log_debug("Final volume factor %s.", pa_cvolume_snprint(t, sizeof(t), &v)); - - pa_sink_input_new_data_apply_volume_factor(data, &v); + pa_sink_input_new_data_apply_volume_factor_sink(data, &v); return PA_HOOK_OK; } diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 119f5b9f..43748bd0 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2004-2008 Lennart Poettering + Copyright 2004-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 @@ -48,17 +48,18 @@ PA_MODULE_USAGE( "master=<name of sink to remap> " "master_channel_map=<channel map> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map> " "remix=<remix channels?>"); struct userdata { - pa_core *core; pa_module *module; - pa_sink *sink, *master; + pa_sink *sink; pa_sink_input *sink_input; + + pa_bool_t auto_desc; }; static const char* const valid_modargs[] = { @@ -80,19 +81,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch (code) { - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t usec = 0; + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it yet */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } - /* Get the latency of the master sink */ - if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - usec = 0; + *((pa_usec_t*) data) = + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + - /* Add the latency internal to our sink input on top */ - usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - *((pa_usec_t*) data) = usec; return 0; - } } return pa_sink_process_msg(o, code, data, offset, chunk); @@ -105,12 +111,11 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - if (PA_SINK_IS_LINKED(state) && - u->sink_input && - PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) - - pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; } @@ -121,6 +126,10 @@ static void sink_request_rewind(pa_sink *s) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE); } @@ -131,6 +140,10 @@ static void sink_update_requested_latency(pa_sink *s) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + /* Just hand this one over to the master sink */ pa_sink_input_set_requested_latency_within_thread( u->sink_input, @@ -145,8 +158,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return -1; + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); pa_sink_render(u->sink, nbytes, chunk); return 0; @@ -160,9 +173,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return; - if (u->sink->thread_info.rewind_nbytes > 0) { amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes); u->sink->thread_info.rewind_nbytes = 0; @@ -178,9 +188,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_max_rewind_within_thread(u->sink, nbytes); } @@ -191,9 +198,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_max_request_within_thread(u->sink, nbytes); } @@ -204,24 +208,28 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); } /* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); pa_sink_detach_within_thread(u->sink); - pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); } @@ -232,14 +240,13 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); - pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); - pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); pa_sink_attach_within_thread(u->sink); - - pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); } /* Called from main context */ @@ -249,14 +256,18 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_sink_unlink(u->sink); + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; pa_sink_input_unref(u->sink_input); u->sink_input = NULL; + pa_sink_unref(u->sink); + u->sink = NULL; + pa_module_unload_request(u->module, TRUE); } @@ -286,12 +297,37 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { return u->sink != dest; } +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + 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); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); + + if (u->auto_desc && dest) { + const char *k; + pa_proplist *pl; + + pl = pa_proplist_new(); + k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map sink_map, stream_map; pa_modargs *ma; - const char *k; pa_sink *master; pa_sink_input_new_data sink_input_data; pa_sink_new_data sink_data; @@ -336,12 +372,8 @@ int pa__init(pa_module*m) { } u = pa_xnew0(struct userdata, 1); - u->core = m->core; u->module = m; m->userdata = u; - u->master = master; - u->sink = NULL; - u->sink_input = NULL; /* Create sink */ pa_sink_new_data_init(&sink_data); @@ -351,8 +383,6 @@ int pa__init(pa_module*m) { sink_data.name = pa_sprintf_malloc("%s.remapped", master->name); pa_sink_new_data_set_sample_spec(&sink_data, &ss); pa_sink_new_data_set_channel_map(&sink_data, &sink_map); - k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); @@ -362,7 +392,14 @@ int pa__init(pa_module*m) { goto fail; } - u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); + if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *k; + + k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); + } + + u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)); pa_sink_new_data_done(&sink_data); if (!u->sink) { @@ -377,19 +414,19 @@ int pa__init(pa_module*m) { u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - pa_sink_set_rtpoll(u->sink, master->rtpoll); /* Create sink input */ pa_sink_input_new_data_init(&sink_input_data); sink_input_data.driver = __FILE__; sink_input_data.module = m; - sink_input_data.sink = u->master; + sink_input_data.sink = master; pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map); + sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX); - pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX)); + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); pa_sink_input_new_data_done(&sink_input_data); if (!u->sink_input) @@ -400,11 +437,13 @@ int pa__init(pa_module*m) { u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; u->sink_input->update_max_request = sink_input_update_max_request_cb; u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; u->sink_input->kill = sink_input_kill_cb; u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; u->sink_input->userdata = u; pa_sink_put(u->sink); @@ -440,15 +479,20 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink) { - pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - } + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ - if (u->sink_input) { + if (u->sink_input) pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) pa_sink_input_unref(u->sink_input); - } + + if (u->sink) + pa_sink_unref(u->sink); pa_xfree(u); } diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index c23feceb..722d84b2 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -45,13 +45,46 @@ static const char* const valid_modargs[] = { }; struct userdata { - pa_hook_slot *sink_slot, *source_slot; + pa_hook_slot + *sink_unlink_slot, + *source_unlink_slot, + *sink_input_move_fail_slot, + *source_output_move_fail_slot; }; -static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { +static pa_sink* find_evacuation_sink(pa_core *c, pa_sink_input *i, pa_sink *skip) { + pa_sink *target, *def; + uint32_t idx; + + pa_assert(c); + pa_assert(i); + + def = pa_namereg_get_default_sink(c); + + if (def && def != skip && pa_sink_input_may_move_to(i, def)) + return def; + + PA_IDXSET_FOREACH(target, c->sinks, idx) { + if (target == def) + continue; + + if (target == skip) + continue; + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(target))) + continue; + + if (pa_sink_input_may_move_to(i, target)) + return target; + } + + pa_log_debug("No evacuation sink found."); + return NULL; +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { pa_sink_input *i; uint32_t idx; - pa_sink *target; pa_assert(c); pa_assert(sink); @@ -65,21 +98,12 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user return PA_HOOK_OK; } - if (!(target = pa_namereg_get_default_sink(c)) || target == sink) { - - PA_IDXSET_FOREACH(target, c->sinks, idx) - if (target != sink) - break; - - if (!target) { - pa_log_debug("No evacuation sink found."); - return PA_HOOK_OK; - } - } + PA_IDXSET_FOREACH(i, sink->inputs, idx) { + pa_sink *target; - pa_assert(target != sink); + if (!(target = find_evacuation_sink(c, i, sink))) + continue; - PA_IDXSET_FOREACH(i, sink->inputs, idx) { if (pa_sink_input_move_to(i, target, FALSE) < 0) pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); @@ -91,9 +115,66 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user return PA_HOOK_OK; } -static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) { +static pa_hook_result_t sink_input_move_fail_hook_callback(pa_core *c, pa_sink_input *i, void *userdata) { + pa_sink *target; + + pa_assert(c); + pa_assert(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + if (!(target = find_evacuation_sink(c, i, NULL))) + return PA_HOOK_OK; + + if (pa_sink_input_finish_move(i, target, FALSE) < 0) { + pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_OK; + + } else { + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_STOP; + } +} + +static pa_source* find_evacuation_source(pa_core *c, pa_source_output *o, pa_source *skip) { + pa_source *target, *def; + uint32_t idx; + + pa_assert(c); + pa_assert(o); + + def = pa_namereg_get_default_source(c); + + if (def && def != skip && pa_source_output_may_move_to(o, def)) + return def; + + PA_IDXSET_FOREACH(target, c->sources, idx) { + if (target == def) + continue; + + if (target == skip) + continue; + + if (!target->monitor_of != !skip->monitor_of) + continue; + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(target))) + continue; + + if (pa_source_output_may_move_to(o, target)) + return target; + } + + pa_log_debug("No evacuation source found."); + return NULL; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) { pa_source_output *o; - pa_source *target; uint32_t idx; pa_assert(c); @@ -108,21 +189,12 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void return PA_HOOK_OK; } - if (!(target = pa_namereg_get_default_source(c)) || target == source) { - - PA_IDXSET_FOREACH(target, c->sources, idx) - if (target != source && !target->monitor_of == !source->monitor_of) - break; - - if (!target) { - pa_log_info("No evacuation source found."); - return PA_HOOK_OK; - } - } + PA_IDXSET_FOREACH(o, source->outputs, idx) { + pa_source *target; - pa_assert(target != source); + if (!(target = find_evacuation_source(c, o, source))) + continue; - PA_IDXSET_FOREACH(o, source->outputs, idx) { if (pa_source_output_move_to(o, target, FALSE) < 0) pa_log_info("Failed to move source output %u \"%s\" to %s.", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); @@ -134,6 +206,31 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void return PA_HOOK_OK; } +static pa_hook_result_t source_output_move_fail_hook_callback(pa_core *c, pa_source_output *i, void *userdata) { + pa_source *target; + + pa_assert(c); + pa_assert(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + if (!(target = find_evacuation_source(c, i, NULL))) + return PA_HOOK_OK; + + if (pa_source_output_finish_move(i, target, FALSE) < 0) { + pa_log_info("Failed to move source input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_OK; + + } else { + pa_log_info("Sucessfully moved source input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_STOP; + } +} + int pa__init(pa_module*m) { pa_modargs *ma; struct userdata *u; @@ -148,8 +245,11 @@ int pa__init(pa_module*m) { m->userdata = u = pa_xnew(struct userdata, 1); /* A little bit later than module-stream-restore, module-intended-roles... */ - u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_hook_callback, u); - u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_hook_callback, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_unlink_hook_callback, u); + + u->sink_input_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_input_move_fail_hook_callback, u); + u->source_output_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) source_output_move_fail_hook_callback, u); pa_modargs_free(ma); return 0; @@ -163,10 +263,15 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink_slot) - pa_hook_slot_free(u->sink_slot); - if (u->source_slot) - pa_hook_slot_free(u->source_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->sink_input_move_fail_slot) + pa_hook_slot_free(u->sink_input_move_fail_slot); + if (u->source_output_move_fail_slot) + pa_hook_slot_free(u->source_output_move_fail_slot); pa_xfree(u); } diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index 0be1d722..69b20028 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -163,7 +163,7 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency); pa_sink_input_new_data_set_sample_spec(&data, &ss); - pa_sink_input_new(&u->sink_input, m->core, &data, 0); + pa_sink_input_new(&u->sink_input, m->core, &data); pa_sink_input_new_data_done(&data); if (!u->sink_input) diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 0920d25e..955997ba 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -60,6 +60,7 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> #include <pulsecore/thread.h> +#include <pulsecore/time-smoother.h> #include "module-solaris-symdef.h" @@ -110,6 +111,8 @@ struct userdata { uint32_t prev_playback_samples, prev_record_samples; int32_t minimum_request; + + pa_smoother *smoother; }; static const char* const valid_modargs[] = { @@ -133,6 +136,9 @@ static const char* const valid_modargs[] = { #define MAX_RENDER_HZ (300) /* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */ +#define MAX_BUFFER_SIZE (128 * 1024) +/* An attempt to buffer more than 128 KB causes write() to fail with errno == EAGAIN. */ + static uint64_t get_playback_buffered_bytes(struct userdata *u) { audio_info_t info; uint64_t played_bytes; @@ -145,7 +151,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) { /* Handle wrap-around of the device's sample counter, which is a uint_32. */ if (u->prev_playback_samples > info.play.samples) { - /* Unfortunately info.play.samples can sometimes go backwards, even before it wraps! */ + /* + * Unfortunately info.play.samples can sometimes go backwards, even before it wraps! + * The bug seems to be absent on Solaris x86 nv117 with audio810 driver, at least on this (UP) machine. + * The bug is present on a different (SMP) machine running Solaris x86 nv103 with audioens driver. + * An earlier revision of this file mentions the same bug independently (unknown configuration). + */ if (u->prev_playback_samples + info.play.samples < 240000) { ++u->play_samples_msw; } else { @@ -155,6 +166,8 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) { u->prev_playback_samples = info.play.samples; played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) * u->frame_size; + pa_smoother_put(u->smoother, pa_rtclock_now(), pa_bytes_to_usec(played_bytes, &u->sink->sample_spec)); + return u->written_bytes - played_bytes; } @@ -314,7 +327,7 @@ static int open_audio_device(struct userdata *u, pa_sample_spec *ss) { pa_assert(u); pa_assert(ss); - if ((u->fd = open(u->device_name, u->mode | O_NONBLOCK)) < 0) { + if ((u->fd = pa_open_cloexec(u->device_name, u->mode | O_NONBLOCK)) < 0) { pa_log_warn("open %s failed (%s)", u->device_name, pa_cstrerror(errno)); return -1; } @@ -387,6 +400,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + pa_smoother_pause(u->smoother, pa_rtclock_now()); + if (!u->source || u->source_suspended) { if (suspend(u) < 0) return -1; @@ -398,6 +413,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_RUNNING: if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); + if (!u->source || u->source_suspended) { if (unsuspend(u) < 0) return -1; @@ -479,7 +496,7 @@ static void sink_set_volume(pa_sink *s) { if (u->fd >= 0) { AUDIO_INITINFO(&info); - info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; assert(info.play.gain <= AUDIO_MAX_GAIN); if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -501,8 +518,7 @@ static void sink_get_volume(pa_sink *s) { if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); else - pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); } } @@ -515,7 +531,7 @@ static void source_set_volume(pa_source *s) { if (u->fd >= 0) { AUDIO_INITINFO(&info); - info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + info.play.gain = pa_cvolume_max(&s->volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; assert(info.play.gain <= AUDIO_MAX_GAIN); if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -537,8 +553,7 @@ static void source_get_volume(pa_source *s) { if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); else - pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + pa_cvolume_set(&s->volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); } } @@ -585,6 +600,10 @@ static void process_rewind(struct userdata *u) { pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); rewind_nbytes = PA_MIN(u->memchunk.length, rewind_nbytes); u->memchunk.length -= rewind_nbytes; + if (u->memchunk.length <= 0 && u->memchunk.memblock) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); } @@ -606,11 +625,13 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_now()); + for (;;) { /* Render some data and write it to the dsp */ if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - pa_usec_t xtime0; + pa_usec_t xtime0, ysleep_interval, xsleep_interval; uint64_t buffered_bytes; if (u->sink->thread_info.rewind_requested) @@ -629,12 +650,15 @@ static void thread_func(void *userdata) { info.play.error = 0; if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + + pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE); } for (;;) { void *p; ssize_t w; size_t len; + int write_type = 1; /* * Since we cannot modify the size of the output buffer we fake it @@ -651,39 +675,32 @@ static void thread_func(void *userdata) { if (len < (size_t) u->minimum_request) break; - if (u->memchunk.length < len) + if (!u->memchunk.length) pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk); + len = PA_MIN(u->memchunk.length, len); + p = pa_memblock_acquire(u->memchunk.memblock); - w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL); + w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, len, &write_type); pa_memblock_release(u->memchunk.memblock); if (w <= 0) { - switch (errno) { - case EINTR: - continue; - case EAGAIN: - /* If the buffer_size is too big, we get EAGAIN. Avoiding that limit by trial and error - * is not ideal, but I don't know how to get the system to tell me what the limit is. - */ - u->buffer_size = u->buffer_size * 18 / 25; - u->buffer_size -= u->buffer_size % u->frame_size; - u->buffer_size = PA_MAX(u->buffer_size, 2 * u->minimum_request); - pa_sink_set_max_request_within_thread(u->sink, u->buffer_size); - pa_sink_set_max_rewind_within_thread(u->sink, u->buffer_size); - pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); - break; - default: - pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); - goto fail; + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + /* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */ + pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes); + break; + } else { + pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); + goto fail; } } else { pa_assert(w % u->frame_size == 0); u->written_bytes += w; - u->memchunk.length -= w; - u->memchunk.index += w; + u->memchunk.length -= w; if (u->memchunk.length <= 0) { pa_memblock_unref(u->memchunk.memblock); pa_memchunk_reset(&u->memchunk); @@ -691,7 +708,9 @@ static void thread_func(void *userdata) { } } - pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); + ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec); + xsleep_interval = pa_smoother_translate(u->smoother, xtime0, ysleep_interval); + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + PA_MIN(xsleep_interval, ysleep_interval)); } else pa_rtpoll_set_timer_disabled(u->rtpoll); @@ -797,7 +816,7 @@ static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void pa_log_debug("caught signal"); if (u->sink) { - pa_sink_get_volume(u->sink, TRUE, FALSE); + pa_sink_get_volume(u->sink, TRUE); pa_sink_get_mute(u->sink, TRUE); } @@ -812,7 +831,7 @@ int pa__init(pa_module *m) { pa_channel_map map; pa_modargs *ma = NULL; uint32_t buffer_length_msec; - int fd; + int fd = -1; pa_sink_new_data sink_new_data; pa_source_new_data source_new_data; char const *name; @@ -838,6 +857,9 @@ int pa__init(pa_module *m) { u = pa_xnew0(struct userdata, 1); + if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, TRUE, TRUE, 10, pa_rtclock_now(), TRUE))) + goto fail; + /* * For a process (or several processes) to use the same audio device for both * record and playback at the same time, the device's mixer must be enabled. @@ -861,7 +883,13 @@ int pa__init(pa_module *m) { } u->buffer_size = pa_usec_to_bytes(1000 * buffer_length_msec, &ss); if (u->buffer_size < 2 * u->minimum_request) { - pa_log("supplied buffer size argument is too small"); + pa_log("buffer_length argument cannot be smaller than %u", + (unsigned)(pa_bytes_to_usec(2 * u->minimum_request, &ss) / 1000)); + goto fail; + } + if (u->buffer_size > MAX_BUFFER_SIZE) { + pa_log("buffer_length argument cannot be greater than %u", + (unsigned)(pa_bytes_to_usec(MAX_BUFFER_SIZE, &ss) / 1000)); goto fail; } @@ -924,6 +952,7 @@ int pa__init(pa_module *m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec)); u->source->get_volume = source_get_volume; u->source->set_volume = source_set_volume; @@ -966,15 +995,15 @@ int pa__init(pa_module *m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec)); + pa_sink_set_max_request(u->sink, u->buffer_size); + pa_sink_set_max_rewind(u->sink, u->buffer_size); u->sink->get_volume = sink_get_volume; u->sink->set_volume = sink_set_volume; u->sink->get_mute = sink_get_mute; u->sink->set_mute = sink_set_mute; u->sink->refresh_volume = u->sink->refresh_muted = TRUE; - - pa_sink_set_max_request(u->sink, u->buffer_size); - pa_sink_set_max_rewind(u->sink, u->buffer_size); } else u->sink = NULL; @@ -1075,6 +1104,9 @@ void pa__done(pa_module *m) { if (u->fd >= 0) close(u->fd); + if (u->smoother) + pa_smoother_free(u->smoother); + pa_xfree(u->device_name); pa_xfree(u); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 8c0bb6b0..788f458b 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -2,6 +2,7 @@ This file is part of PulseAudio. Copyright 2008 Lennart Poettering + Copyright 2009 Tanu Kaskinen PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -51,6 +52,11 @@ #include <pulsecore/pstream-util.h> #include <pulsecore/database.h> +#ifdef HAVE_DBUS +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> +#endif + #include "module-stream-restore-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); @@ -100,17 +106,24 @@ struct userdata { pa_native_protocol *protocol; pa_idxset *subscribed; + +#ifdef HAVE_DBUS + pa_dbus_protocol *dbus_protocol; + pa_hashmap *dbus_entries; + uint32_t next_index; /* For generating object paths for entries. */ +#endif }; -#define ENTRY_VERSION 2 +#define ENTRY_VERSION 3 struct entry { uint8_t version; - pa_bool_t muted_valid:1, volume_valid:1, device_valid:1; + pa_bool_t muted_valid:1, volume_valid:1, device_valid:1, card_valid:1; pa_bool_t muted:1; pa_channel_map channel_map; pa_cvolume volume; char device[PA_NAME_MAX]; + char card[PA_NAME_MAX]; } PA_GCC_PACKED; enum { @@ -122,6 +135,835 @@ enum { SUBCOMMAND_EVENT }; +static struct entry *read_entry(struct userdata *u, const char *name); +static void apply_entry(struct userdata *u, const char *name, struct entry *e); +static void trigger_save(struct userdata *u); + +#ifdef HAVE_DBUS + +#define OBJECT_PATH "/org/pulseaudio/stream_restore1" +#define ENTRY_OBJECT_NAME "entry" +#define INTERFACE_STREAM_RESTORE "org.PulseAudio.Ext.StreamRestore1" +#define INTERFACE_ENTRY INTERFACE_STREAM_RESTORE ".RestoreEntry" + +#define DBUS_INTERFACE_REVISION 0 + +struct dbus_entry { + struct userdata *userdata; + + char *entry_name; + uint32_t index; + char *object_path; +}; + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); + +static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INTERFACE_REVISION, + PROPERTY_HANDLER_ENTRIES, + PROPERTY_HANDLER_MAX +}; + +enum entry_property_handler_index { + ENTRY_PROPERTY_HANDLER_INDEX, + ENTRY_PROPERTY_HANDLER_NAME, + ENTRY_PROPERTY_HANDLER_DEVICE, + ENTRY_PROPERTY_HANDLER_VOLUME, + ENTRY_PROPERTY_HANDLER_MUTE, + ENTRY_PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL }, + [PROPERTY_HANDLER_ENTRIES] = { .property_name = "Entries", .type = "ao", .get_cb = handle_get_entries, .set_cb = NULL } +}; + +static pa_dbus_property_handler entry_property_handlers[ENTRY_PROPERTY_HANDLER_MAX] = { + [ENTRY_PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_entry_get_index, .set_cb = NULL }, + [ENTRY_PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_entry_get_name, .set_cb = NULL }, + [ENTRY_PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "s", .get_cb = handle_entry_get_device, .set_cb = handle_entry_set_device }, + [ENTRY_PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "a(uu)", .get_cb = handle_entry_get_volume, .set_cb = handle_entry_set_volume }, + [ENTRY_PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_entry_get_mute, .set_cb = handle_entry_set_mute } +}; + +enum method_handler_index { + METHOD_HANDLER_ADD_ENTRY, + METHOD_HANDLER_GET_ENTRY_BY_NAME, + METHOD_HANDLER_MAX +}; + +enum entry_method_handler_index { + ENTRY_METHOD_HANDLER_REMOVE, + ENTRY_METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info add_entry_args[] = { { "name", "s", "in" }, + { "device", "s", "in" }, + { "volume", "a(uu)", "in" }, + { "mute", "b", "in" }, + { "entry", "o", "out" } }; +static pa_dbus_arg_info get_entry_by_name_args[] = { { "name", "s", "in" }, { "entry", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_ADD_ENTRY] = { + .method_name = "AddEntry", + .arguments = add_entry_args, + .n_arguments = sizeof(add_entry_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_add_entry }, + [METHOD_HANDLER_GET_ENTRY_BY_NAME] = { + .method_name = "GetEntryByName", + .arguments = get_entry_by_name_args, + .n_arguments = sizeof(get_entry_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_entry_by_name } +}; + +static pa_dbus_method_handler entry_method_handlers[ENTRY_METHOD_HANDLER_MAX] = { + [ENTRY_METHOD_HANDLER_REMOVE] = { + .method_name = "Remove", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_entry_remove } +}; + +enum signal_index { + SIGNAL_NEW_ENTRY, + SIGNAL_ENTRY_REMOVED, + SIGNAL_MAX +}; + +enum entry_signal_index { + ENTRY_SIGNAL_DEVICE_UPDATED, + ENTRY_SIGNAL_VOLUME_UPDATED, + ENTRY_SIGNAL_MUTE_UPDATED, + ENTRY_SIGNAL_MAX +}; + +static pa_dbus_arg_info new_entry_args[] = { { "entry", "o", NULL } }; +static pa_dbus_arg_info entry_removed_args[] = { { "entry", "o", NULL } }; + +static pa_dbus_arg_info entry_device_updated_args[] = { { "device", "s", NULL } }; +static pa_dbus_arg_info entry_volume_updated_args[] = { { "volume", "a(uu)", NULL } }; +static pa_dbus_arg_info entry_mute_updated_args[] = { { "muted", "b", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_NEW_ENTRY] = { .name = "NewEntry", .arguments = new_entry_args, .n_arguments = 1 }, + [SIGNAL_ENTRY_REMOVED] = { .name = "EntryRemoved", .arguments = entry_removed_args, .n_arguments = 1 } +}; + +static pa_dbus_signal_info entry_signals[ENTRY_SIGNAL_MAX] = { + [ENTRY_SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = entry_device_updated_args, .n_arguments = 1 }, + [ENTRY_SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = entry_volume_updated_args, .n_arguments = 1 }, + [ENTRY_SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = entry_mute_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info stream_restore_interface_info = { + .name = INTERFACE_STREAM_RESTORE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static pa_dbus_interface_info entry_interface_info = { + .name = INTERFACE_ENTRY, + .method_handlers = entry_method_handlers, + .n_method_handlers = ENTRY_METHOD_HANDLER_MAX, + .property_handlers = entry_property_handlers, + .n_property_handlers = ENTRY_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_entry_get_all, + .signals = entry_signals, + .n_signals = ENTRY_SIGNAL_MAX +}; + +static struct dbus_entry *dbus_entry_new(struct userdata *u, const char *entry_name) { + struct dbus_entry *de; + + pa_assert(u); + pa_assert(entry_name); + pa_assert(*entry_name); + + de = pa_xnew(struct dbus_entry, 1); + de->userdata = u; + de->entry_name = pa_xstrdup(entry_name); + de->index = u->next_index++; + de->object_path = pa_sprintf_malloc("%s/%s%u", OBJECT_PATH, ENTRY_OBJECT_NAME, de->index); + + pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, de->object_path, &entry_interface_info, u) >= 0); + + return de; +} + +static void dbus_entry_free(struct dbus_entry *de) { + pa_assert(de); + + pa_assert_se(pa_dbus_protocol_remove_interface(de->userdata->dbus_protocol, de->object_path, entry_interface_info.name) >= 0); + + pa_xfree(de->entry_name); + pa_xfree(de->object_path); +} + +/* Reads an array [(UInt32, UInt32)] from the iterator. The struct items are + * are a channel position and a volume value, respectively. The result is + * stored in the map and vol arguments. The iterator must point to a "a(uu)" + * element. If the data is invalid, an error reply is sent and a negative + * number is returned. In case of a failure we make no guarantees about the + * state of map and vol. In case of an empty array the channels field of both + * map and vol are set to 0. This function calls dbus_message_iter_next(iter) + * before returning. */ +static int get_volume_arg(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, pa_channel_map *map, pa_cvolume *vol) { + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(pa_streq(dbus_message_iter_get_signature(iter), "a(uu)")); + pa_assert(map); + pa_assert(vol); + + pa_channel_map_init(map); + pa_cvolume_init(vol); + + map->channels = 0; + vol->channels = 0; + + dbus_message_iter_recurse(iter, &array_iter); + + while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) { + dbus_uint32_t chan_pos; + dbus_uint32_t chan_vol; + + dbus_message_iter_recurse(&array_iter, &struct_iter); + + dbus_message_iter_get_basic(&struct_iter, &chan_pos); + + if (chan_pos >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u", chan_pos); + return -1; + } + + pa_assert_se(dbus_message_iter_next(&struct_iter)); + dbus_message_iter_get_basic(&struct_iter, &chan_vol); + + if (chan_vol > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", chan_vol); + return -1; + } + + if (map->channels < PA_CHANNELS_MAX) { + map->map[map->channels] = chan_pos; + vol->values[map->channels] = chan_vol; + } + ++map->channels; + ++vol->channels; + + dbus_message_iter_next(&array_iter); + } + + if (map->channels > PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too many channels: %u. The maximum is %u.", map->channels, PA_CHANNELS_MAX); + return -1; + } + + dbus_message_iter_next(iter); + + return 0; +} + +static void append_volume(DBusMessageIter *iter, struct entry *e) { + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + unsigned i; + + pa_assert(iter); + pa_assert(e); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(uu)", &array_iter)); + + if (!e->volume_valid) { + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); + return; + } + + for (i = 0; i < e->channel_map.channels; ++i) { + pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->channel_map.map[i])); + pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->volume.values[i])); + + pa_assert_se(dbus_message_iter_close_container(&array_iter, &struct_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); +} + +static void append_volume_variant(DBusMessageIter *iter, struct entry *e) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(e); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(uu)", &variant_iter)); + + append_volume(&variant_iter, e); + + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +static void send_new_entry_signal(struct dbus_entry *entry) { + DBusMessage *signal; + + pa_assert(entry); + + pa_assert_se(signal = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_NEW_ENTRY].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal); + dbus_message_unref(signal); +} + +static void send_entry_removed_signal(struct dbus_entry *entry) { + DBusMessage *signal; + + pa_assert(entry); + + pa_assert_se(signal = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_ENTRY_REMOVED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal); + dbus_message_unref(signal); +} + +static void send_device_updated_signal(struct dbus_entry *de, struct entry *e) { + DBusMessage *signal; + const char *device; + + pa_assert(de); + pa_assert(e); + + device = e->device_valid ? e->device : ""; + + pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal); + dbus_message_unref(signal); +} + +static void send_volume_updated_signal(struct dbus_entry *de, struct entry *e) { + DBusMessage *signal; + DBusMessageIter msg_iter; + + pa_assert(de); + pa_assert(e); + + pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_VOLUME_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + append_volume(&msg_iter, e); + pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal); + dbus_message_unref(signal); +} + +static void send_mute_updated_signal(struct dbus_entry *de, struct entry *e) { + DBusMessage *signal; + dbus_bool_t muted; + + pa_assert(de); + pa_assert(e); + + pa_assert(e->muted_valid); + + muted = e->muted; + + pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal); + dbus_message_unref(signal); +} + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_uint32_t interface_revision = DBUS_INTERFACE_REVISION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_entries(struct userdata *u, unsigned *n) { + const char **entries; + unsigned i = 0; + void *state = NULL; + struct dbus_entry *de; + + pa_assert(u); + pa_assert(n); + + *n = pa_hashmap_size(u->dbus_entries); + + if (*n == 0) + return NULL; + + entries = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(de, u->dbus_entries, state) + entries[i++] = de->object_path; + + return entries; +} + +static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + const char **entries; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + entries = get_entries(u, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, entries, n); + + pa_xfree(entries); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t interface_revision; + const char **entries; + unsigned n_entries; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + interface_revision = DBUS_INTERFACE_REVISION; + entries = get_entries(u, &n_entries); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ENTRIES].property_name, DBUS_TYPE_OBJECT_PATH, entries, n_entries); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(entries); +} + +static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + DBusMessageIter msg_iter; + const char *name = NULL; + const char *device = NULL; + pa_channel_map map; + pa_cvolume vol; + dbus_bool_t muted = FALSE; + dbus_bool_t apply_immediately = FALSE; + pa_datum key; + pa_datum value; + struct dbus_entry *dbus_entry = NULL; + struct entry *e = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &device); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (get_volume_arg(conn, msg, &msg_iter, &map, &vol) < 0) + return; + + dbus_message_iter_get_basic(&msg_iter, &muted); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &apply_immediately); + + if (!*name) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "An empty string was given as the entry name."); + return; + } + + if ((dbus_entry = pa_hashmap_get(u->dbus_entries, name))) { + pa_bool_t mute_updated = FALSE; + pa_bool_t volume_updated = FALSE; + pa_bool_t device_updated = FALSE; + + pa_assert_se(e = read_entry(u, name)); + mute_updated = e->muted != muted; + e->muted = muted; + e->muted_valid = TRUE; + + volume_updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol); + e->volume = vol; + e->channel_map = map; + e->volume_valid = !!map.channels; + + device_updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); + pa_strlcpy(e->device, device, sizeof(e->device)); + e->device_valid = !!device[0]; + + if (mute_updated) + send_mute_updated_signal(dbus_entry, e); + if (volume_updated) + send_volume_updated_signal(dbus_entry, e); + if (device_updated) + send_device_updated_signal(dbus_entry, e); + + } else { + dbus_entry = dbus_entry_new(u, name); + pa_assert(pa_hashmap_put(u->dbus_entries, dbus_entry->entry_name, dbus_entry) >= 0); + + e->muted_valid = TRUE; + e->volume_valid = !!map.channels; + e->device_valid = !!device[0]; + e->muted = muted; + e->volume = vol; + e->channel_map = map; + pa_strlcpy(e->device, device, sizeof(e->device)); + + send_new_entry_signal(dbus_entry); + } + + key.data = (char *) name; + key.size = strlen(name); + + value.data = e; + value.size = sizeof(struct entry); + + pa_assert_se(pa_database_set(u->database, &key, &value, TRUE) == 0); + if (apply_immediately) + apply_entry(u, name, e); + + trigger_save(u); + + pa_dbus_send_empty_reply(conn, msg); + + pa_xfree(e); +} + +static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + const char *name; + struct dbus_entry *de; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)); + + if (!(de = pa_hashmap_get(u->dbus_entries, name))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such stream restore entry."); + return; + } + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &de->object_path); +} + +static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &de->index); +} + +static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &de->entry_name); +} + +static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + struct entry *e; + const char *device; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + device = e->device_valid ? e->device : ""; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &device); + + pa_xfree(e); +} + +static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + struct dbus_entry *de = userdata; + const char *device; + struct entry *e; + pa_bool_t updated; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(de); + + dbus_message_iter_get_basic(iter, &device); + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); + + if (updated) { + pa_datum key; + pa_datum value; + + pa_strlcpy(e->device, device, sizeof(e->device)); + e->device_valid = !!device[0]; + + key.data = de->entry_name; + key.size = strlen(de->entry_name); + value.data = e; + value.size = sizeof(struct entry); + pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0); + + send_device_updated_signal(de, e); + trigger_save(de->userdata); + } + + pa_dbus_send_empty_reply(conn, msg); + + pa_xfree(e); +} + +static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + DBusMessage *reply; + DBusMessageIter msg_iter; + struct entry *e; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + pa_assert_se(reply = dbus_message_new_method_return(msg)); + + dbus_message_iter_init_append(reply, &msg_iter); + append_volume_variant(&msg_iter, e); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + pa_xfree(e); +} + +static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + struct dbus_entry *de = userdata; + pa_channel_map map; + pa_cvolume vol; + struct entry *e = NULL; + pa_bool_t updated = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(de); + + if (get_volume_arg(conn, msg, iter, &map, &vol) < 0) + return; + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol); + + if (updated) { + pa_datum key; + pa_datum value; + + e->volume = vol; + e->channel_map = map; + e->volume_valid = !!map.channels; + + key.data = de->entry_name; + key.size = strlen(de->entry_name); + value.data = e; + value.size = sizeof(struct entry); + pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0); + + send_volume_updated_signal(de, e); + trigger_save(de->userdata); + } + + pa_dbus_send_empty_reply(conn, msg); + + pa_xfree(e); +} + +static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + struct entry *e; + dbus_bool_t mute; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + mute = e->muted_valid ? e->muted : FALSE; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute); + + pa_xfree(e); +} + +static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + struct dbus_entry *de = userdata; + dbus_bool_t mute; + struct entry *e; + pa_bool_t updated; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(de); + + dbus_message_iter_get_basic(iter, &mute); + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + updated = !e->muted_valid || e->muted != mute; + + if (updated) { + pa_datum key; + pa_datum value; + + e->muted = mute; + e->muted_valid = TRUE; + + key.data = de->entry_name; + key.size = strlen(de->entry_name); + value.data = e; + value.size = sizeof(struct entry); + pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0); + + send_mute_updated_signal(de, e); + trigger_save(de->userdata); + } + + pa_dbus_send_empty_reply(conn, msg); + + pa_xfree(e); +} + +static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + struct entry *e; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + const char *device; + dbus_bool_t mute; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + + device = e->device_valid ? e->device : ""; + mute = e->muted_valid ? e->muted : FALSE; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name)); + append_volume_variant(&dict_entry_iter, e); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(e); +} + +static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + pa_datum key; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + key.data = de->entry_name; + key.size = strlen(de->entry_name); + + pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0); + + send_entry_removed_signal(de); + trigger_save(de->userdata); + + pa_assert_se(pa_hashmap_remove(de->userdata->dbus_entries, de->entry_name)); + dbus_entry_free(de); + + pa_dbus_send_empty_reply(conn, msg); +} + +#endif /* HAVE_DBUS */ + static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; @@ -162,7 +1004,7 @@ static char *get_name(pa_proplist *p, const char *prefix) { return t; } -static struct entry* read_entry(struct userdata *u, const char *name) { +static struct entry *read_entry(struct userdata *u, const char *name) { pa_datum key, data; struct entry *e; @@ -196,11 +1038,21 @@ static struct entry* read_entry(struct userdata *u, const char *name) { goto fail; } + if (!memchr(e->card, 0, sizeof(e->card))) { + pa_log_warn("Database contains entry for stream %s with missing NUL byte in card name", name); + goto fail; + } + if (e->device_valid && !pa_namereg_is_valid_name(e->device)) { pa_log_warn("Invalid device name stored in database for stream %s", name); goto fail; } + if (e->card_valid && !pa_namereg_is_valid_name(e->card)) { + pa_log_warn("Invalid card name stored in database for stream %s", name); + goto fail; + } + if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) { pa_log_warn("Invalid channel map stored in database for stream %s", name); goto fail; @@ -252,6 +1104,10 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { (a->device_valid && strncmp(a->device, b->device, sizeof(a->device)))) return FALSE; + if (a->card_valid != b->card_valid || + (a->card_valid && strncmp(a->card, b->card, sizeof(a->card)))) + return FALSE; + if (a->muted_valid != b->muted_valid || (a->muted_valid && (a->muted != b->muted))) return FALSE; @@ -266,10 +1122,21 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; - struct entry entry, *old; - char *name; + struct entry entry, *old = NULL; + char *name = NULL; pa_datum key, data; + /* These are only used when D-Bus is enabled, but in order to reduce ifdef + * clutter these are defined here unconditionally. */ + pa_bool_t created_new_entry = TRUE; + pa_bool_t device_updated = FALSE; + pa_bool_t volume_updated = FALSE; + pa_bool_t mute_updated = FALSE; + +#ifdef HAVE_DBUS + struct dbus_entry *de = NULL; +#endif + pa_assert(c); pa_assert(u); @@ -291,23 +1158,38 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!(name = get_name(sink_input->proplist, "sink-input"))) return; - if ((old = read_entry(u, name))) + if ((old = read_entry(u, name))) { entry = *old; + created_new_entry = FALSE; + } if (sink_input->save_volume) { entry.channel_map = sink_input->channel_map; pa_sink_input_get_volume(sink_input, &entry.volume, FALSE); entry.volume_valid = TRUE; + + volume_updated = !created_new_entry + && (!old->volume_valid + || !pa_channel_map_equal(&entry.channel_map, &old->channel_map) + || !pa_cvolume_equal(&entry.volume, &old->volume)); } if (sink_input->save_muted) { entry.muted = pa_sink_input_get_mute(sink_input); entry.muted_valid = TRUE; + + mute_updated = !created_new_entry && (!old->muted_valid || entry.muted != old->muted); } if (sink_input->save_sink) { pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device)); entry.device_valid = TRUE; + + device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device)); + if (sink_input->sink->card) { + pa_strlcpy(entry.card, sink_input->sink->card->name, sizeof(entry.card)); + entry.card_valid = TRUE; + } } } else { @@ -321,12 +1203,21 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!(name = get_name(source_output->proplist, "source-output"))) return; - if ((old = read_entry(u, name))) + if ((old = read_entry(u, name))) { entry = *old; + created_new_entry = FALSE; + } if (source_output->save_source) { pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device)); entry.device_valid = source_output->save_source; + + device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device)); + + if (source_output->source->card) { + pa_strlcpy(entry.card, source_output->source->card->name, sizeof(entry.card)); + entry.card_valid = TRUE; + } } } @@ -351,6 +1242,23 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_database_set(u->database, &key, &data, TRUE); +#ifdef HAVE_DBUS + if (created_new_entry) { + de = dbus_entry_new(u, name); + pa_hashmap_put(u->dbus_entries, de->entry_name, de); + send_new_entry_signal(de); + } else { + pa_assert((de = pa_hashmap_get(u->dbus_entries, name))); + + if (device_updated) + send_device_updated_signal(de, &entry); + if (volume_updated) + send_volume_updated_signal(de, &entry); + if (mute_updated) + send_mute_updated_signal(de, &entry); + } +#endif + pa_xfree(name); trigger_save(u); @@ -368,19 +1276,28 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n if (!(name = get_name(new_data->proplist, "sink-input"))) return PA_HOOK_OK; - if ((e = read_entry(u, name))) { + if (new_data->sink) + pa_log_debug("Not restoring device for stream %s, because already set.", name); + else if ((e = read_entry(u, name))) { + pa_sink *s = NULL; - if (e->device_valid) { - pa_sink *s; + if (e->device_valid) + s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK); - if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK))) { - if (!new_data->sink) { - pa_log_info("Restoring device for stream %s.", name); - new_data->sink = s; - new_data->save_sink = TRUE; - } else - pa_log_debug("Not restoring device for stream %s, because already set.", name); - } + if (!s && e->card_valid) { + pa_card *card; + + if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD))) + s = pa_idxset_first(card->sinks, NULL); + } + + /* 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 */ + if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) { + pa_log_info("Restoring device for stream %s.", name); + new_data->sink = s; + new_data->save_sink = TRUE; } pa_xfree(e); @@ -455,18 +1372,28 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou if (!(name = get_name(new_data->proplist, "source-output"))) return PA_HOOK_OK; - if ((e = read_entry(u, name))) { - pa_source *s; + if (new_data->source) + pa_log_debug("Not restoring device for stream %s, because already set", name); + else if ((e = read_entry(u, name))) { + pa_source *s = NULL; - if (e->device_valid) { - if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE))) { - if (!new_data->source) { - pa_log_info("Restoring device for stream %s.", name); - new_data->source = s; - new_data->save_source = TRUE; - } else - pa_log_debug("Not restoring device for stream %s, because already set", name); - } + if (e->device_valid) + s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE); + + if (!s && e->card_valid) { + pa_card *card; + + if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD))) + s = pa_idxset_first(card->sources, NULL); + } + + /* 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 */ + if (s && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) { + pa_log_info("Restoring device for stream %s.", name); + new_data->source = s; + new_data->save_source = TRUE; } pa_xfree(e); @@ -496,6 +1423,17 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct if (si->save_sink) continue; + /* Skip this if it is already in the process of being moved + * anyway */ + if (!si->sink) + continue; + + /* 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 */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + continue; + if (!(name = get_name(si->proplist, "sink-input"))) continue; @@ -534,6 +1472,16 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, if (so->direct_on_input) continue; + /* Skip this if it is already in the process of being moved anyway */ + if (!so->source) + continue; + + /* 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 */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + continue; + if (!(name = get_name(so->proplist, "source-input"))) continue; @@ -567,6 +1515,9 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str char *name; struct entry *e; + if (!si->sink) + continue; + if (!(name = get_name(si->proplist, "sink-input"))) continue; @@ -575,7 +1526,9 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str if (e->device_valid) { pa_sink *d; - if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && d != sink) + if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && + d != sink && + PA_SINK_IS_LINKED(pa_sink_get_state(d))) pa_sink_input_move_to(si, d, TRUE); } @@ -605,6 +1558,12 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc char *name; struct entry *e; + if (so->direct_on_input) + continue; + + if (!so->source) + continue; + if (!(name = get_name(so->proplist, "source-output"))) continue; @@ -613,7 +1572,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc if (e->device_valid) { pa_source *d; - if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && d != source) + if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && + d != source && + PA_SOURCE_IS_LINKED(pa_source_get_state(d))) pa_source_output_move_to(so, d, TRUE); } @@ -811,14 +1772,27 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio mode != PA_UPDATE_SET) goto fail; - if (mode == PA_UPDATE_SET) + if (mode == PA_UPDATE_SET) { +#ifdef HAVE_DBUS + struct dbus_entry *de; + void *state = NULL; + + PA_HASHMAP_FOREACH(de, u->dbus_entries, state) { + send_entry_removed_signal(de); + dbus_entry_free(pa_hashmap_remove(u->dbus_entries, de->entry_name)); + } +#endif pa_database_clear(u->database); + } while (!pa_tagstruct_eof(t)) { const char *name, *device; pa_bool_t muted; struct entry entry; pa_datum key, data; +#ifdef HAVE_DBUS + struct entry *old; +#endif pa_zero(entry); entry.version = ENTRY_VERSION; @@ -850,15 +1824,54 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio !pa_namereg_is_valid_name(entry.device)) goto fail; +#ifdef HAVE_DBUS + old = read_entry(u, name); +#endif + key.data = (char*) name; key.size = strlen(name); data.data = &entry; data.size = sizeof(entry); - if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) + pa_log_debug("Client %s changes entry %s.", + pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)), + name); + + if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) { +#ifdef HAVE_DBUS + struct dbus_entry *de; + + if (old) { + pa_assert_se((de = pa_hashmap_get(u->dbus_entries, name))); + + if ((old->device_valid != entry.device_valid) + || (entry.device_valid && !pa_streq(entry.device, old->device))) + send_device_updated_signal(de, &entry); + + if ((old->volume_valid != entry.volume_valid) + || (entry.volume_valid && (!pa_cvolume_equal(&entry.volume, &old->volume) + || !pa_channel_map_equal(&entry.channel_map, &old->channel_map)))) + send_volume_updated_signal(de, &entry); + + if (!old->muted_valid || (entry.muted != old->muted)) + send_mute_updated_signal(de, &entry); + + } else { + de = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de)); + send_new_entry_signal(de); + } +#endif + if (apply_immediately) apply_entry(u, name, &entry); + } + +#ifdef HAVE_DBUS + if (old) + pa_xfree(old); +#endif } trigger_save(u); @@ -871,10 +1884,20 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio while (!pa_tagstruct_eof(t)) { const char *name; pa_datum key; +#ifdef HAVE_DBUS + struct dbus_entry *de; +#endif if (pa_tagstruct_gets(t, &name) < 0) goto fail; +#ifdef HAVE_DBUS + if ((de = pa_hashmap_get(u->dbus_entries, name))) { + send_entry_removed_signal(de); + dbus_entry_free(pa_hashmap_remove(u->dbus_entries, name)); + } +#endif + key.data = (char*) name; key.size = strlen(name); @@ -933,6 +1956,10 @@ int pa__init(pa_module*m) { pa_source_output *so; uint32_t idx; pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE, on_hotplug = TRUE, on_rescue = TRUE; +#ifdef HAVE_DBUS + pa_datum key; + pa_bool_t done; +#endif pa_assert(m); @@ -1003,6 +2030,34 @@ int pa__init(pa_module*m) { pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); +#ifdef HAVE_DBUS + u->dbus_protocol = pa_dbus_protocol_get(u->core); + u->dbus_entries = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, OBJECT_PATH, &stream_restore_interface_info, u) >= 0); + pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0); + + /* Create the initial dbus entries. */ + done = !pa_database_first(u->database, &key, NULL); + while (!done) { + pa_datum next_key; + char *name; + struct dbus_entry *de; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); + + de = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) >= 0); + + pa_xfree(name); + + key = next_key; + } +#endif + PA_IDXSET_FOREACH(si, m->core->sink_inputs, idx) subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, si->index, u); @@ -1021,6 +2076,16 @@ fail: return -1; } +#ifdef HAVE_DBUS +static void free_dbus_entry_cb(void *p, void *userdata) { + struct dbus_entry *de = p; + + pa_assert(de); + + dbus_entry_free(de); +} +#endif + void pa__done(pa_module*m) { struct userdata* u; @@ -1029,6 +2094,19 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; +#ifdef HAVE_DBUS + if (u->dbus_protocol) { + pa_assert(u->dbus_entries); + + pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0); + pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, OBJECT_PATH, stream_restore_interface_info.name) >= 0); + + pa_hashmap_free(u->dbus_entries, free_dbus_entry_cb, NULL); + + pa_dbus_protocol_unref(u->dbus_protocol); + } +#endif + if (u->subscription) pa_subscription_free(u->subscription); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 70a7b049..7adaa0b1 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -145,6 +145,9 @@ static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_ pa_assert(data); pa_assert(u); + if (data->flags & PA_SINK_INPUT_START_CORKED) + return PA_HOOK_OK; + if ((d = pa_hashmap_get(u->device_infos, data->sink))) resume(d); @@ -158,6 +161,9 @@ static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_outpu pa_assert(data); pa_assert(u); + if (data->flags & PA_SOURCE_OUTPUT_START_CORKED) + return PA_HOOK_OK; + if (data->source->monitor_of) d = pa_hashmap_get(u->device_infos, data->source->monitor_of); else @@ -226,11 +232,16 @@ static pa_hook_result_t sink_input_move_start_hook_cb(pa_core *c, pa_sink_input static pa_hook_result_t sink_input_move_finish_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { struct device_info *d; + pa_sink_input_state_t state; pa_assert(c); pa_sink_input_assert_ref(s); pa_assert(u); + state = pa_sink_input_get_state(s); + if (state != PA_SINK_INPUT_RUNNING && state != PA_SINK_INPUT_DRAINED) + return PA_HOOK_OK; + if ((d = pa_hashmap_get(u->device_infos, s->sink))) resume(d); @@ -265,6 +276,9 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_ pa_source_output_assert_ref(s); pa_assert(u); + if (pa_source_output_get_state(s) != PA_SOURCE_OUTPUT_RUNNING) + return PA_HOOK_OK; + if (s->source->monitor_of) d = pa_hashmap_get(u->device_infos, s->source->monitor_of); else @@ -279,6 +293,7 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { struct device_info *d; pa_sink_input_state_t state; + pa_assert(c); pa_sink_input_assert_ref(s); pa_assert(u); @@ -292,15 +307,11 @@ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_inp } static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { - pa_source_output_state_t state; - pa_assert(c); pa_source_output_assert_ref(s); pa_assert(u); - state = pa_source_output_get_state(s); - - if (state == PA_SOURCE_OUTPUT_RUNNING) { + if (pa_source_output_get_state(s) == PA_SOURCE_OUTPUT_RUNNING) { struct device_info *d; if (s->source->monitor_of) @@ -387,22 +398,17 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s pa_sink *s = PA_SINK(o); pa_sink_state_t state = pa_sink_get_state(s); - if (pa_sink_check_suspend(s) <= 0) { - + if (pa_sink_check_suspend(s) <= 0) if (PA_SINK_IS_OPENED(state)) restart(d); - } - } else if (pa_source_isinstance(o)) { pa_source *s = PA_SOURCE(o); pa_source_state_t state = pa_source_get_state(s); - if (pa_source_check_suspend(s) <= 0) { - + if (pa_source_check_suspend(s) <= 0) if (PA_SOURCE_IS_OPENED(state)) restart(d); - } } return PA_HOOK_OK; diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index f788f660..c97de3a1 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -332,7 +332,7 @@ static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa static void command_stream_buffer_attr_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; - uint32_t channel, maxlength, tlength, fragsize, prebuf, minreq; + uint32_t channel, maxlength, tlength = 0, fragsize, prebuf, minreq; pa_usec_t usec; pa_assert(pd); @@ -1069,6 +1069,33 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t } } + if (u->version >= 16) { + uint32_t n_ports; + const char *s; + + if (pa_tagstruct_getu32(t, &n_ports)) { + pa_log("Parse failure"); + goto fail; + } + + for (uint32_t j = 0; j < n_ports; j++) { + uint32_t priority; + + if (pa_tagstruct_gets(t, &s) < 0 || /* name */ + pa_tagstruct_gets(t, &s) < 0 || /* description */ + pa_tagstruct_getu32(t, &priority) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (pa_tagstruct_gets(t, &s) < 0) { /* active port */ + pa_log("Parse failure"); + goto fail; + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1097,7 +1124,7 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag uint32_t idx, owner_module, client, sink; pa_usec_t buffer_usec, sink_usec; const char *name, *driver, *resample_method; - pa_bool_t mute; + pa_bool_t mute = FALSE; pa_sample_spec sample_spec; pa_channel_map channel_map; pa_cvolume volume; @@ -1162,13 +1189,13 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag pa_assert(u->sink); if ((u->version < 11 || !!mute == !!u->sink->muted) && - pa_cvolume_equal(&volume, &u->sink->virtual_volume)) + pa_cvolume_equal(&volume, &u->sink->real_volume)) return; - pa_sink_volume_changed(u->sink, &volume, FALSE); + pa_sink_volume_changed(u->sink, &volume); if (u->version >= 11) - pa_sink_mute_changed(u->sink, mute, FALSE); + pa_sink_mute_changed(u->sink, mute); return; @@ -1245,6 +1272,33 @@ static void source_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa } } + if (u->version >= 16) { + uint32_t n_ports; + const char *s; + + if (pa_tagstruct_getu32(t, &n_ports)) { + pa_log("Parse failure"); + goto fail; + } + + for (uint32_t j = 0; j < n_ports; j++) { + uint32_t priority; + + if (pa_tagstruct_gets(t, &s) < 0 || /* name */ + pa_tagstruct_gets(t, &s) < 0 || /* description */ + pa_tagstruct_getu32(t, &priority) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (pa_tagstruct_gets(t, &s) < 0) { /* active port */ + pa_log("Parse failure"); + goto fail; + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1345,12 +1399,11 @@ static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32 /* Called from main context */ static void start_subscribe(struct userdata *u) { pa_tagstruct *t; - uint32_t tag; pa_assert(u); t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE); - pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER| #ifdef TUNNEL_SINK PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK @@ -1526,7 +1579,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t reply = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME); - pa_tagstruct_putu32(reply, tag = u->ctag++); + pa_tagstruct_putu32(reply, u->ctag++); if (u->version >= 13) { pa_proplist *pl; @@ -1753,7 +1806,6 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata static void sink_set_volume(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; - uint32_t tag; pa_assert(sink); u = sink->userdata; @@ -1761,9 +1813,9 @@ static void sink_set_volume(pa_sink *sink) { t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME); - pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, u->device_index); - pa_tagstruct_put_cvolume(t, &sink->virtual_volume); + pa_tagstruct_put_cvolume(t, &sink->real_volume); pa_pstream_send_tagstruct(u->pstream, t); } @@ -1771,7 +1823,6 @@ static void sink_set_volume(pa_sink *sink) { static void sink_set_mute(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; - uint32_t tag; pa_assert(sink); u = sink->userdata; @@ -1782,7 +1833,7 @@ static void sink_set_mute(pa_sink *sink) { t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE); - pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, u->device_index); pa_tagstruct_put_boolean(t, !!sink->muted); pa_pstream_send_tagstruct(u->pstream, t); diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c index 11de1ccb..a12f7c93 100644 --- a/src/modules/module-udev-detect.c +++ b/src/modules/module-udev-detect.c @@ -25,13 +25,17 @@ #include <errno.h> #include <limits.h> +#include <dirent.h> #include <sys/inotify.h> #include <libudev.h> +#include <pulse/timeval.h> + #include <pulsecore/modargs.h> #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> #include <pulsecore/namereg.h> +#include <pulsecore/ratelimit.h> #include "module-udev-detect-symdef.h" @@ -39,18 +43,25 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "tsched=<enable system timer based scheduling mode?> " + "ignore_dB=<ignore dB information from the device?>"); struct device { char *path; - pa_bool_t accessible; + pa_bool_t need_verify; char *card_name; + char *args; uint32_t module; + pa_ratelimit ratelimit; }; struct userdata { pa_core *core; pa_hashmap *devices; - pa_bool_t use_tsched; + + pa_bool_t use_tsched:1; + pa_bool_t ignore_dB:1; struct udev* udev; struct udev_monitor *monitor; @@ -62,6 +73,7 @@ struct userdata { static const char* const valid_modargs[] = { "tsched", + "ignore_dB", NULL }; @@ -72,6 +84,7 @@ static void device_free(struct device *d) { pa_xfree(d->path); pa_xfree(d->card_name); + pa_xfree(d->args); pa_xfree(d); } @@ -90,30 +103,196 @@ static const char *path_get_card_id(const char *path) { return e + 5; } +static pa_bool_t is_card_busy(const char *id) { + char *card_path = NULL, *pcm_path = NULL, *sub_status = NULL; + DIR *card_dir = NULL, *pcm_dir = NULL; + FILE *status_file = NULL; + size_t len; + struct dirent *space = NULL, *de; + pa_bool_t busy = FALSE; + int r; + + pa_assert(id); + + /* This simply uses /proc/asound/card.../pcm.../sub.../status to + * check whether there is still a process using this audio device. */ + + card_path = pa_sprintf_malloc("/proc/asound/card%s", id); + + if (!(card_dir = opendir(card_path))) { + pa_log_warn("Failed to open %s: %s", card_path, pa_cstrerror(errno)); + goto fail; + } + + len = offsetof(struct dirent, d_name) + fpathconf(dirfd(card_dir), _PC_NAME_MAX) + 1; + space = pa_xmalloc(len); + + for (;;) { + de = NULL; + + if ((r = readdir_r(card_dir, space, &de)) != 0) { + pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r)); + goto fail; + } + + if (!de) + break; + + if (!pa_startswith(de->d_name, "pcm")) + continue; + + pa_xfree(pcm_path); + pcm_path = pa_sprintf_malloc("%s/%s", card_path, de->d_name); + + if (pcm_dir) + closedir(pcm_dir); + + if (!(pcm_dir = opendir(pcm_path))) { + pa_log_warn("Failed to open %s: %s", pcm_path, pa_cstrerror(errno)); + continue; + } + + for (;;) { + char line[32]; + + if ((r = readdir_r(pcm_dir, space, &de)) != 0) { + pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r)); + goto fail; + } + + if (!de) + break; + + if (!pa_startswith(de->d_name, "sub")) + continue; + + pa_xfree(sub_status); + sub_status = pa_sprintf_malloc("%s/%s/status", pcm_path, de->d_name); + + if (status_file) + fclose(status_file); + + if (!(status_file = pa_fopen_cloexec(sub_status, "r"))) { + pa_log_warn("Failed to open %s: %s", sub_status, pa_cstrerror(errno)); + continue; + } + + if (!(fgets(line, sizeof(line)-1, status_file))) { + pa_log_warn("Failed to read from %s: %s", sub_status, pa_cstrerror(errno)); + continue; + } + + if (!pa_streq(line, "closed\n")) { + busy = TRUE; + break; + } + } + } + +fail: + + pa_xfree(card_path); + pa_xfree(pcm_path); + pa_xfree(sub_status); + pa_xfree(space); + + if (card_dir) + closedir(card_dir); + + if (pcm_dir) + closedir(pcm_dir); + + if (status_file) + fclose(status_file); + + return busy; +} + static void verify_access(struct userdata *u, struct device *d) { char *cd; pa_card *card; + pa_bool_t accessible; pa_assert(u); pa_assert(d); - if (!(card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) - return; - cd = pa_sprintf_malloc("%s/snd/controlC%s", udev_get_dev_path(u->udev), path_get_card_id(d->path)); - d->accessible = access(cd, W_OK) >= 0; - pa_log_info("%s is accessible: %s", cd, pa_yes_no(d->accessible)); + accessible = access(cd, R_OK|W_OK) >= 0; + pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible)); + pa_xfree(cd); - pa_card_suspend(card, !d->accessible, PA_SUSPEND_SESSION); + if (d->module == PA_INVALID_INDEX) { + + /* If we are not loaded, try to load */ + + if (accessible) { + pa_module *m; + pa_bool_t busy; + + /* Check if any of the PCM devices that belong to this + * card are currently busy. If they are, don't try to load + * right now, to make sure the probing phase can + * successfully complete. When the current user of the + * device closes it we will get another notification via + * inotify and can then recheck. */ + + busy = is_card_busy(path_get_card_id(d->path)); + pa_log_debug("%s is busy: %s", d->path, pa_yes_no(busy)); + + if (!busy) { + + /* So, why do we rate limit here? It's certainly ugly, + * but there seems to be no other way. Problem is + * this: if we are unable to configure/probe an audio + * device after opening it we will close it again and + * the module initialization will fail. This will then + * cause an inotify event on the device node which + * will be forwarded to us. We then try to reopen the + * audio device again, practically entering a busy + * loop. + * + * A clean fix would be if we would be able to ignore + * our own inotify close events. However, inotify + * lacks such functionality. Also, during probing of + * the device we cannot really distuingish between + * other processes causing EBUSY or ourselves, which + * means we have no way to figure out if the probing + * during opening was canceled by a "try again" + * failure or a "fatal" failure. */ + + if (pa_ratelimit_test(&d->ratelimit)) { + pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args); + m = pa_module_load(u->core, "module-alsa-card", d->args); + + if (m) { + d->module = m->index; + pa_log_info("Card %s (%s) module loaded.", d->path, d->card_name); + } else + pa_log_info("Card %s (%s) failed to load module.", d->path, d->card_name); + } else + pa_log_warn("Tried to configure %s (%s) more often than %u times in %llus", + d->path, + d->card_name, + d->ratelimit.burst, + (long long unsigned) (d->ratelimit.interval / PA_USEC_PER_SEC)); + } + } + + } else { + + /* If we are already loaded update suspend status with + * accessible boolean */ + + if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) + pa_card_suspend(card, !accessible, PA_SUSPEND_SESSION); + } } static void card_changed(struct userdata *u, struct udev_device *dev) { struct device *d; const char *path; const char *t; - char *card_name, *args; - pa_module *m; char *n; pa_assert(u); @@ -129,42 +308,34 @@ static void card_changed(struct userdata *u, struct udev_device *dev) { return; } + d = pa_xnew0(struct device, 1); + d->path = pa_xstrdup(path); + d->module = PA_INVALID_INDEX; + PA_INIT_RATELIMIT(d->ratelimit, 10*PA_USEC_PER_SEC, 5); + if (!(t = udev_device_get_property_value(dev, "PULSE_NAME"))) if (!(t = udev_device_get_property_value(dev, "ID_ID"))) if (!(t = udev_device_get_property_value(dev, "ID_PATH"))) t = path_get_card_id(path); n = pa_namereg_make_valid_name(t); + d->card_name = pa_sprintf_malloc("alsa_card.%s", n); + d->args = pa_sprintf_malloc("device_id=\"%s\" " + "name=\"%s\" " + "card_name=\"%s\" " + "tsched=%s " + "ignore_dB=%s " + "card_properties=\"module-udev-detect.discovered=1\"", + path_get_card_id(path), + n, + d->card_name, + pa_yes_no(u->use_tsched), + pa_yes_no(u->ignore_dB)); + pa_xfree(n); - card_name = pa_sprintf_malloc("alsa_card.%s", n); - args = pa_sprintf_malloc("device_id=\"%s\" " - "name=\"%s\" " - "card_name=\"%s\" " - "tsched=%i " - "card_properties=\"module-udev-detect.discovered=1\"", - path_get_card_id(path), - n, - card_name, - (int) u->use_tsched); - - pa_log_debug("Loading module-alsa-card with arguments '%s'", args); - m = pa_module_load(u->core, "module-alsa-card", args); - pa_xfree(args); - - if (m) { - pa_log_info("Card %s (%s) added.", path, n); - - d = pa_xnew(struct device, 1); - d->path = pa_xstrdup(path); - d->card_name = card_name; - d->module = m->index; - d->accessible = TRUE; - - pa_hashmap_put(u->devices, d->path, d); - } else - pa_xfree(card_name); + pa_hashmap_put(u->devices, d->path, d); - pa_xfree(n); + verify_access(u, d); } static void remove_card(struct userdata *u, struct udev_device *dev) { @@ -177,7 +348,10 @@ static void remove_card(struct userdata *u, struct udev_device *dev) { return; pa_log_info("Card %s removed.", d->path); - pa_module_unload_request_by_index(u->core, d->module, TRUE); + + if (d->module != PA_INVALID_INDEX) + pa_module_unload_request_by_index(u->core, d->module, TRUE); + device_free(d); } @@ -254,6 +428,34 @@ fail: u->udev_io = NULL; } +static pa_bool_t pcm_node_belongs_to_device( + struct device *d, + const char *node) { + + char *cd; + pa_bool_t b; + + cd = pa_sprintf_malloc("pcmC%sD", path_get_card_id(d->path)); + b = pa_startswith(node, cd); + pa_xfree(cd); + + return b; +} + +static pa_bool_t control_node_belongs_to_device( + struct device *d, + const char *node) { + + char *cd; + pa_bool_t b; + + cd = pa_sprintf_malloc("controlC%s", path_get_card_id(d->path)); + b = pa_streq(node, cd); + pa_xfree(cd); + + return b; +} + static void inotify_cb( pa_mainloop_api*a, pa_io_event* e, @@ -267,10 +469,13 @@ static void inotify_cb( } buf; struct userdata *u = userdata; static int type = 0; - pa_bool_t verify = FALSE, deleted = FALSE; + pa_bool_t deleted = FALSE; + struct device *d; + void *state; for (;;) { ssize_t r; + struct inotify_event *event; pa_zero(buf); if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) { @@ -282,22 +487,51 @@ static void inotify_cb( goto fail; } - if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC")) - verify = TRUE; - - if ((buf.e.mask & (IN_DELETE_SELF|IN_MOVE_SELF))) - deleted = TRUE; + event = &buf.e; + while (r > 0) { + size_t len; + + if ((size_t) r < sizeof(struct inotify_event)) { + pa_log("read() too short."); + goto fail; + } + + len = sizeof(struct inotify_event) + event->len; + + if ((size_t) r < len) { + pa_log("Payload missing."); + goto fail; + } + + /* From udev we get the guarantee that the control + * device's ACL is changed last. To avoid races when ACLs + * are changed we hence watch only the control device */ + if (((event->mask & IN_ATTRIB) && pa_startswith(event->name, "controlC"))) + PA_HASHMAP_FOREACH(d, u->devices, state) + if (control_node_belongs_to_device(d, event->name)) + d->need_verify = TRUE; + + /* ALSA doesn't really give us any guarantee on the closing + * order, so let's simply hope */ + if (((event->mask & IN_CLOSE_WRITE) && pa_startswith(event->name, "pcmC"))) + PA_HASHMAP_FOREACH(d, u->devices, state) + if (pcm_node_belongs_to_device(d, event->name)) + d->need_verify = TRUE; + + /* /dev/snd/ might have been removed */ + if ((event->mask & (IN_DELETE_SELF|IN_MOVE_SELF))) + deleted = TRUE; + + event = (struct inotify_event*) ((uint8_t*) event + len); + r -= len; + } } - if (verify) { - struct device *d; - void *state; - - pa_log_debug("Verifying access."); - - PA_HASHMAP_FOREACH(d, u->devices, state) + PA_HASHMAP_FOREACH(d, u->devices, state) + if (d->need_verify) { + d->need_verify = FALSE; verify_access(u, d); - } + } if (!deleted) return; @@ -327,7 +561,7 @@ static int setup_inotify(struct userdata *u) { } dev_snd = pa_sprintf_malloc("%s/snd", udev_get_dev_path(u->udev)); - r = inotify_add_watch(u->inotify_fd, dev_snd, IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF); + r = inotify_add_watch(u->inotify_fd, dev_snd, IN_ATTRIB|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF); pa_xfree(dev_snd); if (r < 0) { @@ -364,6 +598,7 @@ int pa__init(pa_module *m) { struct udev_enumerate *enumerate = NULL; struct udev_list_entry *item = NULL, *first = NULL; int fd; + pa_bool_t use_tsched = TRUE, ignore_dB = FALSE; pa_assert(m); @@ -375,13 +610,19 @@ int pa__init(pa_module *m) { m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - u->use_tsched = TRUE; u->inotify_fd = -1; - if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) { - pa_log("Failed to parse tsched argument."); + if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { + pa_log("Failed to parse tsched= argument."); + goto fail; + } + u->use_tsched = use_tsched; + + if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) { + pa_log("Failed to parse ignore_dB= argument."); goto fail; } + u->ignore_dB = ignore_dB; if (!(u->udev = udev_new())) { pa_log("Failed to initialize udev library."); @@ -434,7 +675,7 @@ int pa__init(pa_module *m) { udev_enumerate_unref(enumerate); - pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices)); + pa_log_info("Found %u cards.", pa_hashmap_size(u->devices)); pa_modargs_free(ma); diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 91da598e..6e484eae 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -48,6 +48,7 @@ static const char* const valid_modargs[] = { int pa__init(pa_module*m) { pa_modargs *ma = NULL; pa_bool_t restore_device = TRUE, restore_volume = TRUE; + pa_module *n; char *t; pa_assert(m); @@ -66,13 +67,15 @@ int pa__init(pa_module*m) { pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration."); t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device)); - pa_module_load(m->core, "module-stream-restore", t); + n = pa_module_load(m->core, "module-stream-restore", t); pa_xfree(t); - pa_module_unload_request(m, TRUE); + if (n) + pa_module_unload_request(m, TRUE); pa_modargs_free(ma); - return 0; + + return n ? 0 : -1; fail: if (ma) diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index c44b882b..71536260 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -812,11 +812,11 @@ static void sink_get_volume(pa_sink *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); if (u->mixer_devmask & SOUND_MASK_VOLUME) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_PCM) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); @@ -830,11 +830,11 @@ static void sink_set_volume(pa_sink *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); if (u->mixer_devmask & SOUND_MASK_VOLUME) - if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_PCM) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); @@ -848,11 +848,11 @@ static void source_get_volume(pa_source *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); if (u->mixer_devmask & SOUND_MASK_IGAIN) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_RECLEV) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume) >= 0) return; pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); @@ -866,11 +866,11 @@ static void source_set_volume(pa_source *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); if (u->mixer_devmask & SOUND_MASK_IGAIN) - if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_RECLEV) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume) >= 0) return; pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); diff --git a/src/modules/oss/oss-util.c b/src/modules/oss/oss-util.c index 5a109ae9..b95023c3 100644 --- a/src/modules/oss/oss-util.c +++ b/src/modules/oss/oss-util.c @@ -55,7 +55,7 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { pcaps = ∩︀ if (*mode == O_RDWR) { - if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { + if ((fd = pa_open_cloexec(device, O_RDWR|O_NDELAY, 0)) >= 0) { ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { @@ -71,14 +71,14 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { pa_close(fd); } - if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) { - if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = pa_open_cloexec(device, (*mode = O_WRONLY)|O_NDELAY, 0)) < 0) { + if ((fd = pa_open_cloexec(device, (*mode = O_RDONLY)|O_NDELAY, 0)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } } } else { - if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = pa_open_cloexec(device, *mode|O_NDELAY, 0)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } @@ -145,8 +145,6 @@ success: pa_log_debug("capabilities:%s", t); pa_xfree(t); - pa_make_fd_cloexec(fd); - return fd; fail: @@ -351,9 +349,9 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { if ((n = get_device_number(dev)) < 0) return -1; - if (!(f = fopen("/dev/sndstat", "r")) && - !(f = fopen("/proc/sndstat", "r")) && - !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) { if (errno != ENOENT) pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); @@ -403,7 +401,7 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { static int open_mixer(const char *mixer) { int fd; - if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) + if ((fd = pa_open_cloexec(mixer, O_RDWR|O_NDELAY, 0)) >= 0) return fd; return -1; diff --git a/src/modules/raop/base64.c b/src/modules/raop/base64.c index e1cbed02..5b061034 100644 --- a/src/modules/raop/base64.c +++ b/src/modules/raop/base64.c @@ -57,7 +57,6 @@ int pa_base64_encode(const void *data, int size, char **str) p = s = pa_xnew(char, size * 4 / 3 + 4); q = (const unsigned char *) data; - i = 0; for (i = 0; i < size;) { c = q[i++]; c *= 256; diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c index eaeb77fc..adba8e4f 100644 --- a/src/modules/raop/module-raop-discover.c +++ b/src/modules/raop/module-raop-discover.c @@ -265,7 +265,7 @@ static void browser_cb( struct tunnel *t2; if ((t2 = pa_hashmap_get(u->tunnels, t))) { - pa_module_unload_by_index(u->core, t2->module_index, TRUE); + pa_module_unload_request_by_index(u->core, t2->module_index, TRUE); pa_hashmap_remove(u->tunnels, t2); tunnel_free(t2); } @@ -386,7 +386,7 @@ void pa__done(pa_module*m) { struct tunnel *t; while ((t = pa_hashmap_steal_first(u->tunnels))) { - pa_module_unload_by_index(u->core, t->module_index, TRUE); + pa_module_unload_request_by_index(u->core, t->module_index, TRUE); tunnel_free(t); } diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c index 9699132d..ac48ab10 100644 --- a/src/modules/raop/module-raop-sink.c +++ b/src/modules/raop/module-raop-sink.c @@ -283,15 +283,15 @@ static void sink_set_volume_cb(pa_sink *s) { /* Calculate the max volume of all channels. We'll use this as our (single) volume on the APEX device and emulate any variation in channel volumes in software */ - v = pa_cvolume_max(&s->virtual_volume); + v = pa_cvolume_max(&s->real_volume); /* Create a pa_cvolume version of our single value */ pa_cvolume_set(&hw, s->sample_spec.channels, v); /* Perform any software manipulation of the volume needed */ - pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &hw); + pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw); - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume)); + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume)); pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &hw)); pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume)); diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c index 13ecde2b..ab453e61 100644 --- a/src/modules/reserve-monitor.c +++ b/src/modules/reserve-monitor.c @@ -38,6 +38,7 @@ struct rm_monitor { char *device_name; char *service_name; + char *match; DBusConnection *connection; @@ -51,12 +52,18 @@ struct rm_monitor { #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." +#define SERVICE_FILTER \ + "type='signal'," \ + "sender='" DBUS_SERVICE_DBUS "'," \ + "interface='" DBUS_INTERFACE_DBUS "'," \ + "member='NameOwnerChanged'," \ + "arg0='%s'" + static DBusHandlerResult filter_handler( DBusConnection *c, DBusMessage *s, void *userdata) { - DBusMessage *reply; rm_monitor *m; DBusError error; @@ -97,31 +104,10 @@ static DBusHandlerResult filter_handler( } } - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - invalid: - if (!(reply = dbus_message_new_error( - s, - DBUS_ERROR_INVALID_ARGS, - "Invalid arguments"))) - goto oom; - - if (!dbus_connection_send(c, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - dbus_error_free(&error); - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } int rm_watch( @@ -175,11 +161,13 @@ int rm_watch( m->filtering = 1; - dbus_bus_add_match(m->connection, - "type='signal'," - "sender='" DBUS_SERVICE_DBUS "'," - "interface='" DBUS_INTERFACE_DBUS "'," - "member='NameOwnerChanged'", error); + if (!(m->match = malloc(sizeof(SERVICE_FILTER) - 2 + strlen(m->service_name)))) { + r = -ENOMEM; + goto fail; + } + + sprintf(m->match, SERVICE_FILTER, m->service_name); + dbus_bus_add_match(m->connection, m->match, error); if (dbus_error_is_set(error)) { r = -EIO; @@ -220,10 +208,8 @@ void rm_release(rm_monitor *m) { if (m->matching) dbus_bus_remove_match( m->connection, - "type='signal'," - "sender='" DBUS_SERVICE_DBUS "'," - "interface='" DBUS_INTERFACE_DBUS "'," - "member='NameOwnerChanged'", NULL); + m->match, + NULL); if (m->filtering) dbus_connection_remove_filter( @@ -233,6 +219,7 @@ void rm_release(rm_monitor *m) { free(m->device_name); free(m->service_name); + free(m->match); if (m->connection) dbus_connection_unref(m->connection); diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c index 6086fc99..4be19c73 100644 --- a/src/modules/reserve-wrap.c +++ b/src/modules/reserve-wrap.c @@ -137,7 +137,7 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) #ifdef HAVE_DBUS if (!(r->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { - pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + pa_log_debug("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); /* We don't treat this as error here because we want allow PA * to run even when no session bus is available. */ @@ -154,10 +154,10 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) NULL)) < 0) { if (k == -EBUSY) { - pa_log_error("Device '%s' already locked.", device_name); + pa_log_debug("Device '%s' already locked.", device_name); goto fail; } else { - pa_log_warn("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); + pa_log_debug("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); return r; } } @@ -280,7 +280,7 @@ pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const cha #ifdef HAVE_DBUS if (!(w->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { - pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + pa_log_debug("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); /* We don't treat this as error here because we want allow PA * to run even when no session bus is available. */ @@ -294,7 +294,7 @@ pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const cha change_cb, NULL)) < 0) { - pa_log_warn("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k)); + pa_log_debug("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k)); goto fail; } diff --git a/src/modules/reserve.c b/src/modules/reserve.c index 5597f177..b4c168cf 100644 --- a/src/modules/reserve.c +++ b/src/modules/reserve.c @@ -291,7 +291,6 @@ static DBusHandlerResult filter_handler( DBusMessage *m, void *userdata) { - DBusMessage *reply; rd_device *d; DBusError error; @@ -323,35 +322,13 @@ static DBusHandlerResult filter_handler( rd_release(d); } - return DBUS_HANDLER_RESULT_HANDLED; } } - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - invalid: - if (!(reply = dbus_message_new_error( - m, - DBUS_ERROR_INVALID_ARGS, - "Invalid arguments"))) - goto oom; - - if (!dbus_connection_send(c, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - if (reply) - dbus_message_unref(reply); - dbus_error_free(&error); - return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 5caf8272..7dbb1efa 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -361,7 +361,7 @@ static void sink_input_attach(pa_sink_input *i) { pa_assert_se(s = i->userdata); pa_assert(!s->rtpoll_item); - s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1); + s->rtpoll_item = pa_rtpoll_item_new(i->sink->thread_info.rtpoll, PA_RTPOLL_LATE, 1); p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL); p->fd = s->rtp_context.fd; @@ -390,7 +390,7 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { pa_assert(salen > 0); af = sa->sa_family; - if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { pa_log("Failed to create socket: %s", pa_cstrerror(errno)); goto fail; } @@ -501,8 +501,9 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in pa_proplist_setf(data.proplist, "rtp.payload", "%u", (unsigned) sdp_info->payload); data.module = u->module; pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec); + data.flags = PA_SINK_INPUT_VARIABLE_RATE; - pa_sink_input_new(&s->sink_input, u->module->core, &data, PA_SINK_INPUT_VARIABLE_RATE); + pa_sink_input_new(&s->sink_input, u->module->core, &data); pa_sink_input_new_data_done(&data); if (!s->sink_input) { diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index f147364d..ab815223 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -262,7 +262,7 @@ int pa__init(pa_module*m) { goto fail; } - if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { pa_log("socket() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -277,7 +277,7 @@ int pa__init(pa_module*m) { #endif } - if ((sap_fd = socket(af, SOCK_DGRAM, 0)) < 0) { + if ((sap_fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { pa_log("socket() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -316,8 +316,6 @@ int pa__init(pa_module*m) { /* If the socket queue is full, let's drop packets */ pa_make_fd_nonblock(fd); pa_make_udp_socket_low_delay(fd); - pa_make_fd_cloexec(fd); - pa_make_fd_cloexec(sap_fd); pa_source_output_new_data_init(&data); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, "RTP Monitor Stream"); @@ -330,8 +328,9 @@ int pa__init(pa_module*m) { data.source = s; pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); + data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; - pa_source_output_new(&o, m->core, &data, PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND); + pa_source_output_new(&o, m->core, &data); pa_source_output_new_data_done(&data); if (!o) { diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 72d304e8..59618064 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -60,7 +60,6 @@ struct pa_rtsp_client { uint16_t port; pa_socket_client *sc; - pa_iochannel *io; pa_ioline *ioline; pa_rtsp_cb_t callback; @@ -111,10 +110,8 @@ void pa_rtsp_client_free(pa_rtsp_client* c) { if (c->sc) pa_socket_client_unref(c->sc); - if (c->ioline) - pa_ioline_close(c->ioline); - else if (c->io) - pa_iochannel_free(c->io); + + pa_rtsp_disconnect(c); pa_xfree(c->hostname); pa_xfree(c->url); @@ -187,7 +184,6 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { if (!s) { /* Keep the ioline/iochannel open as they will be freed automatically */ c->ioline = NULL; - c->io = NULL; c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); return; } @@ -214,11 +210,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { /* End of headers */ /* We will have a header left from our looping iteration, so add it in :) */ if (c->last_header) { + char *tmp = pa_strbuf_tostring_free(c->header_buffer); /* This is not a continuation header so let's dump it into our proplist */ - pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_headerlist_puts(c->response_headers, c->last_header, tmp); + pa_xfree(tmp); pa_xfree(c->last_header); c->last_header = NULL; - c->header_buffer= NULL; + c->header_buffer = NULL; } pa_log_debug("Full response received. Dispatching"); @@ -240,9 +238,11 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } if (c->last_header) { + char *tmp = pa_strbuf_tostring_free(c->header_buffer); /* This is not a continuation header so let's dump the full header/value into our proplist */ - pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_headerlist_puts(c->response_headers, c->last_header, tmp); + pa_xfree(tmp); pa_xfree(c->last_header); c->last_header = NULL; c->header_buffer = NULL; @@ -299,8 +299,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_log("Connection failed: %s", pa_cstrerror(errno)); return; } - pa_assert(!c->io); - c->io = io; + pa_assert(!c->ioline); c->ioline = pa_ioline_new(io); pa_ioline_set_callback(c->ioline, line_callback, c); @@ -356,9 +355,6 @@ void pa_rtsp_disconnect(pa_rtsp_client *c) { if (c->ioline) pa_ioline_close(c->ioline); - else if (c->io) - pa_iochannel_free(c->io); - c->io = NULL; c->ioline = NULL; } @@ -404,13 +400,11 @@ static int rtsp_exec(pa_rtsp_client* c, const char* cmd, pa_headerlist* headers) { pa_strbuf* buf; char* hdrs; - ssize_t l; pa_assert(c); pa_assert(c->url); - - if (!cmd) - return -1; + pa_assert(cmd); + pa_assert(c->ioline); pa_log_debug("Sending command: %s", cmd); @@ -449,7 +443,7 @@ static int rtsp_exec(pa_rtsp_client* c, const char* cmd, hdrs = pa_strbuf_tostring_free(buf); /*pa_log_debug("Submitting request:"); pa_log_debug(hdrs);*/ - l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); + pa_ioline_puts(c->ioline, hdrs); pa_xfree(hdrs); return 0; diff --git a/src/modules/x11/module-x11-publish.c b/src/modules/x11/module-x11-publish.c index 2c7fdc12..7ee1b6da 100644 --- a/src/modules/x11/module-x11-publish.c +++ b/src/modules/x11/module-x11-publish.c @@ -90,7 +90,7 @@ static void publish_servers(struct userdata *u, pa_strlist *l) { l = pa_strlist_reverse(l); s = pa_strlist_tostring(l); - l = pa_strlist_reverse(l); + pa_strlist_reverse(l); pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s); pa_xfree(s); diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c index 88823012..9b516262 100644 --- a/src/pulse/channelmap.c +++ b/src/pulse/channelmap.c @@ -219,11 +219,11 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p case 6: m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; - m->map[1] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; m->map[3] = PA_CHANNEL_POSITION_FRONT_RIGHT; - m->map[4] = PA_CHANNEL_POSITION_REAR_RIGHT; - m->map[5] = PA_CHANNEL_POSITION_LFE; + m->map[4] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + m->map[5] = PA_CHANNEL_POSITION_REAR_CENTER; return m; case 5: @@ -247,7 +247,7 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p m->map[0] = PA_CHANNEL_POSITION_LEFT; m->map[1] = PA_CHANNEL_POSITION_CENTER; m->map[2] = PA_CHANNEL_POSITION_RIGHT; - m->map[3] = PA_CHANNEL_POSITION_LFE; + m->map[3] = PA_CHANNEL_POSITION_REAR_CENTER; return m; default: @@ -299,6 +299,8 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p case PA_CHANNEL_MAP_WAVEEX: + /* Following http://www.microsoft.com/whdc/device/audio/multichaud.mspx#EKLAC */ + switch (channels) { case 1: m->map[0] = PA_CHANNEL_POSITION_MONO; @@ -451,6 +453,10 @@ int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) { pa_assert(b); pa_return_val_if_fail(pa_channel_map_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + pa_return_val_if_fail(pa_channel_map_valid(b), 0); if (a->channels != b->channels) @@ -639,6 +645,10 @@ int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { pa_assert(b); pa_return_val_if_fail(pa_channel_map_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + pa_return_val_if_fail(pa_channel_map_valid(b), 0); am = pa_channel_map_mask(a); diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h index d7901ac2..469effc8 100644 --- a/src/pulse/channelmap.h +++ b/src/pulse/channelmap.h @@ -216,17 +216,27 @@ typedef enum pa_channel_map_def { PA_CHANNEL_MAP_AIFF, /**< The mapping from RFC3551, which is based on AIFF-C */ +/** \cond fulldocs */ PA_CHANNEL_MAP_ALSA, - /**< The default mapping used by ALSA */ + /**< The default mapping used by ALSA. This mapping is probably + * not too useful since ALSA's default channel mapping depends on + * the device string used. */ +/** \endcond */ PA_CHANNEL_MAP_AUX, /**< Only aux channels */ PA_CHANNEL_MAP_WAVEEX, - /**< Microsoft's WAVEFORMATEXTENSIBLE mapping */ + /**< Microsoft's WAVEFORMATEXTENSIBLE mapping. This mapping works + * as if all LSBs of dwChannelMask are set. */ +/** \cond fulldocs */ PA_CHANNEL_MAP_OSS, - /**< The default channel mapping used by OSS as defined in the OSS 4.0 API specs */ + /**< The default channel mapping used by OSS as defined in the OSS + * 4.0 API specs. This mapping is probably not too useful since + * the OSS API has changed in this respect and no longer knows a + * default channel mapping based on the number of channels. */ +/** \endcond */ /**< Upper limit of valid channel mapping definitions */ PA_CHANNEL_MAP_DEF_MAX, @@ -282,7 +292,7 @@ pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, /** Return a text label for the specified channel position */ const char* pa_channel_position_to_string(pa_channel_position_t pos) PA_GCC_PURE; -/* The inverse of pa_channel_position_to_string(). \since 0.9.16 */ +/** The inverse of pa_channel_position_to_string(). \since 0.9.16 */ pa_channel_position_t pa_channel_position_from_string(const char *s) PA_GCC_PURE; /** Return a human readable text label for the specified channel position. \since 0.9.7 */ diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index 940d0b67..3eaca4d9 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -57,6 +57,7 @@ static const pa_client_conf default_conf = { .default_sink = NULL, .default_source = NULL, .default_server = NULL, + .default_dbus_server = NULL, .autospawn = TRUE, .disable_shm = FALSE, .cookie_file = NULL, @@ -81,6 +82,7 @@ void pa_client_conf_free(pa_client_conf *c) { pa_xfree(c->default_sink); pa_xfree(c->default_source); pa_xfree(c->default_server); + pa_xfree(c->default_dbus_server); pa_xfree(c->cookie_file); pa_xfree(c); } @@ -92,21 +94,23 @@ int pa_client_conf_load(pa_client_conf *c, const char *filename) { /* Prepare the configuration parse table */ pa_config_item table[] = { - { "daemon-binary", pa_config_parse_string, &c->daemon_binary, NULL }, - { "extra-arguments", pa_config_parse_string, &c->extra_arguments, NULL }, - { "default-sink", pa_config_parse_string, &c->default_sink, NULL }, - { "default-source", pa_config_parse_string, &c->default_source, NULL }, - { "default-server", pa_config_parse_string, &c->default_server, NULL }, - { "autospawn", pa_config_parse_bool, &c->autospawn, NULL }, - { "cookie-file", pa_config_parse_string, &c->cookie_file, NULL }, - { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, - { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, - { NULL, NULL, NULL, NULL }, + { "daemon-binary", pa_config_parse_string, &c->daemon_binary, NULL }, + { "extra-arguments", pa_config_parse_string, &c->extra_arguments, NULL }, + { "default-sink", pa_config_parse_string, &c->default_sink, NULL }, + { "default-source", pa_config_parse_string, &c->default_source, NULL }, + { "default-server", pa_config_parse_string, &c->default_server, NULL }, + { "default-dbus-server", pa_config_parse_string, &c->default_dbus_server, NULL }, + { "autospawn", pa_config_parse_bool, &c->autospawn, NULL }, + { "cookie-file", pa_config_parse_string, &c->cookie_file, NULL }, + { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, + { "enable-shm", pa_config_parse_not_bool, &c->disable_shm, NULL }, + { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, + { NULL, NULL, NULL, NULL }, }; if (filename) { - if (!(f = fopen(filename, "r"))) { + if (!(f = pa_fopen_cloexec(filename, "r"))) { pa_log(_("Failed to open configuration file '%s': %s"), fn, pa_cstrerror(errno)); goto finish; } diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h index ab97dc6a..618216f4 100644 --- a/src/pulse/client-conf.h +++ b/src/pulse/client-conf.h @@ -22,12 +22,13 @@ USA. ***/ +#include <pulsecore/macro.h> #include <pulsecore/native-common.h> /* A structure containing configuration data for PulseAudio clients. */ typedef struct pa_client_conf { - char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *cookie_file; + char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *default_dbus_server, *cookie_file; pa_bool_t autospawn, disable_shm; uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; pa_bool_t cookie_valid; /* non-zero, when cookie is valid */ diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in index 579bcc20..e03096e0 100644 --- a/src/pulse/client.conf.in +++ b/src/pulse/client.conf.in @@ -22,6 +22,7 @@ ; default-sink = ; default-source = ; default-server = +; default-dbus-server = ; autospawn = yes ; daemon-binary = @PA_BINARY@ @@ -29,5 +30,5 @@ ; cookie-file = -; disable-shm = no +; enable-shm = yes ; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB diff --git a/src/pulse/context.c b/src/pulse/context.c index 7c3717fa..e33143d9 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -128,6 +128,9 @@ static void reset_callbacks(pa_context *c) { c->event_callback = NULL; c->event_userdata = NULL; + c->ext_device_manager.callback = NULL; + c->ext_device_manager.userdata = NULL; + c->ext_stream_restore.callback = NULL; c->ext_stream_restore.userdata = NULL; } @@ -707,10 +710,13 @@ static int context_autospawn(pa_context *c) { if (c->spawn_api.atfork) c->spawn_api.atfork(); + /* We leave most of the cleaning up of the process environment + * to the executable. We only clean up the file descriptors to + * make sure the executable can actually be loaded + * correctly. */ pa_close_all(-1); /* Setup argv */ - argv[n++] = c->conf->daemon_binary; argv[n++] = "--start"; @@ -1042,7 +1048,10 @@ pa_context_state_t pa_context_get_state(pa_context *c) { } int pa_context_errno(pa_context *c) { - pa_assert(c); + + if (!c) + return PA_ERR_INVALID; + pa_assert(PA_REFCNT_VALUE(c) >= 1); return c->error; @@ -1428,6 +1437,8 @@ void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t if (!strcmp(name, "module-stream-restore")) pa_ext_stream_restore_command(c, tag, t); + else if (!strcmp(name, "module-device-manager")) + pa_ext_device_manager_command(c, tag, t); else pa_log(_("Received message for unknown extension '%s'"), name); @@ -1477,6 +1488,7 @@ pa_time_event* pa_context_rttime_new(pa_context *c, pa_usec_t usec, pa_time_even struct timeval tv; pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); pa_assert(c->mainloop); if (usec == PA_USEC_INVALID) @@ -1491,8 +1503,10 @@ void pa_context_rttime_restart(pa_context *c, pa_time_event *e, pa_usec_t usec) struct timeval tv; pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); pa_assert(c->mainloop); + if (usec == PA_USEC_INVALID) c->mainloop->time_restart(e, NULL); else { @@ -1500,3 +1514,17 @@ void pa_context_rttime_restart(pa_context *c, pa_time_event *e, pa_usec_t usec) c->mainloop->time_restart(e, &tv); } } + +size_t pa_context_get_tile_size(pa_context *c, const pa_sample_spec *ss) { + size_t fs, mbs; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, !pa_detect_fork(), PA_ERR_FORKED, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(c, !ss || pa_sample_spec_valid(ss), PA_ERR_INVALID, (size_t) -1); + + fs = ss ? pa_frame_size(ss) : 1; + mbs = PA_ROUND_DOWN(pa_mempool_block_size_max(c->mempool), fs); + return PA_MAX(mbs, fs); +} diff --git a/src/pulse/context.h b/src/pulse/context.h index cd129313..6ac8ee56 100644 --- a/src/pulse/context.h +++ b/src/pulse/context.h @@ -147,12 +147,6 @@ * server. A pa_context object wraps a connection to a PulseAudio * server using its native protocol. */ -/** \example pacat.c - * A playback and recording tool using the asynchronous API */ - -/** \example paplay.c - * A sound file playback tool using the asynchronous API, based on libsndfile */ - PA_C_DECL_BEGIN /** An opaque connection context to a daemon */ @@ -261,12 +255,27 @@ pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[] uint32_t pa_context_get_index(pa_context *s); /** Create a new timer event source for the specified time (wrapper - for mainloop->time_new). \since 0.9.16 */ + * for mainloop->time_new). \since 0.9.16 */ pa_time_event* pa_context_rttime_new(pa_context *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata); -/** Restart a running or expired timer event source (wrapper - for mainloop->time_restart). \since 0.9.16 */ + +/** Restart a running or expired timer event source (wrapper for + * mainloop->time_restart). \since 0.9.16 */ void pa_context_rttime_restart(pa_context *c, pa_time_event *e, pa_usec_t usec); +/* Return the optimal block size for passing around audio buffers. It + * is recommended to allocate buffers of the size returned here when + * writing audio data to playback streams, if the latency constraints + * permit this. It is not recommended writing larger blocks than this + * because usually they will then be split up internally into chunks + * of this size. It is not recommended writing smaller blocks than + * this (unless required due to latency demands) because this + * increases CPU usage. If ss is NULL you will be returned the + * byte-exact tile size. If you pass a valid ss, then the tile size + * will be rounded down to multiple of the frame size. This is + * supposed to be used in a construct such as + * pa_context_get_tile_size(pa_stream_get_context(s), + * pa_stream_get_sample_spec(ss)); \since 0.9.20 */ +size_t pa_context_get_tile_size(pa_context *c, const pa_sample_spec *ss); PA_C_DECL_END diff --git a/src/pulse/def.h b/src/pulse/def.h index 08399ca8..30a076d5 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -95,13 +95,14 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) { typedef enum pa_operation_state { PA_OPERATION_RUNNING, /**< The operation is still running */ PA_OPERATION_DONE, /**< The operation has been completed */ - PA_OPERATION_CANCELED /**< The operation has been canceled */ + PA_OPERATION_CANCELLED /**< The operation has been cancelled. Before 0.9.18 this was called PA_OPERATION_CANCELED. That name is still available for compatibility. */ } pa_operation_state_t; /** \cond fulldocs */ #define PA_OPERATION_RUNNING PA_OPERATION_RUNNING #define PA_OPERATION_DONE PA_OPERATION_DONE -#define PA_OPERATION_CANCELED PA_OPERATION_CANCELED +#define PA_OPERATION_CANCELED PA_OPERATION_CANCELLED +#define PA_OPERATION_CANCELLED PA_OPERATION_CANCELLED /** \endcond */ /** An invalid index */ @@ -109,6 +110,8 @@ typedef enum pa_operation_state { /** Some special flags for contexts. */ typedef enum pa_context_flags { + PA_CONTEXT_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ PA_CONTEXT_NOAUTOSPAWN = 0x0001U, /**< Disabled autospawning of the PulseAudio daemon if required */ PA_CONTEXT_NOFAIL = 0x0002U @@ -139,6 +142,9 @@ typedef enum pa_stream_direction { /** Some special flags for stream connections. */ typedef enum pa_stream_flags { + PA_STREAM_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + PA_STREAM_START_CORKED = 0x0001U, /**< Create the stream corked, requiring an explicit * pa_stream_cork() call to uncork it. */ @@ -270,11 +276,18 @@ typedef enum pa_stream_flags { * whether to create the stream in muted or in unmuted * state. \since 0.9.15 */ - PA_STREAM_FAIL_ON_SUSPEND = 0x20000U + PA_STREAM_FAIL_ON_SUSPEND = 0x20000U, /**< If the sink/source this stream is connected to is suspended * during the creation of this stream, cause it to fail. If the * sink/source is being suspended during creation of this stream, * make sure this stream is terminated. \since 0.9.15 */ + + PA_STREAM_RELATIVE_VOLUME = 0x40000U, + /**< If a volume is passed when this stream is created, consider + * it relative to the sink's current volume, never as absolute + * device volume. If this is not specified the volume will be + * consider absolute when the sink is in flat volume mode, + * relative otherwise. \since 0.9.20 */ } pa_stream_flags_t; /** \cond fulldocs */ @@ -301,6 +314,7 @@ typedef enum pa_stream_flags { #define PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND #define PA_STREAM_START_UNMUTED PA_STREAM_START_UNMUTED #define PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND +#define PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME /** \endcond */ @@ -394,6 +408,7 @@ enum { PA_ERR_NOTIMPLEMENTED, /**< Missing implementation. \since 0.9.15 */ PA_ERR_FORKED, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */ PA_ERR_IO, /**< An IO error happened. \since 0.9.16 */ + PA_ERR_BUSY, /**< Device or resource busy. \since 0.9.17 */ PA_ERR_MAX /**< Not really an error but the first invalid error code */ }; @@ -686,6 +701,9 @@ typedef enum pa_seek_mode { /** Special sink flags. */ typedef enum pa_sink_flags { + PA_SINK_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + PA_SINK_HW_VOLUME_CTRL = 0x0001U, /**< Supports hardware volume control */ @@ -773,6 +791,9 @@ static inline int PA_SINK_IS_OPENED(pa_sink_state_t x) { /** Special source flags. */ typedef enum pa_source_flags { + PA_SOURCE_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + PA_SOURCE_HW_VOLUME_CTRL = 0x0001U, /**< Supports hardware volume control */ diff --git a/src/pulse/error.c b/src/pulse/error.c index 93a13fc6..e8276990 100644 --- a/src/pulse/error.c +++ b/src/pulse/error.c @@ -64,7 +64,9 @@ const char*pa_strerror(int error) { [PA_ERR_NOEXTENSION] = N_("No such extension"), [PA_ERR_OBSOLETE] = N_("Obsolete functionality"), [PA_ERR_NOTIMPLEMENTED] = N_("Missing implementation"), - [PA_ERR_FORKED] = N_("Client forked") + [PA_ERR_FORKED] = N_("Client forked"), + [PA_ERR_IO] = N_("Input/Output error"), + [PA_ERR_BUSY] = N_("Device or resource busy") }; pa_init_i18n(); diff --git a/src/pulse/ext-device-manager.c b/src/pulse/ext-device-manager.c new file mode 100644 index 00000000..57cb57c8 --- /dev/null +++ b/src/pulse/ext-device-manager.c @@ -0,0 +1,437 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/context.h> +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "operation.h" +#include "fork-detect.h" + +#include "ext-device-manager.h" + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_RENAME, + SUBCOMMAND_DELETE, + SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, + SUBCOMMAND_REORDER, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + +static void ext_device_manager_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t version = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + } else if (pa_tagstruct_getu32(t, &version) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_ext_device_manager_test_cb_t cb = (pa_ext_device_manager_test_cb_t) o->callback; + cb(o->context, version, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_TEST); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_manager_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +static void ext_device_manager_read_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_ext_device_manager_info i; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_gets(t, &i.icon) < 0 || + pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_getu32(t, &i.n_role_priorities) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (i.n_role_priorities > 0) { + uint32_t j; + i.role_priorities = pa_xnew0(pa_ext_device_manager_role_priority_info, i.n_role_priorities+1); + + for (j = 0; j < i.n_role_priorities; j++) { + + if (pa_tagstruct_gets(t, &i.role_priorities[j].role) < 0 || + pa_tagstruct_getu32(t, &i.role_priorities[j].priority) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.role_priorities); + goto finish; + } + } + + /* Terminate with an extra NULL entry, just to make sure */ + i.role_priorities[j].role = NULL; + i.role_priorities[j].priority = 0; + } + + if (o->callback) { + pa_ext_device_manager_read_cb_t cb = (pa_ext_device_manager_read_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_xfree(i.role_priorities); + } + } + + if (o->callback) { + pa_ext_device_manager_read_cb_t cb = (pa_ext_device_manager_read_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_READ); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_manager_read_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(device); + pa_assert(description); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_RENAME); + + pa_tagstruct_puts(t, device); + pa_tagstruct_puts(t, description); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + const char *const *k; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(s); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_DELETE); + + for (k = s; *k; k++) { + if (!*k || !**k) + goto fail; + + pa_tagstruct_puts(t, *k); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; + +fail: + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + if (t) + pa_tagstruct_free(t); + + pa_context_set_error(c, PA_ERR_INVALID); + return NULL; +} + +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING); + pa_tagstruct_put_boolean(t, !!enable); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag, i; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + pa_assert(role); + pa_assert(devices); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_REORDER); + pa_tagstruct_puts(t, role); + + i = 0; while (devices[i]) i++; + pa_tagstruct_putu32(t, i); + + i = 0; + while (devices[i]) + pa_tagstruct_puts(t, devices[i++]); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_SUBSCRIBE); + pa_tagstruct_put_boolean(t, enable); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata) { + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + c->ext_device_manager.callback = cb; + c->ext_device_manager.userdata = userdata; +} + +void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t) { + uint32_t subcommand; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &subcommand) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (subcommand != SUBCOMMAND_EVENT) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (c->ext_device_manager.callback) + c->ext_device_manager.callback(c, c->ext_device_manager.userdata); +} diff --git a/src/pulse/ext-device-manager.h b/src/pulse/ext-device-manager.h new file mode 100644 index 00000000..1442a1a9 --- /dev/null +++ b/src/pulse/ext-device-manager.h @@ -0,0 +1,128 @@ +#ifndef foopulseextdevicemanagerhfoo +#define foopulseextdevicemanagerhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/context.h> +#include <pulse/version.h> + +/** \file + * + * Routines for controlling module-device-manager + */ + +PA_C_DECL_BEGIN + +typedef struct pa_ext_device_manager_role_priority_info { + const char *role; + uint32_t priority; +} pa_ext_device_manager_role_priority_info; + +/** Stores information about one device in the device database that is + * maintained by module-device-manager. \since 0.9.19 */ +typedef struct pa_ext_device_manager_info { + const char *name; /**< Identifier string of the device. A string like "sink:" or similar followed by the name of the device. */ + const char *description; /**< The description of the device when it was last seen, if applicable and saved */ + const char *icon; /**< The icon given to the device */ + uint32_t index; /**< The device index if it is currently available or PA_INVALID_INDEX */ + uint32_t n_role_priorities; /**< How many role priorities do we have? */ + pa_ext_device_manager_role_priority_info *role_priorities; /**< An array of role priority structures or NULL */ +} pa_ext_device_manager_info; + +/** Callback prototype for pa_ext_device_manager_test(). \since 0.9.19 */ +typedef void (*pa_ext_device_manager_test_cb_t)( + pa_context *c, + uint32_t version, + void *userdata); + +/** Test if this extension module is available in the server. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_manager_read(). \since 0.9.19 */ +typedef void (*pa_ext_device_manager_read_cb_t)( + pa_context *c, + const pa_ext_device_manager_info *info, + int eol, + void *userdata); + +/** Read all entries from the device database. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata); + +/** Sets the description for a device. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata); + +/** Delete entries from the device database. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata); + +/** Enable the role-based device-priority routing mode. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Prefer a given device in the priority list. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata); + +/** Subscribe to changes in the device database. \since 0.9.19 */ +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_manager_set_subscribe_cb(). \since 0.9.19 */ +typedef void (*pa_ext_device_manager_subscribe_cb_t)( + pa_context *c, + void *userdata); + +/** Set the subscription callback that is called when + * pa_ext_device_manager_subscribe() was called. \since 0.9.19 */ +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/ext-stream-restore.c b/src/pulse/ext-stream-restore.c index 63c911f8..10e9fd5d 100644 --- a/src/pulse/ext-stream-restore.c +++ b/src/pulse/ext-stream-restore.c @@ -239,13 +239,10 @@ pa_operation *pa_ext_stream_restore_write( return o; fail: - if (o) { - pa_operation_cancel(o); - pa_operation_unref(o); - } + pa_operation_cancel(o); + pa_operation_unref(o); - if (t) - pa_tagstruct_free(t); + pa_tagstruct_free(t); pa_context_set_error(c, PA_ERR_INVALID); return NULL; @@ -290,13 +287,10 @@ pa_operation *pa_ext_stream_restore_delete( return o; fail: - if (o) { - pa_operation_cancel(o); - pa_operation_unref(o); - } + pa_operation_cancel(o); + pa_operation_unref(o); - if (t) - pa_tagstruct_free(t); + pa_tagstruct_free(t); pa_context_set_error(c, PA_ERR_INVALID); return NULL; diff --git a/src/pulse/ext-stream-restore.h b/src/pulse/ext-stream-restore.h index 0b5d8eb6..54516f63 100644 --- a/src/pulse/ext-stream-restore.h +++ b/src/pulse/ext-stream-restore.h @@ -24,6 +24,8 @@ #include <pulse/context.h> #include <pulse/version.h> +#include <pulse/volume.h> +#include <pulse/channelmap.h> /** \file * diff --git a/src/pulse/gccmacro.h b/src/pulse/gccmacro.h index e85ecb66..57e80509 100644 --- a/src/pulse/gccmacro.h +++ b/src/pulse/gccmacro.h @@ -118,7 +118,7 @@ #endif #ifndef PA_GCC_WEAKREF -#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ > 1)) || (__GNUC__ > 4)) +#if defined(__GNUC__) && defined(__ELF__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ > 1)) || (__GNUC__ > 4)) /** Macro for usgae of GCC's weakref attribute */ #define PA_GCC_WEAKREF(x) __attribute__((weakref(#x))); #endif diff --git a/src/pulse/glib-mainloop.h b/src/pulse/glib-mainloop.h index 189513a8..67aba27d 100644 --- a/src/pulse/glib-mainloop.h +++ b/src/pulse/glib-mainloop.h @@ -56,7 +56,9 @@ pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c); /** Free the GLIB main loop object */ void pa_glib_mainloop_free(pa_glib_mainloop* g); -/** Return the abstract main loop API vtable for the GLIB main loop object */ +/** Return the abstract main loop API vtable for the GLIB main loop + object. No need of freeing the API as it is owned by the loop and + it is destroyed when this dies */ pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g); PA_C_DECL_END diff --git a/src/pulse/internal.h b/src/pulse/internal.h index e069c9e9..b371bfc2 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -28,6 +28,7 @@ #include <pulse/stream.h> #include <pulse/operation.h> #include <pulse/subscribe.h> +#include <pulse/ext-device-manager.h> #include <pulse/ext-stream-restore.h> #include <pulsecore/socket-client.h> @@ -102,6 +103,10 @@ struct pa_context { /* Extension specific data */ struct { + pa_ext_device_manager_subscribe_cb_t callback; + void *userdata; + } ext_device_manager; + struct { pa_ext_stream_restore_subscribe_cb_t callback; void *userdata; } ext_stream_restore; @@ -283,6 +288,7 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta #define PA_FAIL_RETURN_NULL(context, error) \ PA_FAIL_RETURN_ANY(context, error, NULL) +void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t); void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); pa_bool_t pa_mainloop_is_our_api(pa_mainloop_api*m); diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index 27a587cb..100413ee 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -834,6 +834,8 @@ pa_operation* pa_context_get_card_info_by_name(pa_context *c, const char*name, p } pa_operation* pa_context_get_card_info_list(pa_context *c, pa_card_info_cb_t cb, void *userdata) { + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15, PA_ERR_NOTSUPPORTED); + return pa_context_send_simple_command(c, PA_COMMAND_GET_CARD_INFO_LIST, context_get_card_info_callback, (pa_operation_cb_t) cb, userdata); } diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index ee982100..68cfc874 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -331,6 +331,12 @@ pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, i /** Set the mute switch of a source device specified by its name */ pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); +/** Suspend/Resume a source. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_name(pa_context *c, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a source. If idx is PA_INVALID_INDEX all sources will be suspended. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); + /** Change the profile of a source. \since 0.9.16 */ pa_operation* pa_context_set_source_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata); @@ -557,12 +563,6 @@ pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, /** Move the specified source output to a different source. \since 0.9.5 */ pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata); -/** Suspend/Resume a source. \since 0.9.7 */ -pa_operation* pa_context_suspend_source_by_name(pa_context *c, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata); - -/** Suspend/Resume a source. If idx is PA_INVALID_INDEX all sources will be suspended. \since 0.9.7 */ -pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); - /** Kill a source output. */ pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c index 3dc74398..70c0122c 100644 --- a/src/pulse/mainloop-signal.c +++ b/src/pulse/mainloop-signal.c @@ -124,15 +124,13 @@ int pa_signal_init(pa_mainloop_api *a) { pa_assert(signal_pipe[1] == -1); pa_assert(!io_event); - if (pipe(signal_pipe) < 0) { + if (pa_pipe_cloexec(signal_pipe) < 0) { pa_log("pipe(): %s", pa_cstrerror(errno)); return -1; } pa_make_fd_nonblock(signal_pipe[0]); pa_make_fd_nonblock(signal_pipe[1]); - pa_make_fd_cloexec(signal_pipe[0]); - pa_make_fd_cloexec(signal_pipe[1]); api = a; diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c index c418d108..6cd089ef 100644 --- a/src/pulse/mainloop.c +++ b/src/pulse/mainloop.c @@ -78,6 +78,7 @@ struct pa_time_event { pa_bool_t dead:1; pa_bool_t enabled:1; + pa_bool_t use_rtclock:1; pa_usec_t time; pa_time_event_cb_t callback; @@ -112,7 +113,7 @@ struct pa_mainloop { struct pollfd *pollfds; unsigned max_pollfds, n_pollfds; - int prepared_timeout; + pa_usec_t prepared_timeout; pa_time_event *cached_next_time_event; pa_mainloop_api api; @@ -172,17 +173,14 @@ static pa_io_event* mainloop_io_new( m = a->userdata; pa_assert(a == &m->api); - e = pa_xnew(pa_io_event, 1); + e = pa_xnew0(pa_io_event, 1); e->mainloop = m; - e->dead = FALSE; e->fd = fd; e->events = events; - e->pollfd = NULL; e->callback = callback; e->userdata = userdata; - e->destroy_callback = NULL; #ifdef OS_IS_WIN32 { @@ -265,16 +263,14 @@ static pa_defer_event* mainloop_defer_new( m = a->userdata; pa_assert(a == &m->api); - e = pa_xnew(pa_defer_event, 1); + e = pa_xnew0(pa_defer_event, 1); e->mainloop = m; - e->dead = FALSE; e->enabled = TRUE; m->n_enabled_defer_events++; e->callback = callback; e->userdata = userdata; - e->destroy_callback = NULL; PA_LLIST_PREPEND(pa_defer_event, m->defer_events, e); @@ -320,18 +316,20 @@ static void mainloop_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy } /* Time events */ -static pa_usec_t timeval_load(const struct timeval *tv) { - pa_bool_t is_rtclock; +static pa_usec_t make_rt(const struct timeval *tv, pa_bool_t *use_rtclock) { struct timeval ttv; - if (!tv) + if (!tv) { + *use_rtclock = FALSE; return PA_USEC_INVALID; + } ttv = *tv; - is_rtclock = !!(ttv.tv_usec & PA_TIMEVAL_RTCLOCK); - ttv.tv_usec &= ~PA_TIMEVAL_RTCLOCK; + *use_rtclock = !!(ttv.tv_usec & PA_TIMEVAL_RTCLOCK); - if (!is_rtclock) + if (*use_rtclock) + ttv.tv_usec &= ~PA_TIMEVAL_RTCLOCK; + else pa_rtclock_from_wallclock(&ttv); return pa_timeval_load(&ttv); @@ -346,22 +344,23 @@ static pa_time_event* mainloop_time_new( pa_mainloop *m; pa_time_event *e; pa_usec_t t; + pa_bool_t use_rtclock = FALSE; pa_assert(a); pa_assert(a->userdata); pa_assert(callback); - t = timeval_load(tv); + t = make_rt(tv, &use_rtclock); m = a->userdata; pa_assert(a == &m->api); - e = pa_xnew(pa_time_event, 1); + e = pa_xnew0(pa_time_event, 1); e->mainloop = m; - e->dead = FALSE; if ((e->enabled = (t != PA_USEC_INVALID))) { e->time = t; + e->use_rtclock= use_rtclock; m->n_enabled_time_events++; @@ -375,7 +374,6 @@ static pa_time_event* mainloop_time_new( e->callback = callback; e->userdata = userdata; - e->destroy_callback = NULL; PA_LLIST_PREPEND(pa_time_event, m->time_events, e); @@ -388,11 +386,12 @@ static pa_time_event* mainloop_time_new( static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { pa_bool_t valid; pa_usec_t t; + pa_bool_t use_rtclock = FALSE; pa_assert(e); pa_assert(!e->dead); - t = timeval_load(tv); + t = make_rt(tv, &use_rtclock); valid = (t != PA_USEC_INVALID); if (e->enabled && !valid) { @@ -403,6 +402,7 @@ static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { if ((e->enabled = valid)) { e->time = t; + e->use_rtclock = use_rtclock; pa_mainloop_wakeup(e->mainloop); } @@ -480,10 +480,9 @@ pa_mainloop *pa_mainloop_new(void) { pa_init_i18n(); - m = pa_xnew(pa_mainloop, 1); + m = pa_xnew0(pa_mainloop, 1); - m->wakeup_pipe_type = 0; - if (pipe(m->wakeup_pipe) < 0) { + if (pa_pipe_cloexec(m->wakeup_pipe) < 0) { pa_log_error("ERROR: cannot create wakeup pipe"); pa_xfree(m); return NULL; @@ -491,45 +490,23 @@ pa_mainloop *pa_mainloop_new(void) { pa_make_fd_nonblock(m->wakeup_pipe[0]); pa_make_fd_nonblock(m->wakeup_pipe[1]); - pa_make_fd_cloexec(m->wakeup_pipe[0]); - pa_make_fd_cloexec(m->wakeup_pipe[1]); - m->wakeup_requested = FALSE; - - PA_LLIST_HEAD_INIT(pa_io_event, m->io_events); - PA_LLIST_HEAD_INIT(pa_time_event, m->time_events); - PA_LLIST_HEAD_INIT(pa_defer_event, m->defer_events); - - m->n_enabled_defer_events = m->n_enabled_time_events = m->n_io_events = 0; - m->io_events_please_scan = m->time_events_please_scan = m->defer_events_please_scan = 0; - - m->cached_next_time_event = NULL; - m->prepared_timeout = 0; - m->pollfds = NULL; - m->max_pollfds = m->n_pollfds = 0; m->rebuild_pollfds = TRUE; - m->quit = FALSE; - m->retval = 0; - m->api = vtable; m->api.userdata = m; m->state = STATE_PASSIVE; - m->poll_func = NULL; - m->poll_func_userdata = NULL; m->poll_func_ret = -1; return m; } static void cleanup_io_events(pa_mainloop *m, pa_bool_t force) { - pa_io_event *e; + pa_io_event *e, *n; - e = m->io_events; - while (e) { - pa_io_event *n = e->next; + PA_LLIST_FOREACH_SAFE(e, n, m->io_events) { if (!force && m->io_events_please_scan <= 0) break; @@ -549,19 +526,15 @@ static void cleanup_io_events(pa_mainloop *m, pa_bool_t force) { m->rebuild_pollfds = TRUE; } - - e = n; } pa_assert(m->io_events_please_scan == 0); } static void cleanup_time_events(pa_mainloop *m, pa_bool_t force) { - pa_time_event *e; + pa_time_event *e, *n; - e = m->time_events; - while (e) { - pa_time_event *n = e->next; + PA_LLIST_FOREACH_SAFE(e, n, m->time_events) { if (!force && m->time_events_please_scan <= 0) break; @@ -585,19 +558,15 @@ static void cleanup_time_events(pa_mainloop *m, pa_bool_t force) { pa_xfree(e); } - - e = n; } pa_assert(m->time_events_please_scan == 0); } static void cleanup_defer_events(pa_mainloop *m, pa_bool_t force) { - pa_defer_event *e; + pa_defer_event *e, *n; - e = m->defer_events; - while (e) { - pa_defer_event *n = e->next; + PA_LLIST_FOREACH_SAFE(e, n, m->defer_events) { if (!force && m->defer_events_please_scan <= 0) break; @@ -621,8 +590,6 @@ static void cleanup_defer_events(pa_mainloop *m, pa_bool_t force) { pa_xfree(e); } - - e = n; } pa_assert(m->defer_events_please_scan == 0); @@ -679,7 +646,7 @@ static void rebuild_pollfds(pa_mainloop *m) { m->n_pollfds++; } - for (e = m->io_events; e; e = e->next) { + PA_LLIST_FOREACH(e, m->io_events) { if (e->dead) { e->pollfd = NULL; continue; @@ -697,36 +664,46 @@ static void rebuild_pollfds(pa_mainloop *m) { m->rebuild_pollfds = FALSE; } -static int dispatch_pollfds(pa_mainloop *m) { +static unsigned dispatch_pollfds(pa_mainloop *m) { pa_io_event *e; - int r = 0, k; + unsigned r = 0, k; pa_assert(m->poll_func_ret > 0); - for (e = m->io_events, k = m->poll_func_ret; e && !m->quit && k > 0; e = e->next) { + k = m->poll_func_ret; + + PA_LLIST_FOREACH(e, m->io_events) { + + if (k <= 0 || m->quit) + break; + if (e->dead || !e->pollfd || !e->pollfd->revents) continue; pa_assert(e->pollfd->fd == e->fd); pa_assert(e->callback); + e->callback(&m->api, e, e->fd, map_flags_from_libc(e->pollfd->revents), e->userdata); e->pollfd->revents = 0; r++; - k--; } return r; } -static int dispatch_defer(pa_mainloop *m) { +static unsigned dispatch_defer(pa_mainloop *m) { pa_defer_event *e; - int r = 0; + unsigned r = 0; if (m->n_enabled_defer_events <= 0) return 0; - for (e = m->defer_events; e && !m->quit; e = e->next) { + PA_LLIST_FOREACH(e, m->defer_events) { + + if (m->quit) + break; + if (e->dead || !e->enabled) continue; @@ -745,7 +722,7 @@ static pa_time_event* find_next_time_event(pa_mainloop *m) { if (m->cached_next_time_event) return m->cached_next_time_event; - for (t = m->time_events; t; t = t->next) { + PA_LLIST_FOREACH(t, m->time_events) { if (t->dead || !t->enabled) continue; @@ -763,31 +740,30 @@ static pa_time_event* find_next_time_event(pa_mainloop *m) { return n; } -static int calc_next_timeout(pa_mainloop *m) { +static pa_usec_t calc_next_timeout(pa_mainloop *m) { pa_time_event *t; - pa_usec_t usec; + pa_usec_t clock_now; - if (!m->n_enabled_time_events) - return -1; + if (m->n_enabled_time_events <= 0) + return PA_USEC_INVALID; - t = find_next_time_event(m); - pa_assert(t); + pa_assert_se(t = find_next_time_event(m)); - if (t->time == 0) + if (t->time <= 0) return 0; - usec = t->time - pa_rtclock_now(); + clock_now = pa_rtclock_now(); - if (usec <= 0) + if (t->time <= clock_now) return 0; - return (int) (usec / 1000); /* in milliseconds */ + return t->time - clock_now; } -static int dispatch_timeout(pa_mainloop *m) { +static unsigned dispatch_timeout(pa_mainloop *m) { pa_time_event *e; pa_usec_t now; - int r = 0; + unsigned r = 0; pa_assert(m); if (m->n_enabled_time_events <= 0) @@ -795,7 +771,10 @@ static int dispatch_timeout(pa_mainloop *m) { now = pa_rtclock_now(); - for (e = m->time_events; e && !m->quit; e = e->next) { + PA_LLIST_FOREACH(e, m->time_events) { + + if (m->quit) + break; if (e->dead || !e->enabled) continue; @@ -807,7 +786,7 @@ static int dispatch_timeout(pa_mainloop *m) { /* Disable time event */ mainloop_time_restart(e, NULL); - e->callback(&m->api, e, pa_timeval_rtstore(&tv, e->time, TRUE), e->userdata); + e->callback(&m->api, e, pa_timeval_rtstore(&tv, e->time, e->use_rtclock), e->userdata); r++; } @@ -835,7 +814,8 @@ static void clear_wakeup(pa_mainloop *m) { return; if (m->wakeup_requested) { - while (pa_read(m->wakeup_pipe[0], &c, sizeof(c), &m->wakeup_pipe_type) == sizeof(c)); + while (pa_read(m->wakeup_pipe[0], &c, sizeof(c), &m->wakeup_pipe_type) == sizeof(c)) + ; m->wakeup_requested = 0; } } @@ -851,12 +831,17 @@ int pa_mainloop_prepare(pa_mainloop *m, int timeout) { goto quit; if (m->n_enabled_defer_events <= 0) { + if (m->rebuild_pollfds) rebuild_pollfds(m); m->prepared_timeout = calc_next_timeout(m); - if (timeout >= 0 && (timeout < m->prepared_timeout || m->prepared_timeout < 0)) - m->prepared_timeout = timeout; + if (timeout >= 0) { + uint64_t u = (uint64_t) timeout * PA_USEC_PER_MSEC; + + if (u < m->prepared_timeout || m->prepared_timeout == PA_USEC_INVALID) + m->prepared_timeout = timeout; + } } m->state = STATE_PREPARED; @@ -867,6 +852,13 @@ quit: return -2; } +static int usec_to_timeout(pa_usec_t u) { + if (u == PA_USEC_INVALID) + return -1; + + return (u + PA_USEC_PER_MSEC - 1) / PA_USEC_PER_MSEC; +} + int pa_mainloop_poll(pa_mainloop *m) { pa_assert(m); pa_assert(m->state == STATE_PREPARED); @@ -882,9 +874,24 @@ int pa_mainloop_poll(pa_mainloop *m) { pa_assert(!m->rebuild_pollfds); if (m->poll_func) - m->poll_func_ret = m->poll_func(m->pollfds, m->n_pollfds, m->prepared_timeout, m->poll_func_userdata); - else - m->poll_func_ret = poll(m->pollfds, m->n_pollfds, m->prepared_timeout); + m->poll_func_ret = m->poll_func( + m->pollfds, m->n_pollfds, + usec_to_timeout(m->prepared_timeout), + m->poll_func_userdata); + else { +#ifdef HAVE_PPOLL + struct timespec ts; + + m->poll_func_ret = ppoll( + m->pollfds, m->n_pollfds, + m->prepared_timeout == PA_USEC_INVALID ? NULL : pa_timespec_store(&ts, m->prepared_timeout), + NULL); +#else + m->poll_func_ret = poll( + m->pollfds, m->n_pollfds, + usec_to_timeout(m->prepared_timeout)); +#endif + } if (m->poll_func_ret < 0) { if (errno == EINTR) @@ -903,7 +910,7 @@ quit: } int pa_mainloop_dispatch(pa_mainloop *m) { - int dispatched = 0; + unsigned dispatched = 0; pa_assert(m); pa_assert(m->state == STATE_POLLED); @@ -929,7 +936,7 @@ int pa_mainloop_dispatch(pa_mainloop *m) { m->state = STATE_PASSIVE; - return dispatched; + return (int) dispatched; quit: m->state = STATE_QUIT; @@ -938,6 +945,7 @@ quit: int pa_mainloop_get_retval(pa_mainloop *m) { pa_assert(m); + return m->retval; } @@ -966,7 +974,8 @@ quit: int pa_mainloop_run(pa_mainloop *m, int *retval) { int r; - while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0); + while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0) + ; if (r == -2) return 1; @@ -986,6 +995,7 @@ void pa_mainloop_quit(pa_mainloop *m, int retval) { pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m) { pa_assert(m); + return &m->api; } diff --git a/src/pulse/mainloop.h b/src/pulse/mainloop.h index 4a83ebe8..63abd588 100644 --- a/src/pulse/mainloop.h +++ b/src/pulse/mainloop.h @@ -108,7 +108,9 @@ int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval); /** Run unlimited iterations of the main loop object until the main loop's quit() routine is called. */ int pa_mainloop_run(pa_mainloop *m, int *retval); -/** Return the abstract main loop abstraction layer vtable for this main loop. */ +/** Return the abstract main loop abstraction layer vtable for this + main loop. No need of freeing the API as it is owned by the loop + and it is destroyed when this dies */ pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m); /** Shutdown the main loop */ diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c index c904f533..faa98b79 100644 --- a/src/pulse/proplist.c +++ b/src/pulse/proplist.c @@ -251,7 +251,7 @@ int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nb pa_assert(p); pa_assert(key); - pa_assert(data); + pa_assert(data || nbytes == 0); if (!property_name_valid(key)) return -1; @@ -264,7 +264,8 @@ int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nb pa_xfree(prop->value); prop->value = pa_xmalloc(nbytes+1); - memcpy(prop->value, data, nbytes); + if (nbytes > 0) + memcpy(prop->value, data, nbytes); ((char*) prop->value)[nbytes] = 0; prop->nbytes = nbytes; @@ -680,3 +681,32 @@ int pa_proplist_isempty(pa_proplist *p) { return pa_hashmap_isempty(MAKE_HASHMAP(p)); } + +int pa_proplist_equal(pa_proplist *a, pa_proplist *b) { + const void *key = NULL; + struct property *a_prop = NULL; + struct property *b_prop = NULL; + void *state = NULL; + + pa_assert(a); + pa_assert(b); + + if (a == b) + return 1; + + if (pa_proplist_size(a) != pa_proplist_size(b)) + return 0; + + while ((a_prop = pa_hashmap_iterate(MAKE_HASHMAP(a), &state, &key))) { + if (!(b_prop = pa_hashmap_get(MAKE_HASHMAP(b), key))) + return 0; + + if (a_prop->nbytes != b_prop->nbytes) + return 0; + + if (memcmp(a_prop->value, b_prop->value, a_prop->nbytes) != 0) + return 0; + } + + return 1; +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index bc4dbd8a..aae05346 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -97,6 +97,27 @@ PA_C_DECL_BEGIN /** For streams that belong to a window on the screen: an XDG icon name for the window. e.g. "totem" */ #define PA_PROP_WINDOW_ICON_NAME "window.icon_name" +/** For streams that belong to a window on the screen: absolute horizontal window position on the screen, integer formatted as text string. e.g. "865". \since 0.9.17 */ +#define PA_PROP_WINDOW_X "window.x" + +/** For streams that belong to a window on the screen: absolute vertical window position on the screen, integer formatted as text string. e.g. "343". \since 0.9.17 */ +#define PA_PROP_WINDOW_Y "window.y" + +/** For streams that belong to a window on the screen: window width on the screen, integer formatted as text string. e.g. "365". \since 0.9.17 */ +#define PA_PROP_WINDOW_WIDTH "window.width" + +/** For streams that belong to a window on the screen: window height on the screen, integer formatted as text string. e.g. "643". \since 0.9.17 */ +#define PA_PROP_WINDOW_HEIGHT "window.height" + +/** For streams that belong to a window on the screen: relative position of the window center on the screen, float formatted as text string, ranging from 0.0 (left side of the screen) to 1.0 (right side of the screen). e.g. "0.65". \since 0.9.17 */ +#define PA_PROP_WINDOW_HPOS "window.hpos" + +/** For streams that belong to a window on the screen: relative position of the window center on the screen, float formatted as text string, ranging from 0.0 (top of the screen) to 1.0 (bottom of the screen). e.g. "0.43". \since 0.9.17 */ +#define PA_PROP_WINDOW_VPOS "window.vpos" + +/** For streams that belong to a window on the screen: if the windowing system supports multiple desktops, a comma seperated list of indexes of the desktops this window is visible on. If this property is an empty string, it is visible on all desktops (i.e. 'sticky'). The first desktop is 0. e.g. "0,2,3" \since 0.9.18 */ +#define PA_PROP_WINDOW_DESKTOP "window.desktop" + /** For streams that belong to an X11 window on the screen: the X11 display string. e.g. ":0.0" */ #define PA_PROP_WINDOW_X11_DISPLAY "window.x11.display" @@ -197,7 +218,7 @@ PA_C_DECL_BEGIN /** For filter devices: master device id if applicable. */ #define PA_PROP_DEVICE_MASTER_DEVICE "device.master_device" -/** For devices: buffer size in bytes, integer formatted as string.. */ +/** For devices: buffer size in bytes, integer formatted as string. */ #define PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE "device.buffering.buffer_size" /** For devices: fragment size in bytes, integer formatted as string. */ @@ -337,7 +358,7 @@ char *pa_proplist_to_string_sep(pa_proplist *p, const char *sep); * readable string. \since 0.9.15 */ pa_proplist *pa_proplist_from_string(const char *str); - /** Returns 1 if an entry for the specified key is existant in the +/** Returns 1 if an entry for the specified key is existant in the * property list. \since 0.9.11 */ int pa_proplist_contains(pa_proplist *p, const char *key); @@ -354,6 +375,10 @@ unsigned pa_proplist_size(pa_proplist *t); /** Returns 0 when the proplist is empty, positive otherwise \since 0.9.15 */ int pa_proplist_isempty(pa_proplist *t); +/** Return non-zero when a and b have the same keys and values. + * \since 0.9.16 */ +int pa_proplist_equal(pa_proplist *a, pa_proplist *b); + PA_C_DECL_END #endif diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h index aa369e69..793ba9b1 100644 --- a/src/pulse/pulseaudio.h +++ b/src/pulse/pulseaudio.h @@ -44,15 +44,17 @@ #include <pulse/util.h> #include <pulse/timeval.h> #include <pulse/proplist.h> +#include <pulse/rtclock.h> /** \file - * Include all libpulse header files at once. The following - * files are included: \ref mainloop-api.h, \ref sample.h, \ref def.h, - * \ref context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h, - * \ref scache.h, \ref version.h, \ref error.h, \ref channelmap.h, - * \ref operation.h,\ref volume.h, \ref xmalloc.h, \ref utf8.h, \ref - * thread-mainloop.h, \ref mainloop.h, \ref util.h, \ref proplist.h, \ref timeval.h and - * \ref mainloop-signal.h at once */ + * Include all libpulse header files at once. The following files are + * included: \ref mainloop-api.h, \ref sample.h, \ref def.h, \ref + * context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h, \ref + * scache.h, \ref version.h, \ref error.h, \ref channelmap.h, \ref + * operation.h,\ref volume.h, \ref xmalloc.h, \ref utf8.h, \ref + * thread-mainloop.h, \ref mainloop.h, \ref util.h, \ref proplist.h, + * \ref timeval.h, \ref rtclock.h and \ref mainloop-signal.h at + * once */ /** \mainpage * diff --git a/src/pulse/sample.c b/src/pulse/sample.c index 0f19f8eb..9698d8a5 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -36,28 +36,27 @@ #include "sample.h" -size_t pa_sample_size_of_format(pa_sample_format_t f) { - - static const size_t table[] = { - [PA_SAMPLE_U8] = 1, - [PA_SAMPLE_ULAW] = 1, - [PA_SAMPLE_ALAW] = 1, - [PA_SAMPLE_S16LE] = 2, - [PA_SAMPLE_S16BE] = 2, - [PA_SAMPLE_FLOAT32LE] = 4, - [PA_SAMPLE_FLOAT32BE] = 4, - [PA_SAMPLE_S32LE] = 4, - [PA_SAMPLE_S32BE] = 4, - [PA_SAMPLE_S24LE] = 3, - [PA_SAMPLE_S24BE] = 3, - [PA_SAMPLE_S24_32LE] = 4, - [PA_SAMPLE_S24_32BE] = 4 - }; +static const size_t size_table[] = { + [PA_SAMPLE_U8] = 1, + [PA_SAMPLE_ULAW] = 1, + [PA_SAMPLE_ALAW] = 1, + [PA_SAMPLE_S16LE] = 2, + [PA_SAMPLE_S16BE] = 2, + [PA_SAMPLE_FLOAT32LE] = 4, + [PA_SAMPLE_FLOAT32BE] = 4, + [PA_SAMPLE_S32LE] = 4, + [PA_SAMPLE_S32BE] = 4, + [PA_SAMPLE_S24LE] = 3, + [PA_SAMPLE_S24BE] = 3, + [PA_SAMPLE_S24_32LE] = 4, + [PA_SAMPLE_S24_32BE] = 4 +}; +size_t pa_sample_size_of_format(pa_sample_format_t f) { pa_assert(f >= 0); pa_assert(f < PA_SAMPLE_MAX); - return table[f]; + return size_table[f]; } size_t pa_sample_size(const pa_sample_spec *spec) { @@ -65,35 +64,35 @@ size_t pa_sample_size(const pa_sample_spec *spec) { pa_assert(spec); pa_return_val_if_fail(pa_sample_spec_valid(spec), 0); - return pa_sample_size_of_format(spec->format); + return size_table[spec->format]; } size_t pa_frame_size(const pa_sample_spec *spec) { pa_assert(spec); pa_return_val_if_fail(pa_sample_spec_valid(spec), 0); - return pa_sample_size(spec) * spec->channels; + return size_table[spec->format] * spec->channels; } size_t pa_bytes_per_second(const pa_sample_spec *spec) { pa_assert(spec); pa_return_val_if_fail(pa_sample_spec_valid(spec), 0); - return spec->rate*pa_frame_size(spec); + return spec->rate * size_table[spec->format] * spec->channels; } pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) { pa_assert(spec); pa_return_val_if_fail(pa_sample_spec_valid(spec), 0); - return (((pa_usec_t) (length / pa_frame_size(spec)) * PA_USEC_PER_SEC) / spec->rate); + return (((pa_usec_t) (length / (size_table[spec->format] * spec->channels)) * PA_USEC_PER_SEC) / spec->rate); } size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) { pa_assert(spec); pa_return_val_if_fail(pa_sample_spec_valid(spec), 0); - return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * pa_frame_size(spec); + return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * (size_table[spec->format] * spec->channels); } pa_sample_spec* pa_sample_spec_init(pa_sample_spec *spec) { @@ -109,12 +108,12 @@ pa_sample_spec* pa_sample_spec_init(pa_sample_spec *spec) { int pa_sample_spec_valid(const pa_sample_spec *spec) { pa_assert(spec); - if (spec->rate <= 0 || + if (PA_UNLIKELY (spec->rate <= 0 || spec->rate > PA_RATE_MAX || spec->channels <= 0 || spec->channels > PA_CHANNELS_MAX || spec->format >= PA_SAMPLE_MAX || - spec->format < 0) + spec->format < 0)) return 0; return 1; @@ -125,6 +124,10 @@ int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) { pa_assert(b); pa_return_val_if_fail(pa_sample_spec_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + pa_return_val_if_fail(pa_sample_spec_valid(b), 0); return diff --git a/src/pulse/sample.h b/src/pulse/sample.h index 53d7dea3..7a4a55a0 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -302,6 +302,13 @@ pa_sample_format_t pa_parse_sample_format(const char *format) PA_GCC_PURE; /** Pretty print a sample type specification to a string */ char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec); +/** Maximum required string length for pa_bytes_snprint(). Please note + * that this value can change with any release without warning and + * without being considered API or ABI breakage. You should not use + * this definition anywhere where it might become part of an + * ABI. \since 0.9.16 */ +#define PA_BYTES_SNPRINT_MAX 11 + /** Pretty print a byte size value. (i.e. "2.5 MiB") */ char* pa_bytes_snprint(char *s, size_t l, unsigned v); diff --git a/src/pulse/scache.c b/src/pulse/scache.c index 77f60d72..27da6887 100644 --- a/src/pulse/scache.c +++ b/src/pulse/scache.c @@ -187,7 +187,7 @@ pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char pa_tagstruct_putu32(t, PA_INVALID_INDEX); pa_tagstruct_puts(t, dev); - if (volume == (pa_volume_t) -1 && c->version < 15) + if (volume == PA_VOLUME_INVALID && c->version < 15) volume = PA_VOLUME_NORM; pa_tagstruct_putu32(t, volume); @@ -216,7 +216,6 @@ pa_operation *pa_context_play_sample_with_proplist(pa_context *c, const char *na PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID); - PA_CHECK_VALIDITY_RETURN_NULL(c, p, PA_ERR_INVALID); PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); @@ -228,12 +227,19 @@ pa_operation *pa_context_play_sample_with_proplist(pa_context *c, const char *na pa_tagstruct_putu32(t, PA_INVALID_INDEX); pa_tagstruct_puts(t, dev); - if (volume == (pa_volume_t) -1 && c->version < 15) + if (volume == PA_VOLUME_INVALID && c->version < 15) volume = PA_VOLUME_NORM; pa_tagstruct_putu32(t, volume); pa_tagstruct_puts(t, name); - pa_tagstruct_put_proplist(t, p); + + if (p) + pa_tagstruct_put_proplist(t, p); + else { + p = pa_proplist_new(); + pa_tagstruct_put_proplist(t, p); + pa_proplist_free(p); + } pa_pstream_send_tagstruct(c->pstream, t); pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_with_proplist_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); diff --git a/src/pulse/scache.h b/src/pulse/scache.h index cd579d2e..31cf7b01 100644 --- a/src/pulse/scache.h +++ b/src/pulse/scache.h @@ -101,7 +101,7 @@ pa_operation* pa_context_play_sample( pa_context *c /**< Context */, const char *name /**< Name of the sample to play */, const char *dev /**< Sink to play this sample on */, - pa_volume_t volume /**< Volume to play this sample with. Starting with 0.9.15 you may pass here (pa_volume_t) -1 which will leave the decision about the volume to the server side which is a good idea. */ , + pa_volume_t volume /**< Volume to play this sample with. Starting with 0.9.15 you may pass here PA_VOLUME_INVALID which will leave the decision about the volume to the server side which is a good idea. */ , pa_context_success_cb_t cb /**< Call this function after successfully starting playback, or NULL */, void *userdata /**< Userdata to pass to the callback */); @@ -113,7 +113,7 @@ pa_operation* pa_context_play_sample_with_proplist( pa_context *c /**< Context */, const char *name /**< Name of the sample to play */, const char *dev /**< Sink to play this sample on */, - pa_volume_t volume /**< Volume to play this sample with. Starting with 0.9.15 you may pass here (pa_volume_t) -1 which will leave the decision about the volume to the server side which is a good idea. */ , + pa_volume_t volume /**< Volume to play this sample with. Starting with 0.9.15 you may pass here PA_VOLUME_INVALID which will leave the decision about the volume to the server side which is a good idea. */ , pa_proplist *proplist /**< Property list for this sound. The property list of the cached entry will be merged into this property list */, pa_context_play_sample_cb_t cb /**< Call this function after successfully starting playback, or NULL */, void *userdata /**< Userdata to pass to the callback */); diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 72d49e11..29979625 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -867,7 +867,7 @@ static void automatic_buffer_attr(pa_stream *s, pa_buffer_attr *attr, const pa_s void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_stream *s = userdata; - uint32_t requested_bytes; + uint32_t requested_bytes = 0; pa_assert(pd); pa_assert(s); @@ -1025,7 +1025,8 @@ static int create_stream( PA_STREAM_EARLY_REQUESTS| PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND| PA_STREAM_START_UNMUTED| - PA_STREAM_FAIL_ON_SUSPEND)), PA_ERR_INVALID); + PA_STREAM_FAIL_ON_SUSPEND| + PA_STREAM_RELATIVE_VOLUME)), PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, s->context->version >= 12 || !(flags & PA_STREAM_VARIABLE_RATE), PA_ERR_NOTSUPPORTED); PA_CHECK_VALIDITY(s->context, s->context->version >= 13 || !(flags & PA_STREAM_PEAK_DETECT), PA_ERR_NOTSUPPORTED); @@ -1158,6 +1159,13 @@ static int create_stream( pa_tagstruct_put_boolean(t, flags & PA_STREAM_FAIL_ON_SUSPEND); } + if (s->context->version >= 17) { + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & PA_STREAM_RELATIVE_VOLUME); + + } + pa_pstream_send_tagstruct(s->context->pstream, t); pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 8a08421f..bc54a118 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -31,6 +31,8 @@ #include <pulse/def.h> #include <pulse/cdecl.h> #include <pulse/operation.h> +#include <pulse/context.h> +#include <pulse/proplist.h> /** \page streams Audio Streams * @@ -319,7 +321,7 @@ typedef struct pa_stream pa_stream; typedef void (*pa_stream_success_cb_t) (pa_stream*s, int success, void *userdata); /** A generic request callback */ -typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t bytes, void *userdata); +typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t nbytes, void *userdata); /** A generic notification callback */ typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata); @@ -399,7 +401,22 @@ int pa_stream_is_suspended(pa_stream *s); * not, and negative on error. \since 0.9.11 */ int pa_stream_is_corked(pa_stream *s); -/** Connect the stream to a sink */ +/** Connect the stream to a sink. It is strongly recommended to pass + * NULL in both dev and volume and not to set either + * PA_STREAM_START_MUTED nor PA_STREAM_START_UNMUTED -- unless these + * options are directly dependant on user input or configuration. If + * you follow this rule then the sound server will have the full + * flexibility to choose the device, volume and mute status + * automatically, based on server-side policies, heuristics and stored + * information from previous uses. Also the server may choose to + * reconfigure audio devices to make other sinks/sources or + * capabilities available to be able to accept the stream. Before + * 0.9.20 it was not defined whether the 'volume' parameter was + * interpreted relative to the sink's current volume or treated as + * absolute device volume. Since 0.9.20 it is an absolute volume when + * the sink is in flat volume mode, and relative otherwise, thus + * making sure the volume passed here has always the same semantics as + * the volume passed to pa_context_set_sink_input_volume(). */ int pa_stream_connect_playback( pa_stream *s /**< The stream to connect to a sink */, const char *dev /**< Name of the sink to connect to, or NULL for default */ , diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h index e847070d..2cf496e1 100644 --- a/src/pulse/thread-mainloop.h +++ b/src/pulse/thread-mainloop.h @@ -299,7 +299,9 @@ void pa_threaded_mainloop_accept(pa_threaded_mainloop *m); /** Return the return value as specified with the main loop's quit() routine. */ int pa_threaded_mainloop_get_retval(pa_threaded_mainloop *m); -/** Return the abstract main loop abstraction layer vtable for this main loop. */ +/** Return the abstract main loop abstraction layer vtable for this + main loop. No need of freeing the API as it is owned by the loop + and it is destroyed when this dies */ pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m); /** Returns non-zero when called from withing the event loop thread. \since 0.9.7 */ diff --git a/src/pulse/timeval.c b/src/pulse/timeval.c index 376cf13c..cde4417c 100644 --- a/src/pulse/timeval.c +++ b/src/pulse/timeval.c @@ -33,6 +33,7 @@ #include <pulsecore/winsock.h> #include <pulsecore/macro.h> +#include <pulsecore/core-util.h> #include "timeval.h" @@ -54,9 +55,9 @@ struct timeval *pa_gettimeofday(struct timeval *tv) { #define EPOCHFILETIME (116444736000000000LL) #endif - FILETIME ft; - LARGE_INTEGER li; - __int64 t; + FILETIME ft; + LARGE_INTEGER li; + int64_t t; pa_assert(tv); @@ -82,7 +83,7 @@ pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { pa_assert(b); /* Check which whan is the earlier time and swap the two arguments if required. */ - if (pa_timeval_cmp(a, b) < 0) { + if (PA_UNLIKELY(pa_timeval_cmp(a, b) < 0)) { const struct timeval *c; c = a; a = b; @@ -94,9 +95,9 @@ pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { /* Calculate the microsecond difference */ if (a->tv_usec > b->tv_usec) - r += ((pa_usec_t) a->tv_usec - (pa_usec_t) b->tv_usec); + r += (pa_usec_t) a->tv_usec - (pa_usec_t) b->tv_usec; else if (a->tv_usec < b->tv_usec) - r -= ((pa_usec_t) b->tv_usec - (pa_usec_t) a->tv_usec); + r -= (pa_usec_t) b->tv_usec - (pa_usec_t) a->tv_usec; return r; } @@ -128,45 +129,77 @@ pa_usec_t pa_timeval_age(const struct timeval *tv) { } struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) { - unsigned long secs; + time_t secs; pa_assert(tv); - secs = (unsigned long) (v/PA_USEC_PER_SEC); - tv->tv_sec += (time_t) secs; - v -= ((pa_usec_t) secs) * PA_USEC_PER_SEC; + secs = (time_t) (v/PA_USEC_PER_SEC); + if (PA_UNLIKELY(tv->tv_sec > PA_INT_TYPE_MAX(time_t) - secs)) + goto overflow; + + tv->tv_sec += secs; + v -= (pa_usec_t) secs * PA_USEC_PER_SEC; tv->tv_usec += (suseconds_t) v; /* Normalize */ - while ((unsigned) tv->tv_usec >= PA_USEC_PER_SEC) { + while ((pa_usec_t) tv->tv_usec >= PA_USEC_PER_SEC) { + + if (PA_UNLIKELY(tv->tv_sec >= PA_INT_TYPE_MAX(time_t))) + goto overflow; + tv->tv_sec++; tv->tv_usec -= (suseconds_t) PA_USEC_PER_SEC; } return tv; + +overflow: + tv->tv_sec = PA_INT_TYPE_MAX(time_t); + tv->tv_usec = (suseconds_t) (PA_USEC_PER_SEC-1); + return tv; } struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v) { - unsigned long secs; + time_t secs; pa_assert(tv); - secs = (unsigned long) (v/PA_USEC_PER_SEC); - tv->tv_sec -= (time_t) secs; - v -= ((pa_usec_t) secs) * PA_USEC_PER_SEC; + secs = (time_t) (v/PA_USEC_PER_SEC); + + if (PA_UNLIKELY(tv->tv_sec < secs)) + goto underflow; + + tv->tv_sec -= secs; + v -= (pa_usec_t) secs * PA_USEC_PER_SEC; if (tv->tv_usec >= (suseconds_t) v) tv->tv_usec -= (suseconds_t) v; else { + + if (PA_UNLIKELY(tv->tv_sec <= 0)) + goto underflow; + tv->tv_sec --; tv->tv_usec += (suseconds_t) (PA_USEC_PER_SEC - v); } return tv; + +underflow: + tv->tv_sec = 0; + tv->tv_usec = 0; + return tv; } struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) { pa_assert(tv); + if (PA_UNLIKELY(v == PA_USEC_INVALID)) { + tv->tv_sec = PA_INT_TYPE_MAX(time_t); + tv->tv_usec = (suseconds_t) (PA_USEC_PER_SEC-1); + + return tv; + } + tv->tv_sec = (time_t) (v / PA_USEC_PER_SEC); tv->tv_usec = (suseconds_t) (v % PA_USEC_PER_SEC); @@ -174,7 +207,9 @@ struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) { } pa_usec_t pa_timeval_load(const struct timeval *tv) { - pa_assert(tv); + + if (PA_UNLIKELY(!tv)) + return PA_USEC_INVALID; return (pa_usec_t) tv->tv_sec * PA_USEC_PER_SEC + diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h index 48c6cdb3..3cea5d3b 100644 --- a/src/pulse/timeval.h +++ b/src/pulse/timeval.h @@ -51,12 +51,15 @@ PA_C_DECL_BEGIN /** The number of nanoseconds in a microsecond */ #define PA_NSEC_PER_USEC ((unsigned long long) 1000ULL) -/** Invalid time in usec */ +/** Invalid time in usec. \since 0.9.15 */ #define PA_USEC_INVALID ((pa_usec_t) -1) +/** Biggest time in usec. \since 0.9.18 */ +#define PA_USEC_MAX ((pa_usec_t) -2) + struct timeval; -/** Return the current timestamp, just like UNIX gettimeofday() */ +/** Return the current wallclock timestamp, just like UNIX gettimeofday(). */ struct timeval *pa_gettimeofday(struct timeval *tv); /** Calculate the difference between the two specified timeval diff --git a/src/pulse/utf8.c b/src/pulse/utf8.c index 6b58bde3..fe7bcd26 100644 --- a/src/pulse/utf8.c +++ b/src/pulse/utf8.c @@ -120,10 +120,8 @@ static char* utf8_validate(const char *str, char *output) { size = 4; min = (1 << 16); val = (uint32_t) (*p & 0x07); - } else { - size = 1; + } else goto error; - } p++; if (!is_continuation_char(*p)) @@ -150,12 +148,9 @@ ONE_REMAINING: if (o) { memcpy(o, last, (size_t) size); - o += size - 1; + o += size; } - if (o) - o++; - continue; error: diff --git a/src/pulse/util.c b/src/pulse/util.c index 6f1e40a9..9440f5de 100644 --- a/src/pulse/util.c +++ b/src/pulse/util.c @@ -61,38 +61,40 @@ #include <pulsecore/log.h> #include <pulsecore/core-util.h> #include <pulsecore/macro.h> +#include <pulsecore/usergroup.h> #include "util.h" char *pa_get_user_name(char *s, size_t l) { const char *p; + char *name = NULL; +#ifdef OS_IS_WIN32 char buf[1024]; +#endif #ifdef HAVE_PWD_H - struct passwd pw, *r; + struct passwd *r; #endif pa_assert(s); pa_assert(l > 0); - if (!(p = (getuid() == 0 ? "root" : NULL)) && - !(p = getenv("USER")) && - !(p = getenv("LOGNAME")) && - !(p = getenv("USERNAME"))) { + if ((p = (getuid() == 0 ? "root" : NULL)) || + (p = getenv("USER")) || + (p = getenv("LOGNAME")) || + (p = getenv("USERNAME"))) + { + name = pa_strlcpy(s, p, l); + } else { #ifdef HAVE_PWD_H -#ifdef HAVE_GETPWUID_R - if (getpwuid_r(getuid(), &pw, buf, sizeof(buf), &r) != 0 || !r) { -#else - /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) - * that do not support getpwuid_r. */ - if ((r = getpwuid(getuid())) == NULL) { -#endif + if ((r = pa_getpwuid_malloc(getuid())) == NULL) { pa_snprintf(s, l, "%lu", (unsigned long) getuid()); return s; } - p = r->pw_name; + name = pa_strlcpy(s, r->pw_name, l); + pa_getpwuid_free(r); #elif defined(OS_IS_WIN32) /* HAVE_PWD_H */ DWORD size = sizeof(buf); @@ -102,7 +104,7 @@ char *pa_get_user_name(char *s, size_t l) { return NULL; } - p = buf; + name = pa_strlcpy(s, buf, l); #else /* HAVE_PWD_H */ @@ -110,7 +112,7 @@ char *pa_get_user_name(char *s, size_t l) { #endif /* HAVE_PWD_H */ } - return pa_strlcpy(s, p, l); + return name; } char *pa_get_host_name(char *s, size_t l) { @@ -126,11 +128,10 @@ char *pa_get_host_name(char *s, size_t l) { } char *pa_get_home_dir(char *s, size_t l) { - char *e; + char *e, *dir; #ifdef HAVE_PWD_H - char buf[1024]; - struct passwd pw, *r; + struct passwd *r; #endif pa_assert(s); @@ -143,22 +144,19 @@ char *pa_get_home_dir(char *s, size_t l) { return pa_strlcpy(s, e, l); #ifdef HAVE_PWD_H - errno = 0; -#ifdef HAVE_GETPWUID_R - if (getpwuid_r(getuid(), &pw, buf, sizeof(buf), &r) != 0 || !r) { -#else - /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) - * that do not support getpwuid_r. */ - if ((r = getpwuid(getuid())) == NULL) { -#endif + if ((r = pa_getpwuid_malloc(getuid())) == NULL) { if (!errno) errno = ENOENT; return NULL; } - return pa_strlcpy(s, r->pw_dir, l); + dir = pa_strlcpy(s, r->pw_dir, l); + + pa_getpwuid_free(r); + + return dir; #else /* HAVE_PWD_H */ errno = ENOENT; diff --git a/src/pulse/volume.c b/src/pulse/volume.c index 42cde5b9..59e9a189 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -40,6 +40,10 @@ int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { pa_assert(b); pa_return_val_if_fail(pa_cvolume_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + pa_return_val_if_fail(pa_cvolume_valid(b), 0); if (a->channels != b->channels) @@ -60,7 +64,7 @@ pa_cvolume* pa_cvolume_init(pa_cvolume *a) { a->channels = 0; for (c = 0; c < PA_CHANNELS_MAX; c++) - a->values[c] = (pa_volume_t) -1; + a->values[c] = PA_VOLUME_INVALID; return a; } @@ -122,7 +126,7 @@ pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, p } pa_volume_t pa_cvolume_max(const pa_cvolume *a) { - pa_volume_t m = 0; + pa_volume_t m = PA_VOLUME_MUTED; unsigned c; pa_assert(a); @@ -135,9 +139,23 @@ pa_volume_t pa_cvolume_max(const pa_cvolume *a) { return m; } +pa_volume_t pa_cvolume_min(const pa_cvolume *a) { + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + if (a->values[c] < m) + m = a->values[c]; + + return m; +} + pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { - pa_volume_t m = 0; - unsigned c, n; + pa_volume_t m = PA_VOLUME_MUTED; + unsigned c; pa_assert(a); @@ -146,7 +164,7 @@ pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, p pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); - for (c = n = 0; c < a->channels; c++) { + for (c = 0; c < a->channels; c++) { if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) continue; @@ -158,17 +176,48 @@ pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, p return m; } +pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + + pa_assert(a); + + if (!cm) + return pa_cvolume_min(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] < m) + m = a->values[c]; + } + + return m; +} + pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) { - return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) * pa_sw_volume_to_linear(b)); + + pa_return_val_if_fail(a != PA_VOLUME_INVALID, PA_VOLUME_INVALID); + pa_return_val_if_fail(b != PA_VOLUME_INVALID, PA_VOLUME_INVALID); + + /* cbrt((a/PA_VOLUME_NORM)^3*(b/PA_VOLUME_NORM)^3)*PA_VOLUME_NORM = a*b/PA_VOLUME_NORM */ + + return (pa_volume_t) (((uint64_t) a * (uint64_t) b + (uint64_t) PA_VOLUME_NORM / 2ULL) / (uint64_t) PA_VOLUME_NORM); } pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) { - double v = pa_sw_volume_to_linear(b); - if (v <= 0) + pa_return_val_if_fail(a != PA_VOLUME_INVALID, PA_VOLUME_INVALID); + pa_return_val_if_fail(b != PA_VOLUME_INVALID, PA_VOLUME_INVALID); + + if (b <= PA_VOLUME_MUTED) return 0; - return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) / v); + return (pa_volume_t) (((uint64_t) a * (uint64_t) PA_VOLUME_NORM + (uint64_t) b / 2ULL) / (uint64_t) b); } /* Amplitude, not power */ @@ -189,6 +238,8 @@ pa_volume_t pa_sw_volume_from_dB(double dB) { double pa_sw_volume_to_dB(pa_volume_t v) { + pa_return_val_if_fail(v != PA_VOLUME_INVALID, PA_DECIBEL_MININFTY); + if (v <= PA_VOLUME_MUTED) return PA_DECIBEL_MININFTY; @@ -205,14 +256,19 @@ pa_volume_t pa_sw_volume_from_linear(double v) { * * http://www.robotplanet.dk/audio/audio_gui_design/ * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 + * + * We make sure that the conversion to linear and back yields the + * same volume value! That's why we need the lround() below! */ - return (pa_volume_t) (cbrt(v) * PA_VOLUME_NORM); + return (pa_volume_t) lround(cbrt(v) * PA_VOLUME_NORM); } double pa_sw_volume_to_linear(pa_volume_t v) { double f; + pa_return_val_if_fail(v != PA_VOLUME_INVALID, 0.0); + if (v <= PA_VOLUME_MUTED) return 0.0; @@ -246,7 +302,7 @@ char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { l -= pa_snprintf(e, l, "%s%u: %3u%%", first ? "" : " ", channel, - (c->values[channel]*100)/PA_VOLUME_NORM); + (c->values[channel]*100+PA_VOLUME_NORM/2)/PA_VOLUME_NORM); e = strchr(e, 0); first = FALSE; @@ -261,12 +317,12 @@ char *pa_volume_snprint(char *s, size_t l, pa_volume_t v) { pa_init_i18n(); - if (v == (pa_volume_t) -1) { + if (v == PA_VOLUME_INVALID) { pa_snprintf(s, l, _("(invalid)")); return s; } - pa_snprintf(s, l, "%3u%%", (v*100)/PA_VOLUME_NORM); + pa_snprintf(s, l, "%3u%%", (v*100+PA_VOLUME_NORM/2)/PA_VOLUME_NORM); return s; } @@ -311,7 +367,7 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v) { pa_init_i18n(); - if (v == (pa_volume_t) -1) { + if (v == PA_VOLUME_INVALID) { pa_snprintf(s, l, _("(invalid)")); return s; } @@ -328,6 +384,7 @@ int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) { pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), 0); + pa_return_val_if_fail(v != PA_VOLUME_INVALID, 0); for (c = 0; c < a->channels; c++) if (a->values[c] != v) @@ -361,6 +418,7 @@ pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(b != PA_VOLUME_INVALID, NULL); for (i = 0; i < a->channels; i++) dest->values[i] = pa_sw_volume_multiply(a->values[i], b); @@ -395,6 +453,7 @@ pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, p pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(b != PA_VOLUME_INVALID, NULL); for (i = 0; i < a->channels; i++) dest->values[i] = pa_sw_volume_divide(a->values[i], b); @@ -413,7 +472,7 @@ int pa_cvolume_valid(const pa_cvolume *v) { return 0; for (c = 0; c < v->channels; c++) - if (v->values[c] == (pa_volume_t) -1) + if (v->values[c] == PA_VOLUME_INVALID) return 0; return 1; @@ -451,8 +510,6 @@ pa_cvolume *pa_cvolume_remap(pa_cvolume *v, const pa_channel_map *from, const pa pa_assert(from); pa_assert(to); - pa_return_val_if_fail(pa_cvolume_valid(v), NULL); - pa_return_val_if_fail(pa_channel_map_valid(from), NULL); pa_return_val_if_fail(pa_channel_map_valid(to), NULL); pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, from), NULL); @@ -554,8 +611,6 @@ float pa_cvolume_get_balance(const pa_cvolume *v, const pa_channel_map *map) { pa_assert(v); pa_assert(map); - pa_return_val_if_fail(pa_cvolume_valid(v), 0.0f); - pa_return_val_if_fail(pa_channel_map_valid(map), 0.0f); pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); if (!pa_channel_map_can_balance(map)) @@ -587,12 +642,10 @@ pa_cvolume* pa_cvolume_set_balance(pa_cvolume *v, const pa_channel_map *map, flo pa_assert(map); pa_assert(v); - pa_assert(new_balance >= -1.0f); - pa_assert(new_balance <= 1.0f); - pa_return_val_if_fail(pa_cvolume_valid(v), NULL); - pa_return_val_if_fail(pa_channel_map_valid(map), NULL); pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_balance >= -1.0f, NULL); + pa_return_val_if_fail(new_balance <= 1.0f, NULL); if (!pa_channel_map_can_balance(map)) return v; @@ -633,7 +686,7 @@ pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) { pa_assert(v); pa_return_val_if_fail(pa_cvolume_valid(v), NULL); - pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + pa_return_val_if_fail(max != PA_VOLUME_INVALID, NULL); t = pa_cvolume_max(v); @@ -652,8 +705,12 @@ pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map pa_assert(v); - pa_return_val_if_fail(pa_cvolume_valid(v), NULL); - pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + pa_return_val_if_fail(max != PA_VOLUME_INVALID, NULL); + + if (!cm) + return pa_cvolume_scale(v, max); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, cm), NULL); t = pa_cvolume_max_mask(v, cm, mask); @@ -704,8 +761,6 @@ float pa_cvolume_get_fade(const pa_cvolume *v, const pa_channel_map *map) { pa_assert(v); pa_assert(map); - pa_return_val_if_fail(pa_cvolume_valid(v), 0.0f); - pa_return_val_if_fail(pa_channel_map_valid(map), 0.0f); pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); if (!pa_channel_map_can_fade(map)) @@ -728,12 +783,10 @@ pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float pa_assert(map); pa_assert(v); - pa_assert(new_fade >= -1.0f); - pa_assert(new_fade <= 1.0f); - pa_return_val_if_fail(pa_cvolume_valid(v), NULL); - pa_return_val_if_fail(pa_channel_map_valid(map), NULL); pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_fade >= -1.0f, NULL); + pa_return_val_if_fail(new_fade <= 1.0f, NULL); if (!pa_channel_map_can_fade(map)) return v; @@ -781,6 +834,7 @@ pa_cvolume* pa_cvolume_set_position( pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), NULL); pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, NULL); + pa_return_val_if_fail(v != PA_VOLUME_INVALID, NULL); for (c = 0; c < map->channels; c++) if (map->map[c] == t) { @@ -812,3 +866,61 @@ pa_volume_t pa_cvolume_get_position( return v; } + +pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + for (i = 0; i < a->channels && i < b->channels; i++) + dest->values[i] = PA_MAX(a->values[i], b->values[i]); + + dest->channels = (uint8_t) i; + + return dest; +} + +pa_cvolume* pa_cvolume_inc_clamp(pa_cvolume *v, pa_volume_t inc, pa_volume_t limit) { + pa_volume_t m; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(inc != PA_VOLUME_INVALID, NULL); + + m = pa_cvolume_max(v); + + if (m >= limit - inc) + m = limit; + else + m += inc; + + return pa_cvolume_scale(v, m); +} + +pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc){ + return pa_cvolume_inc_clamp(v, inc, PA_VOLUME_MAX); +} + +pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec) { + pa_volume_t m; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(dec != PA_VOLUME_INVALID, NULL); + + m = pa_cvolume_max(v); + + if (m <= PA_VOLUME_MUTED + dec) + m = PA_VOLUME_MUTED; + else + m -= dec; + + return pa_cvolume_scale(v, m); +} diff --git a/src/pulse/volume.h b/src/pulse/volume.h index 05b7ebb4..ded4422e 100644 --- a/src/pulse/volume.h +++ b/src/pulse/volume.h @@ -106,11 +106,14 @@ typedef uint32_t pa_volume_t; /** Normal volume (100%, 0 dB) */ #define PA_VOLUME_NORM ((pa_volume_t) 0x10000U) -/** Muted volume (0%, -inf dB) */ +/** Muted (minimal valid) volume (0%, -inf dB) */ #define PA_VOLUME_MUTED ((pa_volume_t) 0U) -/** Maximum volume we can store. \since 0.9.15 */ -#define PA_VOLUME_MAX ((pa_volume_t) UINT32_MAX) +/** Maximum valid volume we can store. \since 0.9.15 */ +#define PA_VOLUME_MAX ((pa_volume_t) UINT32_MAX-1) + +/** Special 'invalid' volume. \since 0.9.16 */ +#define PA_VOLUME_INVALID ((pa_volume_t) UINT32_MAX) /** A structure encapsulating a per-channel volume */ typedef struct pa_cvolume { @@ -195,6 +198,16 @@ pa_volume_t pa_cvolume_max(const pa_cvolume *a) PA_GCC_PURE; * \since 0.9.16 */ pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; +/** Return the minimum volume of all channels. \since 0.9.16 */ +pa_volume_t pa_cvolume_min(const pa_cvolume *a) PA_GCC_PURE; + +/** Return the minimum volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_min(). If no + * channel is selected the returned value will be PA_VOLUME_MUTED. + * \since 0.9.16 */ +pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + /** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */ int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE; @@ -213,11 +226,13 @@ int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) PA_GCC_PURE pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; /** Multiply two per-channel volumes and return the result in - * *dest. This is only valid for software volumes! */ + * *dest. This is only valid for software volumes! a, b and dest may + * point to the same structure. */ pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); /** Multiply a per-channel volume with a scalar volume and return the - * result in *dest. This is only valid for software volumes! \since + * result in *dest. This is only valid for software volumes! a + * and dest may point to the same structure. \since * 0.9.16 */ pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b); @@ -228,11 +243,13 @@ pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; /** Divide two per-channel volumes and return the result in - * *dest. This is only valid for software volumes! \since 0.9.13 */ + * *dest. This is only valid for software volumes! a, b + * and dest may point to the same structure. \since 0.9.13 */ pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); /** Divide a per-channel volume by a scalar volume and return the - * result in *dest. This is only valid for software volumes! \since + * result in *dest. This is only valid for software volumes! a + * and dest may point to the same structure. \since * 0.9.16 */ pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b); @@ -326,6 +343,23 @@ pa_cvolume* pa_cvolume_set_position(pa_cvolume *cv, const pa_channel_map *map, p * position by calling pa_channel_map_has_position(). \since 0.9.16 */ pa_volume_t pa_cvolume_get_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t) PA_GCC_PURE; +/** This goes through all channels in a and b and sets the + * corresponding channel in dest to the greater volume of both. a, b + * and dest may point to the same structure. \since 0.9.16 */ +pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); + +/** Increase the volume passed in by 'inc', but not exceeding 'limit'. + * The proportions between the channels are kept. \since 0.9.19 */ +pa_cvolume* pa_cvolume_inc_clamp(pa_cvolume *v, pa_volume_t inc, pa_volume_t limit); + +/** Increase the volume passed in by 'inc'. The proportions between + * the channels are kept. \since 0.9.16 */ +pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc); + +/** Increase the volume passed in by 'inc'. The proportions between + * the channels are kept. \since 0.9.16 */ +pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec); + PA_C_DECL_END #endif diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c index 083d9de2..b0804f79 100644 --- a/src/pulsecore/asyncmsgq.c +++ b/src/pulsecore/asyncmsgq.c @@ -26,14 +26,16 @@ #include <unistd.h> #include <errno.h> +#include <pulse/xmalloc.h> + #include <pulsecore/atomic.h> +#include <pulsecore/macro.h> #include <pulsecore/log.h> #include <pulsecore/thread.h> #include <pulsecore/semaphore.h> #include <pulsecore/macro.h> #include <pulsecore/core-util.h> #include <pulsecore/flist.h> -#include <pulse/xmalloc.h> #include "asyncmsgq.h" @@ -76,7 +78,7 @@ static void asyncmsgq_free(pa_asyncmsgq *a) { struct asyncmsgq_item *i; pa_assert(a); - while ((i = pa_asyncq_pop(a->asyncq, 0))) { + while ((i = pa_asyncq_pop(a->asyncq, FALSE))) { pa_assert(!i->semaphore); @@ -246,7 +248,7 @@ int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code) { pa_memchunk chunk; int ret; - if (pa_asyncmsgq_get(a, &o, &c, &data, &offset, &chunk, 1) < 0) + if (pa_asyncmsgq_get(a, &o, &c, &data, &offset, &chunk, TRUE) < 0) return -1; ret = pa_asyncmsgq_dispatch(o, c, data, offset, &chunk); @@ -269,7 +271,7 @@ int pa_asyncmsgq_process_one(pa_asyncmsgq *a) { pa_assert(PA_REFCNT_VALUE(a) > 0); - if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, 0) < 0) + if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, FALSE) < 0) return 0; pa_asyncmsgq_ref(a); @@ -323,3 +325,35 @@ int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_ return 0; } + +void pa_asyncmsgq_flush(pa_asyncmsgq *a, pa_bool_t run) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + for (;;) { + pa_msgobject *object; + int code; + void *data; + int64_t offset; + pa_memchunk chunk; + int ret; + + if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, FALSE) < 0) + return; + + if (!run) { + pa_asyncmsgq_done(a, -1); + continue; + } + + pa_asyncmsgq_ref(a); + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(a, ret); + pa_asyncmsgq_unref(a); + } +} + +pa_bool_t pa_asyncmsgq_dispatching(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return !!a->current; +} diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h index 1f38207a..1085c2f0 100644 --- a/src/pulsecore/asyncmsgq.h +++ b/src/pulsecore/asyncmsgq.h @@ -66,6 +66,8 @@ void pa_asyncmsgq_done(pa_asyncmsgq *q, int ret); int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code); int pa_asyncmsgq_process_one(pa_asyncmsgq *a); +void pa_asyncmsgq_flush(pa_asyncmsgq *a, pa_bool_t run); + /* For the reading side */ int pa_asyncmsgq_read_fd(pa_asyncmsgq *q); int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a); @@ -76,4 +78,6 @@ int pa_asyncmsgq_write_fd(pa_asyncmsgq *q); void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a); void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a); +pa_bool_t pa_asyncmsgq_dispatching(pa_asyncmsgq *a); + #endif diff --git a/src/pulsecore/aupdate.c b/src/pulsecore/aupdate.c index 56ebb8e5..85b6e00e 100644 --- a/src/pulsecore/aupdate.c +++ b/src/pulsecore/aupdate.c @@ -39,6 +39,7 @@ struct pa_aupdate { pa_atomic_t read_lock; pa_mutex *write_lock; pa_semaphore *semaphore; + pa_bool_t swapped; }; pa_aupdate *pa_aupdate_new(void) { @@ -101,6 +102,8 @@ unsigned pa_aupdate_write_begin(pa_aupdate *a) { n = (unsigned) pa_atomic_load(&a->read_lock); + a->swapped = FALSE; + return !WHICH(n); } @@ -119,11 +122,16 @@ unsigned pa_aupdate_write_swap(pa_aupdate *a) { break; } + a->swapped = TRUE; + return WHICH(n); } void pa_aupdate_write_end(pa_aupdate *a) { pa_assert(a); + if (!a->swapped) + pa_aupdate_write_swap(a); + pa_mutex_unlock(a->write_lock); } diff --git a/src/pulsecore/aupdate.h b/src/pulsecore/aupdate.h index 072e382d..fb38ffa2 100644 --- a/src/pulsecore/aupdate.h +++ b/src/pulsecore/aupdate.h @@ -93,6 +93,10 @@ unsigned pa_aupdate_write_swap(pa_aupdate *a); * pa_update_write_end(a) * } * + * In some cases keeping both structures up-to-date might not be + * necessary, since they are fully rebuilt on each iteration + * anyway. In that case you may leave the _write_swap() call out, it + * will then be done implicitly in the _write_end() invocation. */ #endif diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c index 15613e27..d671e367 100644 --- a/src/pulsecore/authkey.c +++ b/src/pulsecore/authkey.c @@ -70,10 +70,6 @@ static int generate(int fd, void *ret_data, size_t length) { #define O_BINARY 0 #endif -#ifndef O_NOCTTY -#define O_NOCTTY 0 -#endif - /* Load an euthorization cookie from file fn and store it in data. If * the cookie file doesn't exist, create it */ static int load(const char *fn, void *data, size_t length) { @@ -86,9 +82,9 @@ static int load(const char *fn, void *data, size_t length) { pa_assert(data); pa_assert(length > 0); - if ((fd = open(fn, O_RDWR|O_CREAT|O_BINARY|O_NOCTTY, S_IRUSR|S_IWUSR)) < 0) { + if ((fd = pa_open_cloexec(fn, O_RDWR|O_CREAT|O_BINARY, S_IRUSR|S_IWUSR)) < 0) { - if (errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY|O_NOCTTY)) < 0) { + if (errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY)) < 0) { pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); goto finish; } else @@ -204,7 +200,7 @@ int pa_authkey_save(const char *fn, const void *data, size_t length) { if (!(p = normalize_path(fn))) return -2; - if ((fd = open(p, O_RDWR|O_CREAT|O_NOCTTY, S_IRUSR|S_IWUSR)) < 0) { + if ((fd = pa_open_cloexec(p, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) { pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); goto finish; } diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index e2c3c066..82a44d84 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -328,7 +328,7 @@ static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char ss[PA_SAMPLE_SPEC_SNPRINT_MAX]; char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; - char s[256]; + char bytes[PA_BYTES_SNPRINT_MAX]; const pa_mempool_stat *stat; unsigned k; pa_sink *def_sink; @@ -352,22 +352,22 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b pa_strbuf_printf(buf, "Memory blocks currently allocated: %u, size: %s.\n", (unsigned) pa_atomic_load(&stat->n_allocated), - pa_bytes_snprint(s, sizeof(s), (unsigned) pa_atomic_load(&stat->allocated_size))); + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&stat->allocated_size))); pa_strbuf_printf(buf, "Memory blocks allocated during the whole lifetime: %u, size: %s.\n", (unsigned) pa_atomic_load(&stat->n_accumulated), - pa_bytes_snprint(s, sizeof(s), (unsigned) pa_atomic_load(&stat->accumulated_size))); + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&stat->accumulated_size))); pa_strbuf_printf(buf, "Memory blocks imported from other processes: %u, size: %s.\n", (unsigned) pa_atomic_load(&stat->n_imported), - pa_bytes_snprint(s, sizeof(s), (unsigned) pa_atomic_load(&stat->imported_size))); + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&stat->imported_size))); pa_strbuf_printf(buf, "Memory blocks exported to other processes: %u, size: %s.\n", (unsigned) pa_atomic_load(&stat->n_exported), - pa_bytes_snprint(s, sizeof(s), (unsigned) pa_atomic_load(&stat->exported_size))); + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&stat->exported_size))); pa_strbuf_printf(buf, "Total sample cache size: %s.\n", - pa_bytes_snprint(s, sizeof(s), (unsigned) pa_scache_total_size(c))); + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_scache_total_size(c))); pa_strbuf_printf(buf, "Default sample spec: %s\n", pa_sample_spec_snprint(ss, sizeof(ss), &c->default_sample_spec)); @@ -529,8 +529,8 @@ static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return -1; } - pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume); - pa_sink_set_volume(sink, &cvolume, TRUE, TRUE, TRUE, TRUE); + pa_cvolume_set(&cvolume, 1, volume); + pa_sink_set_volume(sink, &cvolume, TRUE, TRUE); return 0; } @@ -571,7 +571,7 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb return -1; } - pa_cvolume_set(&cvolume, si->sample_spec.channels, volume); + pa_cvolume_set(&cvolume, 1, volume); pa_sink_input_set_volume(si, &cvolume, TRUE, TRUE); return 0; } @@ -607,7 +607,7 @@ static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf * return -1; } - pa_cvolume_set(&cvolume, source->sample_spec.channels, volume); + pa_cvolume_set(&cvolume, 1, volume); pa_source_set_volume(source, &cvolume, TRUE); return 0; } @@ -1549,7 +1549,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b pa_sink *sink; pa_source *source; pa_card *card; - int nl; + pa_bool_t nl; uint32_t idx; char txt[256]; time_t now; @@ -1567,7 +1567,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime(&now)); #endif - for (m = pa_idxset_first(c->modules, &idx); m; m = pa_idxset_next(c->modules, &idx)) { + PA_IDXSET_FOREACH(m, c->modules, idx) { pa_strbuf_printf(buf, "load-module %s", m->name); @@ -1577,58 +1577,58 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b pa_strbuf_puts(buf, "\n"); } - nl = 0; - - for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) { + nl = FALSE; + PA_IDXSET_FOREACH(sink, c->sinks, idx) { if (!nl) { pa_strbuf_puts(buf, "\n"); - nl = 1; + nl = TRUE; } - pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, FALSE, TRUE))); + pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_max(pa_sink_get_volume(sink, FALSE))); pa_strbuf_printf(buf, "set-sink-mute %s %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink, FALSE))); pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(pa_sink_get_state(sink) == PA_SINK_SUSPENDED)); } - for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) { + nl = FALSE; + PA_IDXSET_FOREACH(source, c->sources, idx) { if (!nl) { pa_strbuf_puts(buf, "\n"); - nl = 1; + nl = TRUE; } - pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_avg(pa_source_get_volume(source, FALSE))); + pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_max(pa_source_get_volume(source, FALSE))); pa_strbuf_printf(buf, "set-source-mute %s %s\n", source->name, pa_yes_no(pa_source_get_mute(source, FALSE))); pa_strbuf_printf(buf, "suspend-source %s %s\n", source->name, pa_yes_no(pa_source_get_state(source) == PA_SOURCE_SUSPENDED)); } - for (card = pa_idxset_first(c->cards, &idx); card; card = pa_idxset_next(c->cards, &idx)) { + nl = FALSE; + PA_IDXSET_FOREACH(card, c->cards, idx) { if (!nl) { pa_strbuf_puts(buf, "\n"); - nl = 1; + nl = TRUE; } if (card->active_profile) pa_strbuf_printf(buf, "set-card-profile %s %s\n", card->name, card->active_profile->name); } - nl = 0; - + nl = FALSE; if ((sink = pa_namereg_get_default_sink(c))) { if (!nl) { pa_strbuf_puts(buf, "\n"); - nl = 1; + nl = TRUE; } + pa_strbuf_printf(buf, "set-default-sink %s\n", sink->name); } if ((source = pa_namereg_get_default_source(c))) { - if (!nl) { + if (!nl) pa_strbuf_puts(buf, "\n"); - nl = 1; - } + pa_strbuf_printf(buf, "set-default-source %s\n", source->name); } @@ -1804,7 +1804,7 @@ int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_b if (!fail) fail = &_fail; - if (!(f = fopen(fn, "r"))) { + if (!(f = pa_fopen_cloexec(fn, "r"))) { pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno)); if (!*fail) ret = 0; @@ -1813,8 +1813,6 @@ int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_b ret = pa_cli_command_execute_file_stream(c, f, buf, fail); - ret = 0; - fail: if (f) fclose(f); diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 9395513d..23a57d37 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -232,6 +232,7 @@ char *pa_sink_list_to_string(pa_core *c) { "\tflags: %s%s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tsuspend cause: %s%s%s%s\n" + "\tpriority: %u\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" "\tbase volume: %s%s%s\n" @@ -262,10 +263,11 @@ char *pa_sink_list_to_string(pa_core *c) { sink->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", sink->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", sink->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "", - pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE, FALSE)), + sink->priority, + pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE)), sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t " : "", - sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_get_volume(sink, FALSE, FALSE)) : "", - pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE, FALSE), &sink->channel_map), + sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_get_volume(sink, FALSE)) : "", + pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE), &sink->channel_map), pa_volume_snprint(v, sizeof(v), sink->base_volume), sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t " : "", sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), sink->base_volume) : "", @@ -296,7 +298,7 @@ char *pa_sink_list_to_string(pa_core *c) { pa_strbuf_printf( s, "\tfixed latency: %0.2f ms\n", - (double) pa_sink_get_requested_latency(sink) / PA_USEC_PER_MSEC); + (double) pa_sink_get_fixed_latency(sink) / PA_USEC_PER_MSEC); if (sink->card) pa_strbuf_printf(s, "\tcard: %u <%s>\n", sink->card->index, sink->card->name); @@ -356,6 +358,7 @@ char *pa_source_list_to_string(pa_core *c) { "\tflags: %s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tsuspend cause: %s%s%s%s\n" + "\tpriority: %u\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" "\tbase volume: %s%s%s\n" @@ -383,6 +386,7 @@ char *pa_source_list_to_string(pa_core *c) { source->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", source->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", source->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "", + source->priority, pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source, FALSE)), source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t " : "", source->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_source_get_volume(source, FALSE)) : "", @@ -415,7 +419,7 @@ char *pa_source_list_to_string(pa_core *c) { pa_strbuf_printf( s, "\tfixed latency: %0.2f ms\n", - (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC); + (double) pa_source_get_fixed_latency(source) / PA_USEC_PER_MSEC); if (source->monitor_of) pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index); @@ -482,7 +486,7 @@ char *pa_source_output_list_to_string(pa_core *c) { s, " index: %u\n" "\tdriver: <%s>\n" - "\tflags: %s%s%s%s%s%s%s%s%s%s\n" + "\tflags: %s%s%s%s%s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tsource: %u <%s>\n" "\tcurrent latency: %0.2f ms\n" @@ -501,7 +505,8 @@ char *pa_source_output_list_to_string(pa_core *c) { o->flags & PA_SOURCE_OUTPUT_FIX_RATE ? "FIX_RATE " : "", o->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", o->flags & PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND ? "DONT_INHIBIT_AUTO_SUSPEND " : "", - o->flags & PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND ? "FAIL_ON_SUSPEND " : "", + o->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_ON_SUSPEND " : "", + o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "", state_table[pa_source_output_get_state(o)], o->source->index, o->source->name, (double) pa_source_output_get_latency(o, NULL) / PA_USEC_PER_MSEC, @@ -564,7 +569,7 @@ char *pa_sink_input_list_to_string(pa_core *c) { s, " index: %u\n" "\tdriver: <%s>\n" - "\tflags: %s%s%s%s%s%s%s%s%s%s\n" + "\tflags: %s%s%s%s%s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tsink: %u <%s>\n" "\tvolume: %s\n" @@ -587,7 +592,8 @@ char *pa_sink_input_list_to_string(pa_core *c) { i->flags & PA_SINK_INPUT_FIX_RATE ? "FIX_RATE " : "", i->flags & PA_SINK_INPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", i->flags & PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND ? "DONT_INHIBIT_AUTO_SUSPEND " : "", - i->flags & PA_SINK_INPUT_FAIL_ON_SUSPEND ? "FAIL_ON_SUSPEND " : "", + i->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_SUSPEND " : "", + i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "", state_table[pa_sink_input_get_state(i)], i->sink->index, i->sink->name, pa_cvolume_snprint(cv, sizeof(cv), &v), diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c index 2dc9a223..34b4d6fe 100644 --- a/src/pulsecore/conf-parser.c +++ b/src/pulsecore/conf-parser.c @@ -113,7 +113,7 @@ static int parse_line(const char *filename, unsigned line, char **section, const return 0; if (pa_startswith(b, ".include ")) { - char *path, *fn; + char *path = NULL, *fn; int r; fn = strip(b+9); @@ -168,7 +168,7 @@ int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void pa_assert(filename); pa_assert(t); - if (!f && !(f = fopen(filename, "r"))) { + if (!f && !(f = pa_fopen_cloexec(filename, "r"))) { if (errno == ENOENT) { pa_log_debug("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno)); r = 0; @@ -278,6 +278,30 @@ int pa_config_parse_bool(const char *filename, unsigned line, const char *sectio return 0; } +int pa_config_parse_not_bool( + const char *filename, unsigned line, + const char *section, + const char *lvalue, const char *rvalue, + void *data, void *userdata) { + + int k; + pa_bool_t *b = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if ((k = pa_parse_boolean(rvalue)) < 0) { + pa_log("[%s:%u] Failed to parse boolean value: %s", filename, line, rvalue); + return -1; + } + + *b = !k; + + return 0; +} + int pa_config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { char **s = data; diff --git a/src/pulsecore/conf-parser.h b/src/pulsecore/conf-parser.h index 08e17ca7..c6c8a148 100644 --- a/src/pulsecore/conf-parser.h +++ b/src/pulsecore/conf-parser.h @@ -47,6 +47,7 @@ int pa_config_parse_int(const char *filename, unsigned line, const char *section int pa_config_parse_unsigned(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); int pa_config_parse_size(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); int pa_config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); +int pa_config_parse_not_bool(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); int pa_config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); #endif diff --git a/src/pulsecore/core-rtclock.c b/src/pulsecore/core-rtclock.c index 3b3e3a64..4fe0a47b 100644 --- a/src/pulsecore/core-rtclock.c +++ b/src/pulsecore/core-rtclock.c @@ -33,6 +33,12 @@ #include <sys/prctl.h> #endif +#ifdef OS_IS_DARWIN +#include <CoreServices/CoreServices.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#endif + #include <pulse/timeval.h> #include <pulsecore/macro.h> #include <pulsecore/core-error.h> @@ -47,7 +53,8 @@ pa_usec_t pa_rtclock_age(const struct timeval *tv) { } struct timeval *pa_rtclock_get(struct timeval *tv) { -#ifdef HAVE_CLOCK_GETTIME + +#if defined(HAVE_CLOCK_GETTIME) struct timespec ts; #ifdef CLOCK_MONOTONIC @@ -59,7 +66,7 @@ struct timeval *pa_rtclock_get(struct timeval *tv) { no_monotonic = TRUE; if (no_monotonic) -#endif +#endif /* CLOCK_MONOTONIC */ pa_assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); pa_assert(tv); @@ -69,7 +76,30 @@ struct timeval *pa_rtclock_get(struct timeval *tv) { return tv; -#else /* HAVE_CLOCK_GETTIME */ +#elif defined(OS_IS_DARWIN) + static mach_timebase_info_data_t tbi; + uint64_t nticks; + uint64_t time_nsec; + + /* Refer Apple ADC QA1398 + Also: http://devworld.apple.com/documentation/Darwin/Conceptual/KernelProgramming/services/services.html + + Note: argument is timespec NOT timeval (timespec uses nsec, timeval uses usec) + */ + + /* try and be a mite efficient - maybe I should keep the N/D as a float !? */ + if (tbi.denom == 0) + mach_timebase_info(&tbi); + + nticks = mach_absolute_time(); + time_nsec = nticks * tbi.numer / tbi.denom; // see above + + tv->tv_sec = time_nsec / PA_NSEC_PER_SEC; + tv->tv_usec = time_nsec / PA_NSEC_PER_USEC; + + return tv; + +#else /* OS_IS_DARWIN */ return pa_gettimeofday(tv); @@ -77,19 +107,30 @@ struct timeval *pa_rtclock_get(struct timeval *tv) { } pa_bool_t pa_rtclock_hrtimer(void) { -#ifdef HAVE_CLOCK_GETTIME + +#if defined(HAVE_CLOCK_GETTIME) struct timespec ts; #ifdef CLOCK_MONOTONIC + if (clock_getres(CLOCK_MONOTONIC, &ts) >= 0) return ts.tv_sec == 0 && ts.tv_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC); -#endif +#endif /* CLOCK_MONOTONIC */ pa_assert_se(clock_getres(CLOCK_REALTIME, &ts) == 0); return ts.tv_sec == 0 && ts.tv_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC); -#else /* HAVE_CLOCK_GETTIME */ +#elif defined (OS_IS_DARWIN) + mach_timebase_info_data_t tbi; + uint64_t time_nsec; + + mach_timebase_info(&tbi); + /* nsec = nticks * (N/D) - we want 1 tick == resolution !? */ + time_nsec = tbi.numer / tbi.denom; + return time_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC); + +#else /* OS_IS_DARWIN */ return FALSE; #endif @@ -98,6 +139,7 @@ pa_bool_t pa_rtclock_hrtimer(void) { #define TIMER_SLACK_NS (int) ((500 * PA_NSEC_PER_USEC)) void pa_rtclock_hrtimer_enable(void) { + #ifdef PR_SET_TIMERSLACK int slack_ns; @@ -132,6 +174,8 @@ struct timeval* pa_rtclock_from_wallclock(struct timeval *tv) { pa_assert(tv); + /* pa_timeval_sub() saturates on underflow! */ + if (pa_timeval_cmp(&wc_now, tv) < 0) pa_timeval_add(&rt_now, pa_timeval_diff(tv, &wc_now)); else @@ -144,13 +188,29 @@ struct timeval* pa_rtclock_from_wallclock(struct timeval *tv) { } pa_usec_t pa_timespec_load(const struct timespec *ts) { - pa_assert(ts); + + if (PA_UNLIKELY(!ts)) + return PA_USEC_INVALID; return (pa_usec_t) ts->tv_sec * PA_USEC_PER_SEC + (pa_usec_t) ts->tv_nsec / PA_NSEC_PER_USEC; } +struct timespec* pa_timespec_store(struct timespec *ts, pa_usec_t v) { + pa_assert(ts); + + if (PA_UNLIKELY(v == PA_USEC_INVALID)) { + ts->tv_sec = PA_INT_TYPE_MAX(time_t); + ts->tv_nsec = (long) (PA_NSEC_PER_SEC-1); + return NULL; + } + + ts->tv_sec = (time_t) (v / PA_USEC_PER_SEC); + ts->tv_nsec = (long) ((v % PA_USEC_PER_SEC) * PA_NSEC_PER_USEC); + + return ts; +} static struct timeval* wallclock_from_rtclock(struct timeval *tv) { @@ -162,6 +222,8 @@ static struct timeval* wallclock_from_rtclock(struct timeval *tv) { pa_assert(tv); + /* pa_timeval_sub() saturates on underflow! */ + if (pa_timeval_cmp(&rt_now, tv) < 0) pa_timeval_add(&wc_now, pa_timeval_diff(tv, &rt_now)); else diff --git a/src/pulsecore/core-rtclock.h b/src/pulsecore/core-rtclock.h index 9f5ae2dd..3b393ed5 100644 --- a/src/pulsecore/core-rtclock.h +++ b/src/pulsecore/core-rtclock.h @@ -44,6 +44,7 @@ void pa_rtclock_hrtimer_enable(void); struct timeval* pa_rtclock_from_wallclock(struct timeval *tv); pa_usec_t pa_timespec_load(const struct timespec *ts); +struct timespec* pa_timespec_store(struct timespec *ts, pa_usec_t v); struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, pa_bool_t rtclock); diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index fde12ecf..1fb81d0d 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -335,12 +335,12 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t pass_volume = TRUE; - if (e->volume_is_set && volume != (pa_volume_t) -1) { + if (e->volume_is_set && volume != PA_VOLUME_INVALID) { pa_cvolume_set(&r, e->sample_spec.channels, volume); pa_sw_cvolume_multiply(&r, &r, &e->volume); } else if (e->volume_is_set) r = e->volume; - else if (volume != (pa_volume_t) -1) + else if (volume != PA_VOLUME_INVALID) pa_cvolume_set(&r, e->sample_spec.channels, volume); else pass_volume = FALSE; diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index d4baf697..93ddf301 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -101,6 +101,10 @@ #include "rtkit.h" #endif +#ifdef __linux__ +#include <sys/personality.h> +#endif + #include <pulse/xmalloc.h> #include <pulse/util.h> #include <pulse/utf8.h> @@ -111,6 +115,8 @@ #include <pulsecore/macro.h> #include <pulsecore/thread.h> #include <pulsecore/strbuf.h> +#include <pulsecore/usergroup.h> +#include <pulsecore/strlist.h> #include "core-util.h" @@ -119,6 +125,8 @@ #define MSG_NOSIGNAL 0 #endif +static pa_strlist *recorded_env = NULL; + #ifdef OS_IS_WIN32 #define PULSE_ROOTENV "PULSE_ROOT" @@ -583,13 +591,13 @@ static int set_scheduler(int rtprio) { sp.sched_priority = rtprio; #ifdef SCHED_RESET_ON_FORK - if ((r = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &sp)) == 0) { + if (pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &sp) == 0) { pa_log_debug("SCHED_RR|SCHED_RESET_ON_FORK worked."); return 0; } #endif - if ((r = pthread_setschedparam(pthread_self(), SCHED_RR, &sp)) == 0) { + if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0) { pa_log_debug("SCHED_RR worked."); return 0; } @@ -604,6 +612,11 @@ static int set_scheduler(int rtprio) { return -1; } + /* We need to disable exit on disconnect because otherwise + * dbus_shutdown will kill us. See + * https://bugs.freedesktop.org/show_bug.cgi?id=16924 */ + dbus_connection_set_exit_on_disconnect(bus, FALSE); + r = rtkit_make_realtime(bus, 0, rtprio); dbus_connection_unref(bus); @@ -672,6 +685,11 @@ static int set_nice(int nice_level) { return -1; } + /* We need to disable exit on disconnect because otherwise + * dbus_shutdown will kill us. See + * https://bugs.freedesktop.org/show_bug.cgi?id=16924 */ + dbus_connection_set_exit_on_disconnect(bus, FALSE); + r = rtkit_make_high_priority(bus, 0, nice_level); dbus_connection_unref(bus); @@ -768,7 +786,6 @@ int pa_match(const char *expr, const char *v) { /* Try to parse a boolean string value.*/ int pa_parse_boolean(const char *v) { const char *expr; - int r; pa_assert(v); /* First we check language independant */ @@ -780,12 +797,12 @@ int pa_parse_boolean(const char *v) { /* And then we check language dependant */ if ((expr = nl_langinfo(YESEXPR))) if (expr[0]) - if ((r = pa_match(expr, v)) > 0) + if (pa_match(expr, v) > 0) return 1; if ((expr = nl_langinfo(NOEXPR))) if (expr[0]) - if ((r = pa_match(expr, v)) > 0) + if (pa_match(expr, v) > 0) return 0; errno = EINVAL; @@ -965,54 +982,24 @@ fail: /* Check whether the specified GID and the group name match */ static int is_group(gid_t gid, const char *name) { - struct group group, *result = NULL; - long n; - void *data; + struct group *group = NULL; int r = -1; -#ifdef HAVE_GETGRGID_R -#ifdef _SC_GETGR_R_SIZE_MAX - n = sysconf(_SC_GETGR_R_SIZE_MAX); -#else - n = -1; -#endif - if (n <= 0) - n = 512; - - data = pa_xmalloc((size_t) n); - errno = 0; - if (getgrgid_r(gid, &group, data, (size_t) n, &result) < 0 || !result) { - pa_log("getgrgid_r(%u): %s", (unsigned) gid, pa_cstrerror(errno)); - + if (!(group = pa_getgrgid_malloc(gid))) + { if (!errno) errno = ENOENT; - goto finish; - } - - r = strcmp(name, result->gr_name) == 0; - -finish: - pa_xfree(data); -#else - /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) that do not - * support getgrgid_r. */ - - errno = 0; - if (!(result = getgrgid(gid))) { - pa_log("getgrgid(%u): %s", gid, pa_cstrerror(errno)); - - if (!errno) - errno = ENOENT; + pa_log("pa_getgrgid_malloc(%u): %s", gid, pa_cstrerror(errno)); goto finish; } - r = strcmp(name, result->gr_name) == 0; + r = strcmp(name, group->gr_name) == 0; finish: -#endif + pa_getgrgid_free(group); return r; } @@ -1061,38 +1048,12 @@ finish: /* Check whether the specifc user id is a member of the specified group */ int pa_uid_in_group(uid_t uid, const char *name) { - char *g_buf, *p_buf; - long g_n, p_n; - struct group grbuf, *gr; + struct group *group = NULL; char **i; int r = -1; -#ifdef _SC_GETGR_R_SIZE_MAX - g_n = sysconf(_SC_GETGR_R_SIZE_MAX); -#else - g_n = -1; -#endif - if (g_n <= 0) - g_n = 512; - - g_buf = pa_xmalloc((size_t) g_n); - -#ifdef _SC_GETPW_R_SIZE_MAX - p_n = sysconf(_SC_GETPW_R_SIZE_MAX); -#else - p_n = -1; -#endif - if (p_n <= 0) - p_n = 512; - - p_buf = pa_xmalloc((size_t) p_n); - errno = 0; -#ifdef HAVE_GETGRNAM_R - if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr) -#else - if (!(gr = getgrnam(name))) -#endif + if (!(group = pa_getgrnam_malloc(name))) { if (!errno) errno = ENOENT; @@ -1100,25 +1061,24 @@ int pa_uid_in_group(uid_t uid, const char *name) { } r = 0; - for (i = gr->gr_mem; *i; i++) { - struct passwd pwbuf, *pw; + for (i = group->gr_mem; *i; i++) { + struct passwd *pw = NULL; -#ifdef HAVE_GETPWNAM_R - if (getpwnam_r(*i, &pwbuf, p_buf, (size_t) p_n, &pw) != 0 || !pw) -#else - if (!(pw = getpwnam(*i))) -#endif + errno = 0; + if (!(pw = pa_getpwnam_malloc(*i))) continue; - if (pw->pw_uid == uid) { + if (pw->pw_uid == uid) r = 1; + + pa_getpwnam_free(pw); + + if (r == 1) break; - } } finish: - pa_xfree(g_buf); - pa_xfree(p_buf); + pa_getgrnam_free(group); return r; } @@ -1126,26 +1086,10 @@ finish: /* Get the GID of a gfiven group, return (gid_t) -1 on failure. */ gid_t pa_get_gid_of_group(const char *name) { gid_t ret = (gid_t) -1; - char *g_buf; - long g_n; - struct group grbuf, *gr; - -#ifdef _SC_GETGR_R_SIZE_MAX - g_n = sysconf(_SC_GETGR_R_SIZE_MAX); -#else - g_n = -1; -#endif - if (g_n <= 0) - g_n = 512; - - g_buf = pa_xmalloc((size_t) g_n); + struct group *gr = NULL; errno = 0; -#ifdef HAVE_GETGRNAM_R - if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr) -#else - if (!(gr = getgrnam(name))) -#endif + if (!(gr = pa_getgrnam_malloc(name))) { if (!errno) errno = ENOENT; @@ -1155,7 +1099,7 @@ gid_t pa_get_gid_of_group(const char *name) { ret = gr->gr_gid; finish: - pa_xfree(g_buf); + pa_getgrnam_free(gr); return ret; } @@ -1250,16 +1194,13 @@ char* pa_strip_nl(char *s) { /* Create a temporary lock file and lock it. */ int pa_lock_lockfile(const char *fn) { - int fd = -1; + int fd; pa_assert(fn); for (;;) { struct stat st; - if ((fd = open(fn, O_CREAT|O_RDWR -#ifdef O_NOCTTY - |O_NOCTTY -#endif + if ((fd = pa_open_cloexec(fn, O_CREAT|O_RDWR #ifdef O_NOFOLLOW |O_NOFOLLOW #endif @@ -1293,8 +1234,6 @@ int pa_lock_lockfile(const char *fn) { fd = -1; goto fail; } - - fd = -1; } return fd; @@ -1436,19 +1375,10 @@ static char* make_random_dir(mode_t m) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; - const char *tmpdir; char *fn; size_t pathlen; - if (!(tmpdir = getenv("TMPDIR"))) - if (!(tmpdir = getenv("TMP"))) - if (!(tmpdir = getenv("TEMP"))) - tmpdir = getenv("TEMPDIR"); - - if (!tmpdir || !pa_is_path_absolute(tmpdir)) - tmpdir = "/tmp"; - - fn = pa_sprintf_malloc("%s/pulse-XXXXXXXXXXXX", tmpdir); + fn = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse-XXXXXXXXXXXX", pa_get_temp_dir()); pathlen = strlen(fn); for (;;) { @@ -1670,7 +1600,7 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env fn = buf; #endif - if ((f = fopen(fn, "r"))) { + if ((f = pa_fopen_cloexec(fn, "r"))) { if (result) *result = pa_xstrdup(fn); @@ -1704,7 +1634,7 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env fn = buf; #endif - if ((f = fopen(fn, "r"))) { + if ((f = pa_fopen_cloexec(fn, "r"))) { if (result) *result = pa_xstrdup(fn); @@ -1731,7 +1661,7 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env global = buf; #endif - if ((f = fopen(global, "r"))) { + if ((f = pa_fopen_cloexec(global, "r"))) { if (result) *result = pa_xstrdup(global); @@ -1945,17 +1875,17 @@ char *pa_make_path_absolute(const char *p) { static char *get_path(const char *fn, pa_bool_t prependmid, pa_bool_t rt) { char *rtp; - if (pa_is_path_absolute(fn)) - return pa_xstrdup(fn); - rtp = rt ? pa_get_runtime_dir() : pa_get_state_dir(); - if (!rtp) - return NULL; - if (fn) { char *r; + if (pa_is_path_absolute(fn)) + return pa_xstrdup(fn); + + if (!rtp) + return NULL; + if (prependmid) { char *mid; @@ -2291,7 +2221,7 @@ int pa_close_all(int except_fd, ...) { va_end(ap); r = pa_close_allv(p); - free(p); + pa_xfree(p); return r; } @@ -2462,7 +2392,7 @@ int pa_reset_sigs(int except, ...) { p[i++] = except; while ((sig = va_arg(ap, int)) >= 0) - sig = p[i++]; + p[i++] = sig; } p[i] = -1; @@ -2519,9 +2449,38 @@ void pa_set_env(const char *key, const char *value) { pa_assert(key); pa_assert(value); + /* This is not thread-safe */ + putenv(pa_sprintf_malloc("%s=%s", key, value)); } +void pa_set_env_and_record(const char *key, const char *value) { + pa_assert(key); + pa_assert(value); + + /* This is not thread-safe */ + + pa_set_env(key, value); + recorded_env = pa_strlist_prepend(recorded_env, key); +} + +void pa_unset_env_recorded(void) { + + /* This is not thread-safe */ + + for (;;) { + char *s; + + recorded_env = pa_strlist_pop(recorded_env, &s); + + if (!s) + break; + + unsetenv(s); + pa_xfree(s); + } +} + pa_bool_t pa_in_system_mode(void) { const char *e; @@ -2601,7 +2560,7 @@ char *pa_machine_id(void) { * since it fits perfectly our needs and is not as volatile as the * hostname which might be set from dhcp. */ - if ((f = fopen(PA_MACHINE_ID, "r"))) { + if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r"))) { char ln[34] = "", *r; r = fgets(ln, sizeof(ln)-1, f); @@ -2716,6 +2675,28 @@ char *pa_replace(const char*s, const char*a, const char *b) { return pa_strbuf_tostring_free(sb); } +char *pa_escape(const char *p, const char *chars) { + const char *s; + const char *c; + pa_strbuf *buf = pa_strbuf_new(); + + for (s = p; *s; ++s) { + if (*s == '\\') + pa_strbuf_putc(buf, '\\'); + else if (chars) { + for (c = chars; *c; ++c) { + if (*s == *c) { + pa_strbuf_putc(buf, '\\'); + break; + } + } + } + pa_strbuf_putc(buf, *s); + } + + return pa_strbuf_tostring_free(buf); +} + char *pa_unescape(char *p) { char *s, *d; pa_bool_t escaped = FALSE; @@ -2855,3 +2836,271 @@ size_t pa_pipe_buf(int fd) { return 4096; #endif } + +void pa_reset_personality(void) { + +#ifdef __linux__ + if (personality(PER_LINUX) < 0) + pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno)); +#endif + +} + +#if defined(__linux__) && !defined(__OPTIMIZE__) + +pa_bool_t pa_run_from_build_tree(void) { + char *rp; + pa_bool_t b = FALSE; + + /* We abuse __OPTIMIZE__ as a check whether we are a debug build + * or not. */ + + if ((rp = pa_readlink("/proc/self/exe"))) { + b = pa_startswith(rp, PA_BUILDDIR); + pa_xfree(rp); + } + + return b; +} + +#endif + +const char *pa_get_temp_dir(void) { + const char *t; + + if ((t = getenv("TMPDIR")) && + pa_is_path_absolute(t)) + return t; + + if ((t = getenv("TMP")) && + pa_is_path_absolute(t)) + return t; + + if ((t = getenv("TEMP")) && + pa_is_path_absolute(t)) + return t; + + if ((t = getenv("TEMPDIR")) && + pa_is_path_absolute(t)) + return t; + + return "/tmp"; +} + +int pa_open_cloexec(const char *fn, int flags, mode_t mode) { + int fd; + +#ifdef O_NOCTTY + flags |= O_NOCTTY; +#endif + +#ifdef O_CLOEXEC + if ((fd = open(fn, flags|O_CLOEXEC, mode)) >= 0) + goto finish; + + if (errno != EINVAL) + return fd; +#endif + + if ((fd = open(fn, flags, mode)) < 0) + return fd; + +finish: + /* Some implementations might simply ignore O_CLOEXEC if it is not + * understood, make sure FD_CLOEXEC is enabled anyway */ + + pa_make_fd_cloexec(fd); + return fd; +} + +int pa_socket_cloexec(int domain, int type, int protocol) { + int fd; + +#ifdef SOCK_CLOEXEC + if ((fd = socket(domain, type | SOCK_CLOEXEC, protocol)) >= 0) + goto finish; + + if (errno != EINVAL) + return fd; +#endif + + if ((fd = socket(domain, type, protocol)) < 0) + return fd; + +finish: + /* Some implementations might simply ignore SOCK_CLOEXEC if it is + * not understood, make sure FD_CLOEXEC is enabled anyway */ + + pa_make_fd_cloexec(fd); + return fd; +} + +int pa_pipe_cloexec(int pipefd[2]) { + int r; + +#ifdef HAVE_PIPE2 + if ((r = pipe2(pipefd, O_CLOEXEC)) >= 0) + goto finish; + + if (errno != EINVAL && errno != ENOSYS) + return r; + +#endif + + if ((r = pipe(pipefd)) < 0) + return r; + +finish: + pa_make_fd_cloexec(pipefd[0]); + pa_make_fd_cloexec(pipefd[1]); + + return 0; +} + +int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + int fd; + +#ifdef HAVE_ACCEPT4 + if ((fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC)) >= 0) + goto finish; + + if (errno != EINVAL && errno != ENOSYS) + return fd; + +#endif + + if ((fd = accept(sockfd, addr, addrlen)) < 0) + return fd; + +finish: + pa_make_fd_cloexec(fd); + return fd; +} + +FILE* pa_fopen_cloexec(const char *path, const char *mode) { + FILE *f; + char *m; + + m = pa_sprintf_malloc("%se", mode); + + errno = 0; + if ((f = fopen(path, m))) { + pa_xfree(m); + goto finish; + } + + pa_xfree(m); + + if (errno != EINVAL) + return NULL; + + if (!(f = fopen(path, mode))) + return NULL; + +finish: + pa_make_fd_cloexec(fileno(f)); + return f; +} + +void pa_nullify_stdfds(void) { + +#ifndef OS_IS_WIN32 + pa_close(STDIN_FILENO); + pa_close(STDOUT_FILENO); + pa_close(STDERR_FILENO); + + pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO); + pa_assert_se(open("/dev/null", O_WRONLY) == STDOUT_FILENO); + pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO); +#else + FreeConsole(); +#endif + +} + +char *pa_read_line_from_file(const char *fn) { + FILE *f; + char ln[256] = "", *r; + + if (!(f = pa_fopen_cloexec(fn, "r"))) + return NULL; + + r = fgets(ln, sizeof(ln)-1, f); + fclose(f); + + if (!r) { + errno = EIO; + return NULL; + } + + pa_strip_nl(ln); + return pa_xstrdup(ln); +} + +pa_bool_t pa_running_in_vm(void) { + +#if defined(__i386__) || defined(__x86_64__) + + /* Both CPUID and DMI are x86 specific interfaces... */ + + uint32_t eax = 0x40000000; + union { + uint32_t sig32[3]; + char text[13]; + } sig; + +#ifdef __linux__ + const char *const dmi_vendors[] = { + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + unsigned i; + + for (i = 0; i < PA_ELEMENTSOF(dmi_vendors); i++) { + char *s; + + if ((s = pa_read_line_from_file(dmi_vendors[i]))) { + + if (pa_startswith(s, "QEMU") || + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + pa_startswith(s, "VMware") || + pa_startswith(s, "VMW") || + pa_startswith(s, "Microsoft Corporation") || + pa_startswith(s, "innotek GmbH") || + pa_startswith(s, "Xen")) { + + pa_xfree(s); + return TRUE; + } + + pa_xfree(s); + } + } + +#endif + + /* http://lwn.net/Articles/301888/ */ + pa_zero(sig); + + __asm__ __volatile__ ( + " xor %%ebx, %%ebx \n\t" + " cpuid \n\t" + + : "=a" (eax), "=b" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + if (pa_streq(sig.text, "XenVMMXenVMM") || + pa_streq(sig.text, "KVMKVMKVM") || + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + pa_streq(sig.text, "VMwareVMware") || + /* http://msdn.microsoft.com/en-us/library/bb969719.aspx */ + pa_streq(sig.text, "Microsoft Hv")) + return TRUE; + +#endif + + return FALSE; +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index 6de4b771..31a83bcc 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -28,6 +28,7 @@ #include <stdarg.h> #include <stdio.h> #include <string.h> +#include <sys/socket.h> #ifdef HAVE_SYS_RESOURCE_H #include <sys/resource.h> @@ -195,6 +196,8 @@ int pa_reset_sigs(int except, ...); int pa_reset_sigsv(const int except[]); void pa_set_env(const char *key, const char *value); +void pa_set_env_and_record(const char *key, const char *value); +void pa_unset_env_recorded(void); pa_bool_t pa_in_system_mode(void); @@ -222,6 +225,13 @@ unsigned pa_ncpus(void); char *pa_replace(const char*s, const char*a, const char *b); +/* Escapes p by inserting backslashes in front of backslashes. chars is a + * regular (ie. NULL-terminated) string containing additional characters that + * should be escaped. chars can be NULL. The caller has to free the returned + * string. */ +char *pa_escape(const char *p, const char *chars); + +/* Does regular backslash unescaping. Returns the argument p. */ char *pa_unescape(char *p); char *pa_realpath(const char *path); @@ -241,4 +251,23 @@ char* pa_maybe_prefix_path(const char *path, const char *prefix); /* Returns size of the specified pipe or 4096 on failure */ size_t pa_pipe_buf(int fd); +void pa_reset_personality(void); + +#if defined(__linux__) && !defined(__OPTIMIZE__) +pa_bool_t pa_run_from_build_tree(void); +#endif + +const char *pa_get_temp_dir(void); + +int pa_open_cloexec(const char *fn, int flags, mode_t mode); +int pa_socket_cloexec(int domain, int type, int protocol); +int pa_pipe_cloexec(int pipefd[2]); +int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +FILE* pa_fopen_cloexec(const char *path, const char *mode); + +void pa_nullify_stdfds(void); + +char *pa_read_line_from_file(const char *fn); +pa_bool_t pa_running_in_vm(void); + #endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index f5eb8352..f0726453 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -47,7 +47,7 @@ #include "core.h" -static PA_DEFINE_CHECK_TYPE(pa_core, pa_msgobject); +PA_DEFINE_PUBLIC_CLASS(pa_core, pa_msgobject); static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { pa_core *c = PA_CORE(o); diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index e7abd61b..bfcea4f6 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -52,6 +52,13 @@ typedef enum pa_suspend_cause { #include <pulsecore/sink-input.h> #include <pulsecore/msgobject.h> +typedef enum pa_server_type { + PA_SERVER_TYPE_UNSET, + PA_SERVER_TYPE_USER, + PA_SERVER_TYPE_SYSTEM, + PA_SERVER_TYPE_NONE +} pa_server_type_t; + typedef enum pa_core_state { PA_CORE_STARTUP, PA_CORE_RUNNING, @@ -83,7 +90,6 @@ typedef enum pa_core_hook { PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL, PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED, PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED, - PA_CORE_HOOK_SINK_INPUT_SET_VOLUME, PA_CORE_HOOK_SINK_INPUT_SEND_EVENT, PA_CORE_HOOK_SOURCE_OUTPUT_NEW, PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE, @@ -162,11 +168,13 @@ struct pa_core { pa_resample_method_t resample_method; int realtime_priority; + pa_server_type_t server_type; + /* hooks */ pa_hook hooks[PA_CORE_HOOK_MAX]; }; -PA_DECLARE_CLASS(pa_core); +PA_DECLARE_PUBLIC_CLASS(pa_core); #define PA_CORE(o) pa_core_cast(o) enum { diff --git a/src/pulsecore/cpu-arm.c b/src/pulsecore/cpu-arm.c new file mode 100644 index 00000000..6bb2eadd --- /dev/null +++ b/src/pulsecore/cpu-arm.c @@ -0,0 +1,139 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/log.h> + +#include "cpu-arm.h" + +#if defined (__arm__) && defined (__linux__) + +#define MAX_BUFFER 4096 +static char * +get_cpuinfo_line (char *cpuinfo, const char *tag) { + char *line, *end, *colon; + + if (!(line = strstr (cpuinfo, tag))) + return NULL; + + if (!(end = strchr (line, '\n'))) + return NULL; + + if (!(colon = strchr (line, ':'))) + return NULL; + + if (++colon >= end) + return NULL; + + return pa_xstrndup (colon, end - colon); +} + +static char *get_cpuinfo(void) { + char *cpuinfo; + int n, fd; + + cpuinfo = pa_xmalloc(MAX_BUFFER); + + if ((fd = pa_open_cloexec("/proc/cpuinfo", O_RDONLY, 0)) < 0) { + pa_xfree(cpuinfo); + return NULL; + } + + if ((n = pa_read(fd, cpuinfo, MAX_BUFFER-1)) < 0) { + pa_xfree(cpuinfo); + pa_close(fd); + return NULL; + } + cpuinfo[n] = 0; + pa_close(fd); + + return cpuinfo; +} +#endif /* defined (__arm__) && defined (__linux__) */ + +void pa_cpu_init_arm (void) { +#if defined (__arm__) +#if defined (__linux__) + char *cpuinfo, *line; + int arch; + pa_cpu_arm_flag_t flags = 0; + + /* We need to read the CPU flags from /proc/cpuinfo because there is no user + * space support to get the CPU features. This only works on linux AFAIK. */ + if (!(cpuinfo = get_cpuinfo ())) { + pa_log ("Can't read cpuinfo"); + return; + } + + /* get the CPU architecture */ + if ((line = get_cpuinfo_line (cpuinfo, "CPU architecture"))) { + arch = strtoul (line, NULL, 0); + if (arch >= 6) + flags |= PA_CPU_ARM_V6; + if (arch >= 7) + flags |= PA_CPU_ARM_V7; + + pa_xfree(line); + } + /* get the CPU features */ + if ((line = get_cpuinfo_line (cpuinfo, "Features"))) { + char *state = NULL, *current; + + while ((current = pa_split_spaces (line, &state))) { + if (!strcmp (current, "vfp")) + flags |= PA_CPU_ARM_VFP; + else if (!strcmp (current, "edsp")) + flags |= PA_CPU_ARM_EDSP; + else if (!strcmp (current, "neon")) + flags |= PA_CPU_ARM_NEON; + else if (!strcmp (current, "vfpv3")) + flags |= PA_CPU_ARM_VFPV3; + + pa_xfree(current); + } + } + pa_xfree(cpuinfo); + + pa_log_info ("CPU flags: %s%s%s%s%s%s", + (flags & PA_CPU_ARM_V6) ? "V6 " : "", + (flags & PA_CPU_ARM_V7) ? "V7 " : "", + (flags & PA_CPU_ARM_VFP) ? "VFP " : "", + (flags & PA_CPU_ARM_EDSP) ? "EDSP " : "", + (flags & PA_CPU_ARM_NEON) ? "NEON " : "", + (flags & PA_CPU_ARM_VFPV3) ? "VFPV3 " : ""); +#else /* defined (__linux__) */ + pa_log ("ARM cpu features not yet supported on this OS"); +#endif /* defined (__linux__) */ + + if (flags & PA_CPU_ARM_V6) + pa_volume_func_init_arm (flags); +#endif /* defined (__arm__) */ +} diff --git a/src/daemon/polkit.h b/src/pulsecore/cpu-arm.h index 018f6ef1..a87cb63b 100644 --- a/src/daemon/polkit.h +++ b/src/pulsecore/cpu-arm.h @@ -1,10 +1,11 @@ -#ifndef foopolkithfoo -#define foopolkithfoo +#ifndef foocpuarmhfoo +#define foocpuarmhfoo /*** This file is part of PulseAudio. - Copyright 2007 Lennart Poettering + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -22,6 +23,20 @@ USA. ***/ -int pa_polkit_check(const char *action); +#include <stdint.h> -#endif +typedef enum pa_cpu_arm_flag { + PA_CPU_ARM_V6 = (1 << 0), + PA_CPU_ARM_V7 = (1 << 1), + PA_CPU_ARM_VFP = (1 << 2), + PA_CPU_ARM_EDSP = (1 << 3), + PA_CPU_ARM_NEON = (1 << 4), + PA_CPU_ARM_VFPV3 = (1 << 5) +} pa_cpu_arm_flag_t; + +void pa_cpu_init_arm (void); + +/* some optimized functions */ +void pa_volume_func_init_arm(pa_cpu_arm_flag_t flags); + +#endif /* foocpuarmhfoo */ diff --git a/src/pulsecore/cpu-x86.c b/src/pulsecore/cpu-x86.c new file mode 100644 index 00000000..f194a608 --- /dev/null +++ b/src/pulsecore/cpu-x86.c @@ -0,0 +1,125 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> + +#include <pulsecore/log.h> + +#include "cpu-x86.h" + +#if defined (__i386__) || defined (__amd64__) +static void +get_cpuid (uint32_t op, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d) +{ + __asm__ __volatile__ ( + " push %%"PA_REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %%esi \n\t" + " pop %%"PA_REG_b" \n\t" + + : "=a" (*a), "=S" (*b), "=c" (*c), "=d" (*d) + : "0" (op) + ); +} +#endif + +void pa_cpu_init_x86 (void) { +#if defined (__i386__) || defined (__amd64__) + uint32_t eax, ebx, ecx, edx; + uint32_t level; + pa_cpu_x86_flag_t flags = 0; + + /* get standard level */ + get_cpuid (0x00000000, &level, &ebx, &ecx, &edx); + if (level >= 1) { + get_cpuid (0x00000001, &eax, &ebx, &ecx, &edx); + + if (edx & (1<<23)) + flags |= PA_CPU_X86_MMX; + + if (edx & (1<<25)) + flags |= PA_CPU_X86_SSE; + + if (edx & (1<<26)) + flags |= PA_CPU_X86_SSE2; + + if (ecx & (1<<0)) + flags |= PA_CPU_X86_SSE3; + + if (ecx & (1<<9)) + flags |= PA_CPU_X86_SSSE3; + + if (ecx & (1<<19)) + flags |= PA_CPU_X86_SSE4_1; + + if (ecx & (1<<20)) + flags |= PA_CPU_X86_SSE4_2; + } + + /* get extended level */ + get_cpuid (0x80000000, &level, &ebx, &ecx, &edx); + if (level >= 0x80000001) { + get_cpuid (0x80000001, &eax, &ebx, &ecx, &edx); + + if (edx & (1<<22)) + flags |= PA_CPU_X86_MMXEXT; + + if (edx & (1<<23)) + flags |= PA_CPU_X86_MMX; + + if (edx & (1<<30)) + flags |= PA_CPU_X86_3DNOWEXT; + + if (edx & (1<<31)) + flags |= PA_CPU_X86_3DNOW; + } + + pa_log_info ("CPU flags: %s%s%s%s%s%s%s%s%s%s", + (flags & PA_CPU_X86_MMX) ? "MMX " : "", + (flags & PA_CPU_X86_SSE) ? "SSE " : "", + (flags & PA_CPU_X86_SSE2) ? "SSE2 " : "", + (flags & PA_CPU_X86_SSE3) ? "SSE3 " : "", + (flags & PA_CPU_X86_SSSE3) ? "SSSE3 " : "", + (flags & PA_CPU_X86_SSE4_1) ? "SSE4_1 " : "", + (flags & PA_CPU_X86_SSE4_2) ? "SSE4_2 " : "", + (flags & PA_CPU_X86_MMXEXT) ? "MMXEXT " : "", + (flags & PA_CPU_X86_3DNOW) ? "3DNOW " : "", + (flags & PA_CPU_X86_3DNOWEXT) ? "3DNOWEXT " : ""); + + /* activate various optimisations */ + if (flags & PA_CPU_X86_MMX) { + pa_volume_func_init_mmx (flags); + pa_remap_func_init_mmx (flags); + } + + if (flags & (PA_CPU_X86_SSE | PA_CPU_X86_SSE2)) { + pa_volume_func_init_sse (flags); + pa_remap_func_init_sse (flags); + pa_convert_func_init_sse (flags); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/cpu-x86.h b/src/pulsecore/cpu-x86.h new file mode 100644 index 00000000..f6484c59 --- /dev/null +++ b/src/pulsecore/cpu-x86.h @@ -0,0 +1,71 @@ +#ifndef foocpux86hfoo +#define foocpux86hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <stdint.h> + +typedef enum pa_cpu_x86_flag { + PA_CPU_X86_MMX = (1 << 0), + PA_CPU_X86_MMXEXT = (1 << 1), + PA_CPU_X86_SSE = (1 << 2), + PA_CPU_X86_SSE2 = (1 << 3), + PA_CPU_X86_SSE3 = (1 << 4), + PA_CPU_X86_SSSE3 = (1 << 5), + PA_CPU_X86_SSE4_1 = (1 << 6), + PA_CPU_X86_SSE4_2 = (1 << 7), + PA_CPU_X86_3DNOW = (1 << 8), + PA_CPU_X86_3DNOWEXT = (1 << 9) +} pa_cpu_x86_flag_t; + +void pa_cpu_init_x86 (void); + + +#if defined (__i386__) +typedef int32_t pa_reg_x86; +#define PA_REG_a "eax" +#define PA_REG_b "ebx" +#define PA_REG_c "ecx" +#define PA_REG_d "edx" +#define PA_REG_D "edi" +#define PA_REG_S "esi" +#elif defined (__amd64__) +typedef int64_t pa_reg_x86; +#define PA_REG_a "rax" +#define PA_REG_b "rbx" +#define PA_REG_c "rcx" +#define PA_REG_d "rdx" +#define PA_REG_D "rdi" +#define PA_REG_S "rsi" +#endif + +/* some optimized functions */ +void pa_volume_func_init_mmx(pa_cpu_x86_flag_t flags); +void pa_volume_func_init_sse(pa_cpu_x86_flag_t flags); + +void pa_remap_func_init_mmx(pa_cpu_x86_flag_t flags); +void pa_remap_func_init_sse(pa_cpu_x86_flag_t flags); + +void pa_convert_func_init_sse (pa_cpu_x86_flag_t flags); + +#endif /* foocpux86hfoo */ diff --git a/src/pulsecore/database-simple.c b/src/pulsecore/database-simple.c new file mode 100644 index 00000000..754930db --- /dev/null +++ b/src/pulsecore/database-simple.c @@ -0,0 +1,510 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Nokia Corporation + Contact: Maemo Multimedia <multimedia@maemo.org> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/hashmap.h> + +#include "database.h" + + +typedef struct simple_data { + char *filename; + char *tmp_filename; + pa_hashmap *map; + pa_bool_t read_only; +} simple_data; + +typedef struct entry { + pa_datum key; + pa_datum data; +} entry; + +void pa_datum_free(pa_datum *d) { + pa_assert(d); + + pa_xfree(d->data); + d->data = NULL; + d->size = 0; +} + +static int compare_func(const void *a, const void *b) { + const pa_datum *aa, *bb; + + aa = (const pa_datum*)a; + bb = (const pa_datum*)b; + + if (aa->size != bb->size) + return aa->size > bb->size ? 1 : -1; + + return memcmp(aa->data, bb->data, aa->size); +} + +/* pa_idxset_string_hash_func modified for our use */ +static unsigned hash_func(const void *p) { + const pa_datum *d; + unsigned hash = 0; + const char *c; + unsigned i; + + d = (const pa_datum*)p; + c = d->data; + + for (i = 0; i < d->size; i++) { + hash = 31 * hash + (unsigned) *c; + c++; + } + + return hash; +} + +static entry* new_entry(const pa_datum *key, const pa_datum *data) { + entry *e; + + pa_assert(key); + pa_assert(data); + + e = pa_xnew0(entry, 1); + e->key.data = key->size > 0 ? pa_xmemdup(key->data, key->size) : NULL; + e->key.size = key->size; + e->data.data = data->size > 0 ? pa_xmemdup(data->data, data->size) : NULL; + e->data.size = data->size; + return e; +} + +static void free_entry(entry *e) { + if (e) { + if (e->key.data) + pa_xfree(e->key.data); + if (e->data.data) + pa_xfree(e->data.data); + pa_xfree(e); + } +} + +static int read_uint(FILE *f, uint32_t *res) { + size_t items = 0; + uint8_t values[4]; + uint32_t tmp; + int i; + + items = fread(&values, sizeof(values), sizeof(uint8_t), f); + + if (feof(f)) /* EOF */ + return 0; + + if (ferror(f)) + return -1; + + for (i = 0; i < 4; ++i) { + tmp = values[i]; + *res += (tmp << (i*8)); + } + + return items; +} + +static int read_data(FILE *f, void **data, ssize_t *length) { + size_t items = 0; + uint32_t data_len = 0; + + pa_assert(f); + + *data = NULL; + *length = 0; + + if ((items = read_uint(f, &data_len)) <= 0) + return -1; + + if (data_len > 0) { + *data = pa_xmalloc0(data_len); + items = fread(*data, data_len, 1, f); + + if (feof(f)) /* EOF */ + goto reset; + + if (ferror(f)) + goto reset; + + *length = data_len; + + } else { /* no data? */ + return -1; + } + + return 0; + +reset: + pa_xfree(*data); + *data = NULL; + *length = 0; + return -1; +} + +static int fill_data(simple_data *db, FILE *f) { + pa_datum key; + pa_datum data; + void *d = NULL; + ssize_t l = 0; + pa_bool_t append = FALSE; + enum { FIELD_KEY = 0, FIELD_DATA } field = FIELD_KEY; + + pa_assert(db); + pa_assert(db->map); + + errno = 0; + + key.size = 0; + key.data = NULL; + + while (!read_data(f, &d, &l)) { + + switch (field) { + case FIELD_KEY: + key.data = d; + key.size = l; + field = FIELD_DATA; + break; + case FIELD_DATA: + data.data = d; + data.size = l; + append = TRUE; + break; + } + + if (append) { + entry *e = pa_xnew0(entry, 1); + e->key.data = key.data; + e->key.size = key.size; + e->data.data = data.data; + e->data.size = data.size; + pa_hashmap_put(db->map, &e->key, e); + append = FALSE; + field = FIELD_KEY; + } + } + + if (ferror(f)) { + pa_log_warn("read error. %s", pa_cstrerror(errno)); + pa_database_clear((pa_database*)db); + } + + if (field == FIELD_DATA && d) + pa_xfree(d); + + return pa_hashmap_size(db->map); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { + FILE *f; + char *path; + simple_data *db; + + pa_assert(fn); + + path = pa_sprintf_malloc("%s."CANONICAL_HOST".simple", fn); + errno = 0; + + f = pa_fopen_cloexec(path, "r"); + + if (f || errno == ENOENT) { /* file not found is ok */ + db = pa_xnew0(simple_data, 1); + db->map = pa_hashmap_new(hash_func, compare_func); + db->filename = pa_xstrdup(path); + db->tmp_filename = pa_sprintf_malloc(".%s.tmp", db->filename); + db->read_only = !for_write; + + if (f) { + fill_data(db, f); + fclose(f); + } + } else { + if (errno == 0) + errno = EIO; + db = NULL; + } + + pa_xfree(path); + + return (pa_database*) db; +} + +void pa_database_close(pa_database *database) { + simple_data *db = (simple_data*)database; + pa_assert(db); + + pa_database_sync(database); + pa_database_clear(database); + pa_xfree(db->filename); + pa_xfree(db->tmp_filename); + pa_hashmap_free(db->map, NULL, NULL); + pa_xfree(db); +} + +pa_datum* pa_database_get(pa_database *database, const pa_datum *key, pa_datum* data) { + simple_data *db = (simple_data*)database; + entry *e; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + e = pa_hashmap_get(db->map, key); + + if (!e) + return NULL; + + data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL; + data->size = e->data.size; + + return data; +} + +int pa_database_set(pa_database *database, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite) { + simple_data *db = (simple_data*)database; + entry *e; + int ret = 0; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + if (db->read_only) + return -1; + + e = new_entry(key, data); + + if (pa_hashmap_put(db->map, &e->key, e) < 0) { + /* entry with same key exists in hashmap */ + entry *r; + if (overwrite) { + r = pa_hashmap_remove(db->map, key); + pa_hashmap_put(db->map, &e->key, e); + } else { + /* wont't overwrite, so clean new entry */ + r = e; + ret = -1; + } + + free_entry(r); + } + + return ret; +} + +int pa_database_unset(pa_database *database, const pa_datum *key) { + simple_data *db = (simple_data*)database; + entry *e; + + pa_assert(db); + pa_assert(key); + + e = pa_hashmap_remove(db->map, key); + if (!e) + return -1; + + free_entry(e); + + return 0; +} + +int pa_database_clear(pa_database *database) { + simple_data *db = (simple_data*)database; + entry *e; + + pa_assert(db); + + while ((e = pa_hashmap_steal_first(db->map))) + free_entry(e); + + return 0; +} + +signed pa_database_size(pa_database *database) { + simple_data *db = (simple_data*)database; + pa_assert(db); + + return (signed) pa_hashmap_size(db->map); +} + +pa_datum* pa_database_first(pa_database *database, pa_datum *key, pa_datum *data) { + simple_data *db = (simple_data*)database; + entry *e; + + pa_assert(db); + pa_assert(key); + + e = pa_hashmap_first(db->map); + + if (!e) + return NULL; + + key->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL; + key->size = e->key.size; + + if (data) { + data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL; + data->size = e->data.size; + } + + return key; +} + +pa_datum* pa_database_next(pa_database *database, const pa_datum *key, pa_datum *next, pa_datum *data) { + simple_data *db = (simple_data*)database; + entry *e; + entry *search; + void *state; + pa_bool_t pick_now; + + pa_assert(db); + pa_assert(next); + + if (!key) + return pa_database_first(database, next, data); + + search = pa_hashmap_get(db->map, key); + + state = NULL; + pick_now = FALSE; + + while ((e = pa_hashmap_iterate(db->map, &state, NULL))) { + if (pick_now) + break; + + if (search == e) + pick_now = TRUE; + } + + if (!pick_now || !e) + return NULL; + + next->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL; + next->size = e->key.size; + + if (data) { + data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL; + data->size = e->data.size; + } + + return next; +} + +static int write_uint(FILE *f, const uint32_t num) { + size_t items; + uint8_t values[4]; + int i; + errno = 0; + + for (i = 0; i < 4; i++) + values[i] = (num >> (i*8)) & 0xFF; + + items = fwrite(&values, sizeof(values), sizeof(uint8_t), f); + + if (ferror(f)) + return -1; + + return items; +} + +static int write_data(FILE *f, void *data, const size_t length) { + size_t items; + uint32_t len; + + len = length; + if ((items = write_uint(f, len)) <= 0) + return -1; + + items = fwrite(data, length, 1, f); + + if (ferror(f) || items != 1) + return -1; + + return 0; +} + +static int write_entry(FILE *f, const entry *e) { + pa_assert(f); + pa_assert(e); + + if (write_data(f, e->key.data, e->key.size) < 0) + return -1; + if (write_data(f, e->data.data, e->data.size) < 0) + return -1; + + return 0; +} + +int pa_database_sync(pa_database *database) { + simple_data *db = (simple_data*)database; + FILE *f; + void *state; + entry *e; + + pa_assert(db); + + if (db->read_only) + return 0; + + errno = 0; + + f = pa_fopen_cloexec(db->tmp_filename, "w"); + + if (!f) + goto fail; + + state = NULL; + while((e = pa_hashmap_iterate(db->map, &state, NULL))) { + if (write_entry(f, e) < 0) { + pa_log_warn("error while writing to file. %s", pa_cstrerror(errno)); + goto fail; + } + } + + fclose(f); + f = NULL; + + if (rename(db->tmp_filename, db->filename) < 0) { + pa_log_warn("error while renaming file. %s", pa_cstrerror(errno)); + goto fail; + } + + return 0; + +fail: + if (f) + fclose(f); + return -1; +} diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c index b79d2837..4e782d65 100644 --- a/src/pulsecore/database-tdb.c +++ b/src/pulsecore/database-tdb.c @@ -66,6 +66,39 @@ void pa_datum_free(pa_datum *d) { pa_zero(d); } +static struct tdb_context *tdb_open_cloexec( + const char *name, + int hash_size, + int tdb_flags, + int open_flags, + mode_t mode) { + + /* Mimics pa_open_cloexec() */ + + struct tdb_context *c; + +#ifdef O_NOCTTY + open_flags |= O_NOCTTY; +#endif + +#ifdef O_CLOEXEC + errno = 0; + if ((c = tdb_open(name, hash_size, tdb_flags, open_flags | O_CLOEXEC, mode))) + goto finish; + + if (errno != EINVAL) + return NULL; +#endif + + errno = 0; + if (!(c = tdb_open(name, hash_size, tdb_flags, open_flags, mode))) + return NULL; + +finish: + pa_make_fd_cloexec(tdb_fd(c)); + return c; +} + pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { struct tdb_context *c; char *path; @@ -73,15 +106,7 @@ pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { pa_assert(fn); path = pa_sprintf_malloc("%s.tdb", fn); - errno = 0; - c = tdb_open(path, 0, TDB_NOSYNC|TDB_NOLOCK, - (for_write ? O_RDWR|O_CREAT : O_RDONLY)|O_NOCTTY -#ifdef O_CLOEXEC - |O_CLOEXEC -#endif - , 0644); - - if (c) + if ((c = tdb_open_cloexec(path, 0, TDB_NOSYNC|TDB_NOLOCK, (for_write ? O_RDWR|O_CREAT : O_RDONLY), 0644))) pa_log_debug("Opened TDB database '%s'", path); pa_xfree(path); diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c index 4e6148f0..e3700ea5 100644 --- a/src/pulsecore/dbus-util.c +++ b/src/pulsecore/dbus-util.c @@ -28,6 +28,7 @@ #include <pulse/rtclock.h> #include <pulse/timeval.h> +#include <pulse/utf8.h> #include <pulse/xmalloc.h> #include <pulsecore/core-rtclock.h> @@ -290,6 +291,31 @@ pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, pa_bool return pconn; } +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing( + pa_mainloop_api *m, + pa_bool_t use_rtclock, + DBusConnection *conn) { + pa_dbus_wrap_connection *pconn; + + pa_assert(m); + pa_assert(conn); + + pconn = pa_xnew(pa_dbus_wrap_connection, 1); + pconn->mainloop = m; + pconn->connection = dbus_connection_ref(conn); + pconn->use_rtclock = use_rtclock; + + dbus_connection_set_exit_on_disconnect(conn, FALSE); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL); + dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); + + pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn); + + return pconn; +} + void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) { pa_assert(c); @@ -422,3 +448,329 @@ void pa_dbus_free_pending_list(pa_dbus_pending **p) { pa_dbus_pending_free(i); } } + +void pa_dbus_send_error(DBusConnection *c, DBusMessage *in_reply_to, const char *name, const char *format, ...) { + va_list ap; + char *message; + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(name); + pa_assert(format); + + va_start(ap, format); + message = pa_vsprintf_malloc(format, ap); + va_end(ap); + pa_assert_se((reply = dbus_message_new_error(in_reply_to, name, message))); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(message); +} + +void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to) { + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) { + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + pa_assert_se(dbus_message_append_args(reply, type, data, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +static const char *signature_from_basic_type(int type) { + switch (type) { + case DBUS_TYPE_BOOLEAN: return DBUS_TYPE_BOOLEAN_AS_STRING; + case DBUS_TYPE_BYTE: return DBUS_TYPE_BYTE_AS_STRING; + case DBUS_TYPE_INT16: return DBUS_TYPE_INT16_AS_STRING; + case DBUS_TYPE_UINT16: return DBUS_TYPE_UINT16_AS_STRING; + case DBUS_TYPE_INT32: return DBUS_TYPE_INT32_AS_STRING; + case DBUS_TYPE_UINT32: return DBUS_TYPE_UINT32_AS_STRING; + case DBUS_TYPE_INT64: return DBUS_TYPE_INT64_AS_STRING; + case DBUS_TYPE_UINT64: return DBUS_TYPE_UINT64_AS_STRING; + case DBUS_TYPE_DOUBLE: return DBUS_TYPE_DOUBLE_AS_STRING; + case DBUS_TYPE_STRING: return DBUS_TYPE_STRING_AS_STRING; + case DBUS_TYPE_OBJECT_PATH: return DBUS_TYPE_OBJECT_PATH_AS_STRING; + case DBUS_TYPE_SIGNATURE: return DBUS_TYPE_SIGNATURE_AS_STRING; + default: pa_assert_not_reached(); + } +} + +void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, + DBUS_TYPE_VARIANT, + signature_from_basic_type(type), + &variant_iter)); + pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data)); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &variant_iter)); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +/* Note: returns sizeof(char*) for strings, object paths and signatures. */ +static unsigned basic_type_size(int type) { + switch (type) { + case DBUS_TYPE_BOOLEAN: return sizeof(dbus_bool_t); + case DBUS_TYPE_BYTE: return 1; + case DBUS_TYPE_INT16: return sizeof(dbus_int16_t); + case DBUS_TYPE_UINT16: return sizeof(dbus_uint16_t); + case DBUS_TYPE_INT32: return sizeof(dbus_int32_t); + case DBUS_TYPE_UINT32: return sizeof(dbus_uint32_t); + case DBUS_TYPE_INT64: return sizeof(dbus_int64_t); + case DBUS_TYPE_UINT64: return sizeof(dbus_uint64_t); + case DBUS_TYPE_DOUBLE: return sizeof(double); + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: return sizeof(char*); + default: pa_assert_not_reached(); + } +} + +void pa_dbus_send_basic_array_variant_reply( + DBusConnection *c, + DBusMessage *in_reply_to, + int item_type, + void *array, + unsigned n) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_dbus_append_basic_array_variant(&msg_iter, item_type, array, n); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(proplist); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_dbus_append_proplist_variant(&msg_iter, proplist); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n) { + DBusMessageIter array_iter; + unsigned i; + unsigned item_size; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + item_size = basic_type_size(item_type); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, signature_from_basic_type(item_type), &array_iter)); + + for (i = 0; i < n; ++i) + pa_assert_se(dbus_message_iter_append_basic(&array_iter, item_type, &((uint8_t*) array)[i * item_size])); + + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); +}; + +void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, signature_from_basic_type(type), &variant_iter)); + pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n) { + DBusMessageIter variant_iter; + char *array_signature; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + array_signature = pa_sprintf_malloc("a%c", *signature_from_basic_type(item_type)); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_signature, &variant_iter)); + pa_dbus_append_basic_array(&variant_iter, item_type, array, n); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); + + pa_xfree(array_signature); +} + +void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_basic_variant(&dict_entry_iter, type, data); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +void pa_dbus_append_basic_array_variant_dict_entry( + DBusMessageIter *dict_iter, + const char *key, + int item_type, + const void *array, + unsigned n) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_basic_array_variant(&dict_entry_iter, item_type, array, n); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist) { + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter array_iter; + void *state = NULL; + const char *key; + + pa_assert(iter); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{say}", &dict_iter)); + + while ((key = pa_proplist_iterate(proplist, &state))) { + const void *value = NULL; + size_t nbytes; + + pa_assert_se(pa_proplist_get(proplist, key, &value, &nbytes) >= 0); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + + pa_assert_se(dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_ARRAY, "y", &array_iter)); + pa_assert_se(dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, &value, nbytes)); + pa_assert_se(dbus_message_iter_close_container(&dict_entry_iter, &array_iter)); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(iter, &dict_iter)); +} + +void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{say}", &variant_iter)); + pa_dbus_append_proplist(&variant_iter, proplist); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_proplist_variant(&dict_entry_iter, proplist); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter) { + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + pa_proplist *proplist = NULL; + const char *key = NULL; + const uint8_t *value = NULL; + int value_length = 0; + + pa_assert(c); + pa_assert(msg); + pa_assert(iter); + pa_assert(pa_streq(dbus_message_iter_get_signature(iter), "a{say}")); + + proplist = pa_proplist_new(); + + dbus_message_iter_recurse(iter, &dict_iter); + + while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + + dbus_message_iter_get_basic(&dict_entry_iter, &key); + dbus_message_iter_next(&dict_entry_iter); + + if (strlen(key) <= 0 || !pa_ascii_valid(key)) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Invalid property list key: '%s'.", key); + goto fail; + } + + dbus_message_iter_get_fixed_array(&dict_entry_iter, &value, &value_length); + + pa_assert(value_length >= 0); + + pa_assert_se(pa_proplist_set(proplist, key, value, value_length) >= 0); + + dbus_message_iter_next(&dict_iter); + } + + dbus_message_iter_next(iter); + + return proplist; + +fail: + if (proplist) + pa_proplist_free(proplist); + + return NULL; +} diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h index 9ff298d8..f35e66cb 100644 --- a/src/pulsecore/dbus-util.h +++ b/src/pulsecore/dbus-util.h @@ -26,11 +26,16 @@ #include <pulsecore/llist.h> #include <pulse/mainloop-api.h> +#include <pulse/proplist.h> /* A wrap connection is not shared or refcounted, it is available in client side */ typedef struct pa_dbus_wrap_connection pa_dbus_wrap_connection; pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *mainloop, pa_bool_t use_rtclock, DBusBusType type, DBusError *error); +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing( + pa_mainloop_api *mainloop, + pa_bool_t use_rtclock, + DBusConnection *conn); void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn); DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn); @@ -60,4 +65,42 @@ void pa_dbus_sync_pending_list(pa_dbus_pending **p); /* Free up a list of pa_dbus_pending_call objects */ void pa_dbus_free_pending_list(pa_dbus_pending **p); +/* Sends an error message as the reply to the given message. */ +void pa_dbus_send_error( + DBusConnection *c, + DBusMessage *in_reply_to, + const char *name, + const char *format, ...) PA_GCC_PRINTF_ATTR(4, 5); + +void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to); +void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data); +void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data); +void pa_dbus_send_basic_array_variant_reply( + DBusConnection *c, + DBusMessage *in_reply_to, + int item_type, + void *array, + unsigned n); +void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist); + +void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n); +void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n); +void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data); +void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data); +void pa_dbus_append_basic_array_variant_dict_entry( + DBusMessageIter *dict_iter, + const char *key, + int item_type, + const void *array, + unsigned n); +void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist); +void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist); +void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist); + +/* Returns a new proplist that the caller has to free. If the proplist contains + * invalid keys, an error reply is sent and NULL is returned. The iterator must + * point to "a{say}" element. This function calls dbus_message_iter_next(iter) + * before returning. */ +pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter); + #endif diff --git a/src/pulsecore/fdsem.c b/src/pulsecore/fdsem.c index 380f34f5..ea14e8a7 100644 --- a/src/pulsecore/fdsem.c +++ b/src/pulsecore/fdsem.c @@ -62,19 +62,15 @@ pa_fdsem *pa_fdsem_new(void) { f = pa_xmalloc(PA_ALIGN(sizeof(pa_fdsem)) + PA_ALIGN(sizeof(pa_fdsem_data))); #ifdef HAVE_SYS_EVENTFD_H - if ((f->efd = eventfd(0, 0)) >= 0) { - pa_make_fd_cloexec(f->efd); + if ((f->efd = eventfd(0, EFD_CLOEXEC)) >= 0) f->fds[0] = f->fds[1] = -1; - } else + else #endif { - if (pipe(f->fds) < 0) { + if (pa_pipe_cloexec(f->fds) < 0) { pa_xfree(f); return NULL; } - - pa_make_fd_cloexec(f->fds[0]); - pa_make_fd_cloexec(f->fds[1]); } f->data = (pa_fdsem_data*) ((uint8_t*) f + PA_ALIGN(sizeof(pa_fdsem))); @@ -114,12 +110,11 @@ pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data, int* event_fd) { f = pa_xnew(pa_fdsem, 1); - if ((f->efd = eventfd(0, 0)) < 0) { + if ((f->efd = eventfd(0, EFD_CLOEXEC)) < 0) { pa_xfree(f); return NULL; } - pa_make_fd_cloexec(f->efd); f->fds[0] = f->fds[1] = -1; f->data = data; diff --git a/src/pulsecore/flist.c b/src/pulsecore/flist.c index 6fb944f9..7e5ee244 100644 --- a/src/pulsecore/flist.c +++ b/src/pulsecore/flist.c @@ -130,15 +130,22 @@ void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb) { } int pa_flist_push(pa_flist*l, void *p) { - unsigned idx, n, len; + unsigned idx, n; pa_atomic_ptr_t*cells; +#ifdef PROFILE + unsigned len; +#endif pa_assert(l); pa_assert(p); cells = PA_FLIST_CELLS(l); - n = len = l->size + N_EXTRA_SCAN - (unsigned) pa_atomic_load(&l->length); + n = l->size + N_EXTRA_SCAN - (unsigned) pa_atomic_load(&l->length); + +#ifdef PROFILE + len = n; +#endif _Y; idx = reduce(l, (unsigned) pa_atomic_load(&l->write_idx)); @@ -171,14 +178,21 @@ int pa_flist_push(pa_flist*l, void *p) { } void* pa_flist_pop(pa_flist*l) { - unsigned idx, len, n; + unsigned idx, n; pa_atomic_ptr_t *cells; +#ifdef PROFILE + unsigned len; +#endif pa_assert(l); cells = PA_FLIST_CELLS(l); - n = len = (unsigned) pa_atomic_load(&l->length) + N_EXTRA_SCAN; + n = (unsigned) pa_atomic_load(&l->length) + N_EXTRA_SCAN; + +#ifdef PROFILE + len = n; +#endif _Y; idx = reduce(l, (unsigned) pa_atomic_load(&l->read_idx)); diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c index a00116d1..d9b9917d 100644 --- a/src/pulsecore/hook-list.c +++ b/src/pulsecore/hook-list.c @@ -97,7 +97,7 @@ pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data) { hook->n_firing ++; - for (slot = hook->slots; slot; slot = slot->next) { + PA_LLIST_FOREACH(slot, hook->slots) { if (slot->dead) continue; diff --git a/src/pulsecore/llist.h b/src/pulsecore/llist.h index 58b51c68..27f174a9 100644 --- a/src/pulsecore/llist.h +++ b/src/pulsecore/llist.h @@ -107,4 +107,7 @@ #define PA_LLIST_FOREACH(i,head) \ for (i = (head); i; i = i->next) +#define PA_LLIST_FOREACH_SAFE(i,n,head) \ + for (i = (head); i && ((n = i->next), 1); i = n) + #endif diff --git a/src/pulsecore/lock-autospawn.c b/src/pulsecore/lock-autospawn.c index c0df7938..65e35634 100644 --- a/src/pulsecore/lock-autospawn.c +++ b/src/pulsecore/lock-autospawn.c @@ -87,12 +87,9 @@ static int ref(void) { pa_assert(pipe_fd[0] < 0); pa_assert(pipe_fd[1] < 0); - if (pipe(pipe_fd) < 0) + if (pa_pipe_cloexec(pipe_fd) < 0) return -1; - pa_make_fd_cloexec(pipe_fd[0]); - pa_make_fd_cloexec(pipe_fd[1]); - pa_make_fd_nonblock(pipe_fd[1]); pa_make_fd_nonblock(pipe_fd[0]); diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h index cf662510..9a5a2670 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -59,39 +59,42 @@ #endif /* Rounds down */ -static inline void* pa_align_ptr(const void *p) { - return (void*) (((size_t) p) & ~(sizeof(void*)-1)); +static inline void* PA_ALIGN_PTR(const void *p) { + return (void*) (((size_t) p) & ~(sizeof(void*) - 1)); } -#define PA_ALIGN_PTR(x) (pa_align_ptr(x)) /* Rounds up */ -static inline size_t pa_align(size_t l) { - return (((l + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)); +static inline size_t PA_ALIGN(size_t l) { + return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1)); } -#define PA_ALIGN(x) (pa_align(x)) /* Rounds down */ -static inline void* pa_page_align_ptr(const void *p) { - return (void*) (((size_t) p) & ~(PA_PAGE_SIZE-1)); +static inline void* PA_PAGE_ALIGN_PTR(const void *p) { + return (void*) (((size_t) p) & ~(PA_PAGE_SIZE - 1)); } -#define PA_PAGE_ALIGN_PTR(x) (pa_page_align_ptr(x)) /* Rounds up */ -static inline size_t pa_page_align(size_t l) { - return ((l + PA_PAGE_SIZE - 1) / PA_PAGE_SIZE) * PA_PAGE_SIZE; +static inline size_t PA_PAGE_ALIGN(size_t l) { + return (l + PA_PAGE_SIZE - 1) & ~(PA_PAGE_SIZE - 1); } -#define PA_PAGE_ALIGN(x) (pa_page_align(x)) #define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) -/* The users of PA_MIN and PA_MAX should be aware that these macros on - * non-GCC executed code with side effects twice. It is thus - * considered misuse to use code with side effects as arguments to MIN - * and MAX. */ +#if defined(__GNUC__) + #define PA_DECLARE_ALIGNED(n,t,v) t v __attribute__ ((aligned (n))) +#else + #define PA_DECLARE_ALIGNED(n,t,v) t v +#endif + +/* The users of PA_MIN and PA_MAX, PA_CLAMP, PA_ROUND_UP should be + * aware that these macros on non-GCC executed code with side effects + * twice. It is thus considered misuse to use code with side effects + * as arguments to MIN and MAX. */ #ifdef __GNUC__ #define PA_MAX(a,b) \ - __extension__ ({ typeof(a) _a = (a); \ + __extension__ ({ \ + typeof(a) _a = (a); \ typeof(b) _b = (b); \ _a > _b ? _a : _b; \ }) @@ -101,7 +104,8 @@ static inline size_t pa_page_align(size_t l) { #ifdef __GNUC__ #define PA_MIN(a,b) \ - __extension__ ({ typeof(a) _a = (a); \ + __extension__ ({ \ + typeof(a) _a = (a); \ typeof(b) _b = (b); \ _a < _b ? _a : _b; \ }) @@ -111,7 +115,8 @@ static inline size_t pa_page_align(size_t l) { #ifdef __GNUC__ #define PA_CLAMP(x, low, high) \ - __extension__ ({ typeof(x) _x = (x); \ + __extension__ ({ \ + typeof(x) _x = (x); \ typeof(low) _low = (low); \ typeof(high) _high = (high); \ ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ @@ -122,7 +127,8 @@ static inline size_t pa_page_align(size_t l) { #ifdef __GNUC__ #define PA_CLAMP_UNLIKELY(x, low, high) \ - __extension__ ({ typeof(x) _x = (x); \ + __extension__ ({ \ + typeof(x) _x = (x); \ typeof(low) _low = (low); \ typeof(high) _high = (high); \ (PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \ @@ -135,6 +141,39 @@ static inline size_t pa_page_align(size_t l) { * make sense: we cannot know if it is more likely that the values is * lower or greater than the boundaries.*/ +#ifdef __GNUC__ +#define PA_ROUND_UP(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + ((_a + _b - 1) / _b) * _b; \ + }) +#else +#define PA_ROUND_UP(a, b) ((((a) + (b) - 1) / (b)) * (b)) +#endif + +#ifdef __GNUC__ +#define PA_ROUND_DOWN(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + (_a / _b) * _b; \ + }) +#else +#define PA_ROUND_DOWN(a, b) (((a) / (b)) * (b)) +#endif + +#ifdef __GNUC__ +#define PA_CLIP_SUB(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a - _b : 0; \ + }) +#else +#define PA_CLIP_SUB(a, b) ((a) > (b) ? (a) - (b) : 0) +#endif + /* This type is not intended to be used in exported APIs! Use classic "int" there! */ #ifdef HAVE_STD_BOOL typedef _Bool pa_bool_t; @@ -265,6 +304,18 @@ typedef int pa_bool_t; #define pa_memzero(x,l) (memset((x), 0, (l))) #define pa_zero(x) (pa_memzero(&(x), sizeof(x))) +#define PA_INT_TYPE_SIGNED(type) (!!((type) 0 > (type) -1)) + +#define PA_INT_TYPE_MAX(type) \ + ((type) (PA_INT_TYPE_SIGNED(type) \ + ? ~(~(type) 0 << (8*sizeof(type)-1)) \ + : (type) -1)) + +#define PA_INT_TYPE_MIN(type) \ + ((type) (PA_INT_TYPE_SIGNED(type) \ + ? (~(type) 0 << (8*sizeof(type)-1)) \ + : (type) 0)) + /* We include this at the very last place */ #include <pulsecore/log.h> diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c index 3bc10de5..f38b17c6 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -258,7 +258,8 @@ static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) { slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) idx)); if (!slot) { - pa_log_debug("Pool full"); + if (pa_log_ratelimit()) + pa_log_debug("Pool full"); pa_atomic_inc(&p->stat.n_pool_full); return NULL; } @@ -302,10 +303,17 @@ static struct mempool_slot* mempool_slot_by_ptr(pa_mempool *p, void *ptr) { pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) { pa_memblock *b = NULL; struct mempool_slot *slot; + static int mempool_disable = 0; pa_assert(p); pa_assert(length); + if (mempool_disable == 0) + mempool_disable = getenv("PULSE_MEMPOOL_DISABLE") ? 1 : -1; + + if (mempool_disable > 0) + return NULL; + /* If -1 is passed as length we choose the size for the caller: we * take the largest size that fits in one of our slots. */ @@ -360,6 +368,7 @@ pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, pa_boo if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) b = pa_xnew(pa_memblock, 1); + PA_REFCNT_INIT(b); b->pool = p; b->type = PA_MEMBLOCK_FIXED; @@ -386,6 +395,7 @@ pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, pa_free if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) b = pa_xnew(pa_memblock, 1); + PA_REFCNT_INIT(b); b->pool = p; b->type = PA_MEMBLOCK_USER; @@ -498,13 +508,19 @@ static void memblock_free(pa_memblock *b) { /* Fall through */ case PA_MEMBLOCK_FIXED: - case PA_MEMBLOCK_APPENDED : if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) pa_xfree(b); break; - case PA_MEMBLOCK_IMPORTED : { + case PA_MEMBLOCK_APPENDED: + + /* We could attached it unused_memblocks, but that would + * probably waste some considerable memory */ + pa_xfree(b); + break; + + case PA_MEMBLOCK_IMPORTED: { pa_memimport_segment *segment; pa_memimport *import; @@ -538,8 +554,7 @@ static void memblock_free(pa_memblock *b) { struct mempool_slot *slot; pa_bool_t call_free; - slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data)); - pa_assert(slot); + pa_assert_se(slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data))); call_free = b->type == PA_MEMBLOCK_POOL_EXTERNAL; @@ -693,7 +708,7 @@ static void memblock_replace_import(pa_memblock *b) { pa_mempool* pa_mempool_new(pa_bool_t shared, size_t size) { pa_mempool *p; - char t1[64], t2[64]; + char t1[PA_BYTES_SNPRINT_MAX], t2[PA_BYTES_SNPRINT_MAX]; p = pa_xnew(pa_mempool, 1); @@ -890,7 +905,7 @@ static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id) { if (pa_hashmap_size(i->segments) >= PA_MEMIMPORT_SEGMENTS_MAX) return NULL; - seg = pa_xnew(pa_memimport_segment, 1); + seg = pa_xnew0(pa_memimport_segment, 1); if (pa_shm_attach_ro(&seg->memory, shm_id) < 0) { pa_xfree(seg); @@ -898,10 +913,9 @@ static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id) { } seg->import = i; - seg->n_blocks = 0; seg->trap = pa_memtrap_add(seg->memory.ptr, seg->memory.size); - pa_hashmap_put(i->segments, PA_UINT32_TO_PTR(shm_id), seg); + pa_hashmap_put(i->segments, PA_UINT32_TO_PTR(seg->memory.id), seg); return seg; } diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index 77f9efc9..32758be3 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -692,6 +692,12 @@ size_t pa_memblockq_get_minreq(pa_memblockq *bq) { return bq->minreq; } +size_t pa_memblockq_get_maxrewind(pa_memblockq *bq) { + pa_assert(bq); + + return bq->maxrewind; +} + int64_t pa_memblockq_get_read_index(pa_memblockq *bq) { pa_assert(bq); diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 146d261b..587c364b 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -141,6 +141,9 @@ size_t pa_memblockq_get_prebuf(pa_memblockq *bq); /* Returns the minimal request value */ size_t pa_memblockq_get_minreq(pa_memblockq *bq); +/* Returns the maximal rewind value */ +size_t pa_memblockq_get_maxrewind(pa_memblockq *bq); + /* Return the base unit in bytes */ size_t pa_memblockq_get_base(pa_memblockq *bq); diff --git a/src/pulsecore/memtrap.c b/src/pulsecore/memtrap.c index c647e507..4fc18210 100644 --- a/src/pulsecore/memtrap.c +++ b/src/pulsecore/memtrap.c @@ -107,7 +107,10 @@ static void memtrap_link(pa_memtrap *m, unsigned j) { pa_assert(m); m->prev[j] = NULL; - m->next[j] = memtraps[j]; + + if ((m->next[j] = memtraps[j])) + m->next[j]->prev[j] = m; + memtraps[j] = m; } @@ -200,13 +203,13 @@ pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) { goto unlock; memtrap_unlink(m, j); - j = pa_aupdate_write_swap(aupdate); + pa_aupdate_write_swap(aupdate); m->start = (void*) start; m->size = size; pa_atomic_store(&m->bad, 0); - j = pa_aupdate_write_swap(aupdate); + pa_assert_se(pa_aupdate_write_swap(aupdate) == j); memtrap_link(m, j); unlock: diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c index c7d734d9..e78cdb9a 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -415,3 +415,13 @@ int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa return 0; } + +const char *pa_modargs_iterate(pa_modargs *ma, void **state) { + pa_hashmap *map = (pa_hashmap*) ma; + struct entry *e; + + if (!(e = pa_hashmap_iterate(map, state, NULL))) + return NULL; + + return e->key; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index b3125b10..1ed66e9a 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -60,4 +60,13 @@ int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *s int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m); +/* Iterate through the module argument list. The user should allocate a + * state variable of type void* and initialize it with NULL. A pointer + * to this variable should then be passed to pa_modargs_iterate() + * which should be called in a loop until it returns NULL which + * signifies EOL. On each invication this function will return the + * key string for the next entry. The keys in the argument list do not + * have any particular order. */ +const char *pa_modargs_iterate(pa_modargs *ma, void **state); + #endif diff --git a/src/pulsecore/msgobject.c b/src/pulsecore/msgobject.c index 6a2a612d..075a28c5 100644 --- a/src/pulsecore/msgobject.c +++ b/src/pulsecore/msgobject.c @@ -26,22 +26,22 @@ #include "msgobject.h" -PA_DEFINE_CHECK_TYPE(pa_msgobject, pa_object); +PA_DEFINE_PUBLIC_CLASS(pa_msgobject, pa_object); -pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) { +pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_id, pa_bool_t (*check_type)(const char *type_name)) { pa_msgobject *o; pa_assert(size > sizeof(pa_msgobject)); - pa_assert(type_name); + pa_assert(type_id); if (!check_type) check_type = pa_msgobject_check_type; - pa_assert(check_type(type_name)); - pa_assert(check_type("pa_object")); - pa_assert(check_type("pa_msgobject")); + pa_assert(check_type(type_id)); + pa_assert(check_type(pa_object_type_id)); + pa_assert(check_type(pa_msgobject_type_id)); - o = PA_MSGOBJECT(pa_object_new_internal(size, type_name, check_type)); + o = PA_MSGOBJECT(pa_object_new_internal(size, type_id, check_type)); o->process_msg = NULL; return o; } diff --git a/src/pulsecore/msgobject.h b/src/pulsecore/msgobject.h index a35a23b5..ee0ec1ed 100644 --- a/src/pulsecore/msgobject.h +++ b/src/pulsecore/msgobject.h @@ -38,15 +38,13 @@ struct pa_msgobject { int (*process_msg)(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); }; -pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)); +pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_id, pa_bool_t (*check_type)(const char *type_name)); -int pa_msgobject_check_type(const char *type); - -#define pa_msgobject_new(type) ((type*) pa_msgobject_new_internal(sizeof(type), #type, type##_check_type)) +#define pa_msgobject_new(type) ((type*) pa_msgobject_new_internal(sizeof(type), type##_type_id, type##_check_type)) #define pa_msgobject_free ((void (*) (pa_msgobject* o)) pa_object_free) #define PA_MSGOBJECT(o) pa_msgobject_cast(o) -PA_DECLARE_CLASS(pa_msgobject); +PA_DECLARE_PUBLIC_CLASS(pa_msgobject); #endif diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c index 9df2f583..d9821876 100644 --- a/src/pulsecore/namereg.c +++ b/src/pulsecore/namereg.c @@ -57,6 +57,8 @@ static pa_bool_t is_valid_char(char c) { pa_bool_t pa_namereg_is_valid_name(const char *name) { const char *c; + pa_assert(name); + if (*name == 0) return FALSE; @@ -70,6 +72,25 @@ pa_bool_t pa_namereg_is_valid_name(const char *name) { return TRUE; } +pa_bool_t pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type) { + + pa_assert(name); + + if (pa_namereg_is_valid_name(name)) + return TRUE; + + if (type == PA_NAMEREG_SINK && + pa_streq(name, "@DEFAULT_SINK@")) + return TRUE; + + if (type == PA_NAMEREG_SOURCE && + (pa_streq(name, "@DEFAULT_SOURCE@") || + pa_streq(name, "@DEFAULT_MONITOR@"))) + return TRUE; + + return FALSE; +} + char* pa_namereg_make_valid_name(const char *name) { const char *a; char *b, *n; @@ -77,7 +98,7 @@ char* pa_namereg_make_valid_name(const char *name) { if (*name == 0) return NULL; - n = pa_xmalloc(strlen(name)+1); + n = pa_xnew(char, strlen(name)+1); for (a = name, b = n; *a && (a-name < PA_NAME_MAX); a++, b++) *b = (char) (is_valid_char(*a) ? *a : '_'); @@ -149,6 +170,17 @@ const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t pa_assert_se(pa_hashmap_put(c->namereg, e->name, e) >= 0); + /* If a sink or source is registered and there was none registered + * before we inform the clients which then can ask for the default + * sink/source which is then assigned. We don't adjust the default + * sink/source here right away to give the module the chance to + * register more sinks/sources before we choose a new default + * sink/source. */ + + if ((!c->default_sink && type == PA_NAMEREG_SINK) || + (!c->default_source && type == PA_NAMEREG_SOURCE)) + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); + return e->name; } @@ -191,7 +223,6 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type) { if ((s = pa_namereg_get(c, NULL, PA_NAMEREG_SINK))) return s->monitor_source; - } if (!name) @@ -223,6 +254,9 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type) { pa_sink* pa_namereg_set_default_sink(pa_core*c, pa_sink *s) { pa_assert(c); + if (s && !PA_SINK_IS_LINKED(pa_sink_get_state(s))) + return NULL; + if (c->default_sink != s) { c->default_sink = s; pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); @@ -234,6 +268,9 @@ pa_sink* pa_namereg_set_default_sink(pa_core*c, pa_sink *s) { pa_source* pa_namereg_set_default_source(pa_core*c, pa_source *s) { pa_assert(c); + if (s && !PA_SOURCE_IS_LINKED(pa_source_get_state(s))) + return NULL; + if (c->default_source != s) { c->default_source = s; pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); @@ -243,34 +280,57 @@ pa_source* pa_namereg_set_default_source(pa_core*c, pa_source *s) { } pa_sink *pa_namereg_get_default_sink(pa_core *c) { - pa_sink *s; + pa_sink *s, *best = NULL; + uint32_t idx; pa_assert(c); - if (c->default_sink) + if (c->default_sink && PA_SINK_IS_LINKED(pa_sink_get_state(c->default_sink))) return c->default_sink; - if ((s = pa_idxset_first(c->sinks, NULL))) - return pa_namereg_set_default_sink(c, s); + PA_IDXSET_FOREACH(s, c->sinks, idx) + if (PA_SINK_IS_LINKED(pa_sink_get_state(s))) + if (!best || s->priority > best->priority) + best = s; + + if (best) + return pa_namereg_set_default_sink(c, best); return NULL; } pa_source *pa_namereg_get_default_source(pa_core *c) { - pa_source *s; + pa_source *s, *best = NULL; uint32_t idx; pa_assert(c); - if (c->default_source) + if (c->default_source && PA_SOURCE_IS_LINKED(pa_source_get_state(c->default_source))) return c->default_source; - for (s = PA_SOURCE(pa_idxset_first(c->sources, &idx)); s; s = PA_SOURCE(pa_idxset_next(c->sources, &idx))) - if (!s->monitor_of) - return pa_namereg_set_default_source(c, s); - - if ((s = pa_idxset_first(c->sources, NULL))) - return pa_namereg_set_default_source(c, s); + /* First, try to find one that isn't a monitor */ + PA_IDXSET_FOREACH(s, c->sources, idx) + if (!s->monitor_of && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) + if (!best || + s->priority > best->priority) + best = s; + + if (best) + return pa_namereg_set_default_source(c, best); + + /* Then, fallback to a monitor */ + PA_IDXSET_FOREACH(s, c->sources, idx) + if (PA_SOURCE_IS_LINKED(pa_source_get_state(s))) + if (!best || + s->priority > best->priority || + (s->priority == best->priority && + s->monitor_of && + best->monitor_of && + s->monitor_of->priority > best->monitor_of->priority)) + best = s; + + if (best) + return pa_namereg_set_default_source(c, best); return NULL; } diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h index 38fae6f5..b5a976d7 100644 --- a/src/pulsecore/namereg.h +++ b/src/pulsecore/namereg.h @@ -45,6 +45,7 @@ pa_sink *pa_namereg_get_default_sink(pa_core *c); pa_source *pa_namereg_get_default_source(pa_core *c); pa_bool_t pa_namereg_is_valid_name(const char *name); +pa_bool_t pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type); char* pa_namereg_make_valid_name(const char *name); #endif diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c index f3ead9c5..099d50d9 100644 --- a/src/pulsecore/object.c +++ b/src/pulsecore/object.c @@ -28,21 +28,23 @@ #include "object.h" -pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) { +const char pa_object_type_id[] = "pa_object"; + +pa_object *pa_object_new_internal(size_t size, const char *type_id, pa_bool_t (*check_type)(const char *type_id)) { pa_object *o; pa_assert(size > sizeof(pa_object)); - pa_assert(type_name); + pa_assert(type_id); if (!check_type) check_type = pa_object_check_type; - pa_assert(check_type(type_name)); - pa_assert(check_type("pa_object")); + pa_assert(check_type(type_id)); + pa_assert(check_type(pa_object_type_id)); o = pa_xmalloc(size); PA_REFCNT_INIT(o); - o->type_name = type_name; + o->type_id = type_id; o->free = pa_object_free; o->check_type = check_type; @@ -65,8 +67,8 @@ void pa_object_unref(pa_object *o) { } } -int pa_object_check_type(const char *type_name) { - pa_assert(type_name); +pa_bool_t pa_object_check_type(const char *type_id) { + pa_assert(type_id); - return pa_streq(type_name, "pa_object"); + return type_id == pa_object_type_id; } diff --git a/src/pulsecore/object.h b/src/pulsecore/object.h index 43e79327..4c120cd5 100644 --- a/src/pulsecore/object.h +++ b/src/pulsecore/object.h @@ -34,21 +34,23 @@ typedef struct pa_object pa_object; struct pa_object { PA_REFCNT_DECLARE; - const char *type_name; + const char *type_id; void (*free)(pa_object *o); - int (*check_type)(const char *type_name); + pa_bool_t (*check_type)(const char *type_name); }; -pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)); -#define pa_object_new(type) ((type*) pa_object_new_internal(sizeof(type), #type, type##_check_type) +pa_object *pa_object_new_internal(size_t size, const char *type_id, pa_bool_t (*check_type)(const char *type_id)); +#define pa_object_new(type) ((type*) pa_object_new_internal(sizeof(type), type##_type_id, type##_check_type) #define pa_object_free ((void (*) (pa_object* _obj)) pa_xfree) -int pa_object_check_type(const char *type); +pa_bool_t pa_object_check_type(const char *type_id); -static inline int pa_object_isinstance(void *o) { +extern const char pa_object_type_id[]; + +static inline pa_bool_t pa_object_isinstance(void *o) { pa_object *obj = (pa_object*) o; - return obj ? obj->check_type("pa_object") : 0; + return obj ? obj->check_type(pa_object_type_id) : TRUE; } pa_object *pa_object_ref(pa_object *o); @@ -60,7 +62,7 @@ static inline int pa_object_refcnt(pa_object *o) { static inline pa_object* pa_object_cast(void *o) { pa_object *obj = (pa_object*) o; - pa_assert(!obj || obj->check_type("pa_object")); + pa_assert(!obj || obj->check_type(pa_object_type_id)); return obj; } @@ -68,10 +70,10 @@ static inline pa_object* pa_object_cast(void *o) { #define PA_OBJECT(o) pa_object_cast(o) -#define PA_DECLARE_CLASS(c) \ - static inline int c##_isinstance(void *o) { \ +#define PA_DECLARE_CLASS_COMMON(c) \ + static inline pa_bool_t c##_isinstance(void *o) { \ pa_object *obj = (pa_object*) o; \ - return obj ? obj->check_type(#c) : 1; \ + return obj ? obj->check_type(c##_type_id) : TRUE; \ } \ static inline c* c##_cast(void *o) { \ pa_assert(c##_isinstance(o)); \ @@ -91,12 +93,27 @@ static inline pa_object* pa_object_cast(void *o) { } \ struct __stupid_useless_struct_to_allow_trailing_semicolon -#define PA_DEFINE_CHECK_TYPE(c, parent) \ - int c##_check_type(const char *type) { \ - pa_assert(type); \ - if (strcmp(type, #c) == 0) \ - return 1; \ - return parent##_check_type(type); \ +#define PA_DECLARE_PUBLIC_CLASS(c) \ + extern const char c##_type_id[]; \ + PA_DECLARE_CLASS_COMMON(c); \ + pa_bool_t c##_check_type(const char *type_id) + +#define PA_DEFINE_PUBLIC_CLASS(c, parent) \ + const char c##_type_id[] = #c; \ + pa_bool_t c##_check_type(const char *type_id) { \ + if (type_id == c##_type_id) \ + return TRUE; \ + return parent##_check_type(type_id); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_DEFINE_PRIVATE_CLASS(c, parent) \ + static const char c##_type_id[] = #c; \ + PA_DECLARE_CLASS_COMMON(c); \ + static pa_bool_t c##_check_type(const char *type_id) { \ + if (type_id == c##_type_id) \ + return TRUE; \ + return parent##_check_type(type_id); \ } \ struct __stupid_useless_struct_to_allow_trailing_semicolon diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index fc8ce76f..f15466ce 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -169,7 +169,7 @@ static const char *command_names[PA_COMMAND_MAX] = { /* Supported since protocol v14 (0.9.12) */ [PA_COMMAND_EXTENSION] = "EXTENSION", - + /* Supported since protocol v15 (0.9.15) */ [PA_COMMAND_GET_CARD_INFO] = "GET_CARD_INFO", [PA_COMMAND_GET_CARD_INFO_LIST] = "GET_CARD_INFO_LIST", [PA_COMMAND_SET_CARD_PROFILE] = "SET_CARD_PROFILE", @@ -180,7 +180,11 @@ static const char *command_names[PA_COMMAND_MAX] = { /* SERVER->CLIENT */ [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = "PLAYBACK_BUFFER_ATTR_CHANGED", - [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = "RECORD_BUFFER_ATTR_CHANGED" + [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = "RECORD_BUFFER_ATTR_CHANGED", + + /* Supported since protocol v16 (0.9.16) */ + [PA_COMMAND_SET_SINK_PORT] = "SET_SINK_PORT", + [PA_COMMAND_SET_SOURCE_PORT] = "SET_SOURCE_PORT" }; #endif @@ -203,10 +207,10 @@ struct pa_pdispatch { const pa_pdispatch_cb_t *callback_table; unsigned n_commands; PA_LLIST_HEAD(struct reply_info, replies); - pa_pdispatch_drain_callback drain_callback; + pa_pdispatch_drain_cb_t drain_callback; void *drain_userdata; const pa_creds *creds; - pa_bool_t use_rtclock:1; + pa_bool_t use_rtclock; }; static void reply_info_free(struct reply_info *r) { @@ -225,19 +229,16 @@ static void reply_info_free(struct reply_info *r) { pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, pa_bool_t use_rtclock, const pa_pdispatch_cb_t *table, unsigned entries) { pa_pdispatch *pd; - pa_assert(mainloop); + pa_assert(mainloop); pa_assert((entries && table) || (!entries && !table)); - pd = pa_xnew(pa_pdispatch, 1); + pd = pa_xnew0(pa_pdispatch, 1); PA_REFCNT_INIT(pd); pd->mainloop = mainloop; pd->callback_table = table; pd->n_commands = entries; PA_LLIST_HEAD_INIT(struct reply_info, pd->replies); - pd->drain_callback = NULL; - pd->drain_userdata = NULL; - pd->creds = NULL; pd->use_rtclock = use_rtclock; return pd; @@ -317,7 +318,7 @@ int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds, if (command == PA_COMMAND_ERROR || command == PA_COMMAND_REPLY) { struct reply_info *r; - for (r = pd->replies; r; r = r->next) + PA_LLIST_FOREACH(r, pd->replies) if (r->tag == tag) break; @@ -325,9 +326,9 @@ int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds, run_action(pd, r, command, ts); } else if (pd->callback_table && (command < pd->n_commands) && pd->callback_table[command]) { - const pa_pdispatch_cb_t *c = pd->callback_table+command; + const pa_pdispatch_cb_t *cb = pd->callback_table+command; - (*c)(pd, command, tag, ts, userdata); + (*cb)(pd, command, tag, ts, userdata); } else { pa_log("Received unsupported command %u", command); goto finish; @@ -375,7 +376,9 @@ void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa r->free_cb = free_cb; r->tag = tag; - pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + timeout * PA_USEC_PER_SEC, pd->use_rtclock), timeout_callback, r)); + pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop, + pa_timeval_rtstore(&tv, pa_rtclock_now() + timeout * PA_USEC_PER_SEC, pd->use_rtclock), + timeout_callback, r)); PA_LLIST_PREPEND(struct reply_info, pd->replies, r); } @@ -387,7 +390,7 @@ int pa_pdispatch_is_pending(pa_pdispatch *pd) { return !!pd->replies; } -void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, void (*cb)(pa_pdispatch *pd, void *userdata), void *userdata) { +void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t cb, void *userdata) { pa_assert(pd); pa_assert(PA_REFCNT_VALUE(pd) >= 1); pa_assert(!cb || pa_pdispatch_is_pending(pd)); @@ -402,12 +405,9 @@ void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata) { pa_assert(pd); pa_assert(PA_REFCNT_VALUE(pd) >= 1); - for (r = pd->replies; r; r = n) { - n = r->next; - + PA_LLIST_FOREACH_SAFE(r, n, pd->replies) if (r->userdata == userdata) reply_info_free(r); - } } void pa_pdispatch_unref(pa_pdispatch *pd) { diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h index dae475af..c5431c2e 100644 --- a/src/pulsecore/pdispatch.h +++ b/src/pulsecore/pdispatch.h @@ -35,9 +35,9 @@ typedef struct pa_pdispatch pa_pdispatch; typedef void (*pa_pdispatch_cb_t)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -typedef void (*pa_pdispatch_drain_callback)(pa_pdispatch *pd, void *userdata); +typedef void (*pa_pdispatch_drain_cb_t)(pa_pdispatch *pd, void *userdata); -pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, pa_bool_t use_rtclock, const pa_pdispatch_cb_t*table, unsigned entries); +pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, pa_bool_t use_rtclock, const pa_pdispatch_cb_t *table, unsigned entries); void pa_pdispatch_unref(pa_pdispatch *pd); pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd); @@ -47,7 +47,7 @@ void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa int pa_pdispatch_is_pending(pa_pdispatch *pd); -void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_callback callback, void *userdata); +void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t callback, void *userdata); /* Remove all reply slots with the give userdata parameter */ void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata); diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c index 00878462..213e7983 100644 --- a/src/pulsecore/pid.c +++ b/src/pulsecore/pid.c @@ -81,17 +81,14 @@ static pid_t read_pid(const char *fn, int fd) { } static int open_pid_file(const char *fn, int mode) { - int fd = -1; + int fd; pa_assert(fn); for (;;) { struct stat st; - if ((fd = open(fn, mode -#ifdef O_NOCTTY - |O_NOCTTY -#endif + if ((fd = pa_open_cloexec(fn, mode #ifdef O_NOFOLLOW |O_NOFOLLOW #endif @@ -123,8 +120,6 @@ static int open_pid_file(const char *fn, int mode) { fd = -1; goto fail; } - - fd = -1; } return fd; @@ -148,7 +143,7 @@ static int proc_name_ours(pid_t pid, const char *procname) { pa_snprintf(bn, sizeof(bn), "/proc/%lu/stat", (unsigned long) pid); - if (!(f = fopen(bn, "r"))) { + if (!(f = pa_fopen_cloexec(bn, "r"))) { pa_log_info("Failed to open %s: %s", bn, pa_cstrerror(errno)); return -1; } else { diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c index fceb2ca1..f528c496 100644 --- a/src/pulsecore/play-memblockq.c +++ b/src/pulsecore/play-memblockq.c @@ -47,9 +47,8 @@ enum { MEMBLOCKQ_STREAM_MESSAGE_UNLINK, }; -PA_DECLARE_CLASS(memblockq_stream); +PA_DEFINE_PRIVATE_CLASS(memblockq_stream, pa_msgobject); #define MEMBLOCKQ_STREAM(o) (memblockq_stream_cast(o)) -static PA_DEFINE_CHECK_TYPE(memblockq_stream, pa_msgobject); static void memblockq_stream_unlink(memblockq_stream *u) { pa_assert(u); @@ -200,7 +199,7 @@ pa_sink_input* pa_memblockq_sink_input_new( pa_sink_input_new_data_set_volume(&data, volume); pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); - pa_sink_input_new(&u->sink_input, sink->core, &data, 0); + pa_sink_input_new(&u->sink_input, sink->core, &data); pa_sink_input_new_data_done(&data); if (!u->sink_input) diff --git a/src/pulsecore/protocol-dbus.c b/src/pulsecore/protocol-dbus.c new file mode 100644 index 00000000..5c1127be --- /dev/null +++ b/src/pulsecore/protocol-dbus.c @@ -0,0 +1,1134 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/shared.h> +#include <pulsecore/strbuf.h> + +#include "protocol-dbus.h" + +struct pa_dbus_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_hashmap *objects; /* Object path -> struct object_entry */ + pa_hashmap *connections; /* DBusConnection -> struct connection_entry */ + pa_idxset *extensions; /* Strings */ + + pa_hook hooks[PA_DBUS_PROTOCOL_HOOK_MAX]; +}; + +struct object_entry { + char *path; + pa_hashmap *interfaces; /* Interface name -> struct interface_entry */ + char *introspection; +}; + +struct connection_entry { + DBusConnection *connection; + pa_client *client; + + pa_bool_t listening_for_all_signals; + + /* Contains object paths. If this is empty, then signals from all objects + * are accepted. Only used when listening_for_all_signals == TRUE. */ + pa_idxset *all_signals_objects; + + /* Signal name -> idxset. The idxsets contain object paths. If an idxset is + * empty, then that signal is accepted from all objects. Only used when + * listening_for_all_signals == FALSE. */ + pa_hashmap *listening_signals; +}; + +struct interface_entry { + char *name; + pa_hashmap *method_handlers; + pa_hashmap *method_signatures; /* Derived from method_handlers. Contains only "in" arguments. */ + pa_hashmap *property_handlers; + pa_dbus_receive_cb_t get_all_properties_cb; + pa_dbus_signal_info *signals; + unsigned n_signals; + void *userdata; +}; + +char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) { + char *address = NULL; + char *runtime_path = NULL; + char *escaped_path = NULL; + + switch (server_type) { + case PA_SERVER_TYPE_USER: + pa_assert_se((runtime_path = pa_runtime_path(PA_DBUS_SOCKET_NAME))); + pa_assert_se((escaped_path = dbus_address_escape_value(runtime_path))); + address = pa_sprintf_malloc("unix:path=%s", escaped_path); + break; + + case PA_SERVER_TYPE_SYSTEM: + pa_assert_se((escaped_path = dbus_address_escape_value(PA_DBUS_SYSTEM_SOCKET_PATH))); + address = pa_sprintf_malloc("unix:path=%s", escaped_path); + break; + + case PA_SERVER_TYPE_NONE: + address = pa_xnew0(char, 1); + break; + + default: + pa_assert_not_reached(); + } + + pa_xfree(runtime_path); + pa_xfree(escaped_path); + + return address; +} + +static pa_dbus_protocol *dbus_protocol_new(pa_core *c) { + pa_dbus_protocol *p; + unsigned i; + + pa_assert(c); + + p = pa_xnew(pa_dbus_protocol, 1); + PA_REFCNT_INIT(p); + p->core = pa_core_ref(c); + p->objects = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + p->connections = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + p->extensions = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i) + pa_hook_init(&p->hooks[i], p); + + pa_assert_se(pa_shared_set(c, "dbus-protocol", p) >= 0); + + return p; +} + +pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c) { + pa_dbus_protocol *p; + + if ((p = pa_shared_get(c, "dbus-protocol"))) + return pa_dbus_protocol_ref(p); + + return dbus_protocol_new(c); +} + +pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_dbus_protocol_unref(pa_dbus_protocol *p) { + unsigned i; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + pa_assert(pa_hashmap_isempty(p->objects)); + pa_assert(pa_hashmap_isempty(p->connections)); + pa_assert(pa_idxset_isempty(p->extensions)); + + pa_hashmap_free(p->objects, NULL, NULL); + pa_hashmap_free(p->connections, NULL, NULL); + pa_idxset_free(p->extensions, NULL, NULL); + + for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i) + pa_hook_done(&p->hooks[i]); + + pa_assert_se(pa_shared_remove(p->core, "dbus-protocol") >= 0); + + pa_core_unref(p->core); + + pa_xfree(p); +} + +static void update_introspection(struct object_entry *oe) { + pa_strbuf *buf; + void *interfaces_state = NULL; + struct interface_entry *iface_entry = NULL; + + pa_assert(oe); + + buf = pa_strbuf_new(); + pa_strbuf_puts(buf, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); + pa_strbuf_puts(buf, "<node>\n"); + + PA_HASHMAP_FOREACH(iface_entry, oe->interfaces, interfaces_state) { + pa_dbus_method_handler *method_handler; + pa_dbus_property_handler *property_handler; + void *handlers_state = NULL; + unsigned i; + unsigned j; + + pa_strbuf_printf(buf, " <interface name=\"%s\">\n", iface_entry->name); + + PA_HASHMAP_FOREACH(method_handler, iface_entry->method_handlers, handlers_state) { + pa_strbuf_printf(buf, " <method name=\"%s\">\n", method_handler->method_name); + + for (i = 0; i < method_handler->n_arguments; ++i) + pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\" direction=\"%s\"/>\n", + method_handler->arguments[i].name, + method_handler->arguments[i].type, + method_handler->arguments[i].direction); + + pa_strbuf_puts(buf, " </method>\n"); + } + + handlers_state = NULL; + + PA_HASHMAP_FOREACH(property_handler, iface_entry->property_handlers, handlers_state) + pa_strbuf_printf(buf, " <property name=\"%s\" type=\"%s\" access=\"%s\"/>\n", + property_handler->property_name, + property_handler->type, + property_handler->get_cb ? (property_handler->set_cb ? "readwrite" : "read") : "write"); + + for (i = 0; i < iface_entry->n_signals; ++i) { + pa_strbuf_printf(buf, " <signal name=\"%s\">\n", iface_entry->signals[i].name); + + for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) + pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\"/>\n", iface_entry->signals[i].arguments[j].name, + iface_entry->signals[i].arguments[j].type); + + pa_strbuf_puts(buf, " </signal>\n"); + } + + pa_strbuf_puts(buf, " </interface>\n"); + } + + pa_strbuf_puts(buf, " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n"); + + pa_strbuf_puts(buf, "</node>\n"); + + pa_xfree(oe->introspection); + oe->introspection = pa_strbuf_tostring_free(buf); +} + +/* Return value of find_handler() and its subfunctions. */ +enum find_result_t { + /* The received message is a valid .Get call. */ + FOUND_GET_PROPERTY, + + /* The received message is a valid .Set call. */ + FOUND_SET_PROPERTY, + + /* The received message is a valid .GetAll call. */ + FOUND_GET_ALL, + + /* The received message is a valid method call. */ + FOUND_METHOD, + + /* The interface of the received message hasn't been registered for the + * destination object. */ + NO_SUCH_INTERFACE, + + /* No property handler was found for the received .Get or .Set call. */ + NO_SUCH_PROPERTY, + + /* The interface argument of a property call didn't match any registered + * interface. */ + NO_SUCH_PROPERTY_INTERFACE, + + /* The received message called .Get or .Set for a property whose access + * mode doesn't match the call. */ + PROPERTY_ACCESS_DENIED, + + /* The new value signature of a .Set call didn't match the expexted + * signature. */ + INVALID_PROPERTY_SIG, + + /* No method handler was found for the received message. */ + NO_SUCH_METHOD, + + /* The signature of the received message didn't match the expected + * signature. Despite the name, this can also be returned for a property + * call if its message signature is invalid. */ + INVALID_METHOD_SIG +}; + +/* Data for resolving the correct reaction to a received message. */ +struct call_info { + DBusMessage *message; /* The received message. */ + struct object_entry *obj_entry; + const char *interface; /* Destination interface name (extracted from the message). */ + struct interface_entry *iface_entry; + + const char *property; /* Property name (extracted from the message). */ + const char *property_interface; /* The interface argument of a property call is stored here. */ + pa_dbus_property_handler *property_handler; + const char *expected_property_sig; /* Property signature from the introspection data. */ + const char *property_sig; /* The signature of the new value in the received .Set message. */ + DBusMessageIter variant_iter; /* Iterator pointing to the beginning of the new value variant of a .Set call. */ + + const char *method; /* Method name (extracted from the message). */ + pa_dbus_method_handler *method_handler; + const char *expected_method_sig; /* Method signature from the introspection data. */ + const char *method_sig; /* The signature of the received message. */ +}; + +/* Called when call_info->property has been set and the property interface has + * not been given. In case of a Set call, call_info->property_sig is also set, + * which is checked against the expected value in this function. */ +static enum find_result_t find_handler_by_property(struct call_info *call_info) { + void *state = NULL; + + pa_assert(call_info); + + PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) { + if ((call_info->property_handler = pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) { + if (pa_streq(call_info->method, "Get")) + return call_info->property_handler->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED; + + else if (pa_streq(call_info->method, "Set")) { + call_info->expected_property_sig = call_info->property_handler->type; + + if (pa_streq(call_info->property_sig, call_info->expected_property_sig)) + return call_info->property_handler->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED; + else + return INVALID_PROPERTY_SIG; + + } else + pa_assert_not_reached(); + } + } + + return NO_SUCH_PROPERTY; +} + +static enum find_result_t find_handler_by_method(struct call_info *call_info) { + void *state = NULL; + + pa_assert(call_info); + + PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) { + if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method))) { + call_info->expected_method_sig = pa_hashmap_get(call_info->iface_entry->method_signatures, call_info->method); + + if (pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return FOUND_METHOD; + else + return INVALID_METHOD_SIG; + } + } + + return NO_SUCH_METHOD; +} + +static enum find_result_t find_handler_from_properties_call(struct call_info *call_info) { + pa_assert(call_info); + + if (pa_streq(call_info->method, "GetAll")) { + call_info->expected_method_sig = "s"; + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + pa_assert_se(dbus_message_get_args(call_info->message, NULL, + DBUS_TYPE_STRING, &call_info->property_interface, + DBUS_TYPE_INVALID)); + + if (*call_info->property_interface) { + if ((call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface))) + return FOUND_GET_ALL; + else + return NO_SUCH_PROPERTY_INTERFACE; + + } else { + pa_assert_se(call_info->iface_entry = pa_hashmap_first(call_info->obj_entry->interfaces)); + return FOUND_GET_ALL; + } + + } else if (pa_streq(call_info->method, "Get")) { + call_info->expected_method_sig = "ss"; + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + pa_assert_se(dbus_message_get_args(call_info->message, NULL, + DBUS_TYPE_STRING, &call_info->property_interface, + DBUS_TYPE_STRING, &call_info->property, + DBUS_TYPE_INVALID)); + + if (*call_info->property_interface) { + if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface))) + return NO_SUCH_PROPERTY_INTERFACE; + else if ((call_info->property_handler = + pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) + return FOUND_GET_PROPERTY; + else + return NO_SUCH_PROPERTY; + + } else + return find_handler_by_property(call_info); + + } else if (pa_streq(call_info->method, "Set")) { + DBusMessageIter msg_iter; + + call_info->expected_method_sig = "ssv"; + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + pa_assert_se(dbus_message_iter_init(call_info->message, &msg_iter)); + + dbus_message_iter_get_basic(&msg_iter, &call_info->property_interface); + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &call_info->property); + pa_assert_se(dbus_message_iter_next(&msg_iter)); + + dbus_message_iter_recurse(&msg_iter, &call_info->variant_iter); + + call_info->property_sig = dbus_message_iter_get_signature(&call_info->variant_iter); + + if (*call_info->property_interface) { + if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface))) + return NO_SUCH_PROPERTY_INTERFACE; + + else if ((call_info->property_handler = + pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) { + call_info->expected_property_sig = call_info->property_handler->type; + + if (pa_streq(call_info->property_sig, call_info->expected_property_sig)) + return FOUND_SET_PROPERTY; + else + return INVALID_PROPERTY_SIG; + + } else + return NO_SUCH_PROPERTY; + + } else + return find_handler_by_property(call_info); + + } else + pa_assert_not_reached(); +} + +static enum find_result_t find_handler(struct call_info *call_info) { + pa_assert(call_info); + + if (call_info->interface) { + if (pa_streq(call_info->interface, DBUS_INTERFACE_PROPERTIES)) + return find_handler_from_properties_call(call_info); + + else if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->interface))) + return NO_SUCH_INTERFACE; + + else if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method))) + return FOUND_METHOD; + + else + return NO_SUCH_METHOD; + + } else { /* The method call doesn't contain an interface. */ + if (pa_streq(call_info->method, "Get") || pa_streq(call_info->method, "Set") || pa_streq(call_info->method, "GetAll")) { + if (find_handler_by_method(call_info) == FOUND_METHOD) + /* The object has a method named Get, Set or GetAll in some other interface than .Properties. */ + return FOUND_METHOD; + else + /* Assume this is a .Properties call. */ + return find_handler_from_properties_call(call_info); + + } else /* This is not a .Properties call. */ + return find_handler_by_method(call_info); + } +} + +static DBusHandlerResult handle_message_cb(DBusConnection *connection, DBusMessage *message, void *user_data) { + pa_dbus_protocol *p = user_data; + struct call_info call_info; + + pa_assert(connection); + pa_assert(message); + pa_assert(p); + pa_assert(p->objects); + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + pa_log_debug("Received message: destination = %s, interface = %s, member = %s", + dbus_message_get_path(message), + dbus_message_get_interface(message), + dbus_message_get_member(message)); + + call_info.message = message; + pa_assert_se(call_info.obj_entry = pa_hashmap_get(p->objects, dbus_message_get_path(message))); + call_info.interface = dbus_message_get_interface(message); + pa_assert_se(call_info.method = dbus_message_get_member(message)); + pa_assert_se(call_info.method_sig = dbus_message_get_signature(message)); + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") || + (!dbus_message_get_interface(message) && dbus_message_has_member(message, "Introspect"))) { + pa_dbus_send_basic_value_reply(connection, message, DBUS_TYPE_STRING, &call_info.obj_entry->introspection); + goto finish; + } + + switch (find_handler(&call_info)) { + case FOUND_GET_PROPERTY: + call_info.property_handler->get_cb(connection, message, call_info.iface_entry->userdata); + break; + + case FOUND_SET_PROPERTY: + call_info.property_handler->set_cb(connection, message, &call_info.variant_iter, call_info.iface_entry->userdata); + break; + + case FOUND_METHOD: + call_info.method_handler->receive_cb(connection, message, call_info.iface_entry->userdata); + break; + + case FOUND_GET_ALL: + if (call_info.iface_entry->get_all_properties_cb) + call_info.iface_entry->get_all_properties_cb(connection, message, call_info.iface_entry->userdata); + else { + DBusMessage *dummy_reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + + pa_assert_se(dummy_reply = dbus_message_new_method_return(message)); + dbus_message_iter_init_append(dummy_reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(connection, dummy_reply, NULL)); + dbus_message_unref(dummy_reply); + } + break; + + case PROPERTY_ACCESS_DENIED: + pa_dbus_send_error(connection, message, DBUS_ERROR_ACCESS_DENIED, + "%s access denied for property %s", call_info.method, call_info.property); + break; + + case NO_SUCH_METHOD: + pa_dbus_send_error(connection, message, DBUS_ERROR_UNKNOWN_METHOD, "No such method: %s", call_info.method); + break; + + case NO_SUCH_PROPERTY: + pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "No such property: %s", call_info.property); + break; + + case INVALID_METHOD_SIG: + pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS, + "Invalid signature for method %s: '%s'. Expected '%s'.", + call_info.method, call_info.method_sig, call_info.expected_method_sig); + break; + + case INVALID_PROPERTY_SIG: + pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS, + "Invalid signature for property %s: '%s'. Expected '%s'.", + call_info.property, call_info.property_sig, call_info.expected_property_sig); + + default: + pa_assert_not_reached(); + } + +finish: + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusObjectPathVTable vtable = { + .unregister_function = NULL, + .message_function = handle_message_cb, + .dbus_internal_pad1 = NULL, + .dbus_internal_pad2 = NULL, + .dbus_internal_pad3 = NULL, + .dbus_internal_pad4 = NULL +}; + +static void register_object(pa_dbus_protocol *p, struct object_entry *obj_entry) { + struct connection_entry *conn_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(obj_entry); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) + pa_assert_se(dbus_connection_register_object_path(conn_entry->connection, obj_entry->path, &vtable, p)); +} + +static pa_dbus_arg_info *copy_args(const pa_dbus_arg_info *src, unsigned n) { + pa_dbus_arg_info *dst; + unsigned i; + + if (n == 0) + return NULL; + + pa_assert(src); + + dst = pa_xnew0(pa_dbus_arg_info, n); + + for (i = 0; i < n; ++i) { + dst[i].name = pa_xstrdup(src[i].name); + dst[i].type = pa_xstrdup(src[i].type); + dst[i].direction = pa_xstrdup(src[i].direction); + } + + return dst; +} + +static pa_hashmap *create_method_handlers(const pa_dbus_interface_info *info) { + pa_hashmap *handlers; + unsigned i; + + pa_assert(info); + pa_assert(info->method_handlers || info->n_method_handlers == 0); + + handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < info->n_method_handlers; ++i) { + pa_dbus_method_handler *h = pa_xnew(pa_dbus_method_handler, 1); + h->method_name = pa_xstrdup(info->method_handlers[i].method_name); + h->arguments = copy_args(info->method_handlers[i].arguments, info->method_handlers[i].n_arguments); + h->n_arguments = info->method_handlers[i].n_arguments; + h->receive_cb = info->method_handlers[i].receive_cb; + + pa_hashmap_put(handlers, h->method_name, h); + } + + return handlers; +} + +static pa_hashmap *extract_method_signatures(pa_hashmap *method_handlers) { + pa_hashmap *signatures = NULL; + pa_dbus_method_handler *handler = NULL; + void *state = NULL; + pa_strbuf *sig_buf = NULL; + unsigned i = 0; + + pa_assert(method_handlers); + + signatures = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + PA_HASHMAP_FOREACH(handler, method_handlers, state) { + sig_buf = pa_strbuf_new(); + + for (i = 0; i < handler->n_arguments; ++i) { + if (pa_streq(handler->arguments[i].direction, "in")) + pa_strbuf_puts(sig_buf, handler->arguments[i].type); + } + + pa_hashmap_put(signatures, handler->method_name, pa_strbuf_tostring_free(sig_buf)); + } + + return signatures; +} + +static pa_hashmap *create_property_handlers(const pa_dbus_interface_info *info) { + pa_hashmap *handlers; + unsigned i = 0; + + pa_assert(info); + pa_assert(info->property_handlers || info->n_property_handlers == 0); + + handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < info->n_property_handlers; ++i) { + pa_dbus_property_handler *h = pa_xnew(pa_dbus_property_handler, 1); + h->property_name = pa_xstrdup(info->property_handlers[i].property_name); + h->type = pa_xstrdup(info->property_handlers[i].type); + h->get_cb = info->property_handlers[i].get_cb; + h->set_cb = info->property_handlers[i].set_cb; + + pa_hashmap_put(handlers, h->property_name, h); + } + + return handlers; +} + +static pa_dbus_signal_info *copy_signals(const pa_dbus_interface_info *info) { + pa_dbus_signal_info *dst; + unsigned i; + + pa_assert(info); + + if (info->n_signals == 0) + return NULL; + + pa_assert(info->signals); + + dst = pa_xnew(pa_dbus_signal_info, info->n_signals); + + for (i = 0; i < info->n_signals; ++i) { + dst[i].name = pa_xstrdup(info->signals[i].name); + dst[i].arguments = copy_args(info->signals[i].arguments, info->signals[i].n_arguments); + dst[i].n_arguments = info->signals[i].n_arguments; + } + + return dst; +} + +int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, + const char *path, + const pa_dbus_interface_info *info, + void *userdata) { + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + pa_bool_t obj_entry_created = FALSE; + + pa_assert(p); + pa_assert(path); + pa_assert(info); + pa_assert(info->name); + pa_assert(info->method_handlers || info->n_method_handlers == 0); + pa_assert(info->property_handlers || info->n_property_handlers == 0); + pa_assert(info->get_all_properties_cb || info->n_property_handlers == 0); + pa_assert(info->signals || info->n_signals == 0); + + if (!(obj_entry = pa_hashmap_get(p->objects, path))) { + obj_entry = pa_xnew(struct object_entry, 1); + obj_entry->path = pa_xstrdup(path); + obj_entry->interfaces = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + obj_entry->introspection = NULL; + + pa_hashmap_put(p->objects, path, obj_entry); + obj_entry_created = TRUE; + } + + if (pa_hashmap_get(obj_entry->interfaces, info->name) != NULL) + goto fail; /* The interface was already registered. */ + + iface_entry = pa_xnew(struct interface_entry, 1); + iface_entry->name = pa_xstrdup(info->name); + iface_entry->method_handlers = create_method_handlers(info); + iface_entry->method_signatures = extract_method_signatures(iface_entry->method_handlers); + iface_entry->property_handlers = create_property_handlers(info); + iface_entry->get_all_properties_cb = info->get_all_properties_cb; + iface_entry->signals = copy_signals(info); + iface_entry->n_signals = info->n_signals; + iface_entry->userdata = userdata; + pa_hashmap_put(obj_entry->interfaces, iface_entry->name, iface_entry); + + update_introspection(obj_entry); + + if (obj_entry_created) + register_object(p, obj_entry); + + pa_log_debug("Interface %s added for object %s", iface_entry->name, obj_entry->path); + + return 0; + +fail: + if (obj_entry_created) { + pa_hashmap_remove(p->objects, path); + pa_xfree(obj_entry); + } + + return -1; +} + +static void unregister_object(pa_dbus_protocol *p, struct object_entry *obj_entry) { + struct connection_entry *conn_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(obj_entry); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) + pa_assert_se(dbus_connection_unregister_object_path(conn_entry->connection, obj_entry->path)); +} + +static void method_handler_free_cb(void *p, void *userdata) { + pa_dbus_method_handler *h = p; + unsigned i; + + pa_assert(h); + + pa_xfree((char *) h->method_name); + + for (i = 0; i < h->n_arguments; ++i) { + pa_xfree((char *) h->arguments[i].name); + pa_xfree((char *) h->arguments[i].type); + pa_xfree((char *) h->arguments[i].direction); + } + + pa_xfree((pa_dbus_arg_info *) h->arguments); +} + +static void method_signature_free_cb(void *p, void *userdata) { + pa_assert(p); + + pa_xfree(p); +} + +static void property_handler_free_cb(void *p, void *userdata) { + pa_dbus_property_handler *h = p; + + pa_assert(h); + + pa_xfree((char *) h->property_name); + pa_xfree((char *) h->type); + + pa_xfree(h); +} + +int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface) { + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + unsigned i; + + pa_assert(p); + pa_assert(path); + pa_assert(interface); + + if (!(obj_entry = pa_hashmap_get(p->objects, path))) + return -1; + + if (!(iface_entry = pa_hashmap_remove(obj_entry->interfaces, interface))) + return -1; + + update_introspection(obj_entry); + + pa_log_debug("Interface %s removed from object %s", iface_entry->name, obj_entry->path); + + pa_xfree(iface_entry->name); + pa_hashmap_free(iface_entry->method_handlers, method_handler_free_cb, NULL); + pa_hashmap_free(iface_entry->method_signatures, method_signature_free_cb, NULL); + pa_hashmap_free(iface_entry->property_handlers, property_handler_free_cb, NULL); + + for (i = 0; i < iface_entry->n_signals; ++i) { + unsigned j; + + pa_xfree((char *) iface_entry->signals[i].name); + + for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) { + pa_xfree((char *) iface_entry->signals[i].arguments[j].name); + pa_xfree((char *) iface_entry->signals[i].arguments[j].type); + pa_assert(iface_entry->signals[i].arguments[j].direction == NULL); + } + + pa_xfree((pa_dbus_arg_info *) iface_entry->signals[i].arguments); + } + + pa_xfree(iface_entry->signals); + pa_xfree(iface_entry); + + if (pa_hashmap_isempty(obj_entry->interfaces)) { + unregister_object(p, obj_entry); + + pa_hashmap_remove(p->objects, path); + pa_xfree(obj_entry->path); + pa_hashmap_free(obj_entry->interfaces, NULL, NULL); + pa_xfree(obj_entry->introspection); + pa_xfree(obj_entry); + } + + return 0; +} + +static void register_all_objects(pa_dbus_protocol *p, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(conn); + + PA_HASHMAP_FOREACH(obj_entry, p->objects, state) + pa_assert_se(dbus_connection_register_object_path(conn, obj_entry->path, &vtable, p)); +} + +int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + pa_assert(client); + + if (pa_hashmap_get(p->connections, conn)) + return -1; /* The connection was already registered. */ + + register_all_objects(p, conn); + + conn_entry = pa_xnew(struct connection_entry, 1); + conn_entry->connection = dbus_connection_ref(conn); + conn_entry->client = client; + conn_entry->listening_for_all_signals = FALSE; + conn_entry->all_signals_objects = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + conn_entry->listening_signals = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + pa_hashmap_put(p->connections, conn, conn_entry); + + return 0; +} + +static void unregister_all_objects(pa_dbus_protocol *p, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(conn); + + PA_HASHMAP_FOREACH(obj_entry, p->objects, state) + pa_assert_se(dbus_connection_unregister_object_path(conn, obj_entry->path)); +} + +static void free_listened_object_name_cb(void *p, void *userdata) { + pa_assert(p); + + pa_xfree(p); +} + +static void free_listening_signals_idxset_cb(void *p, void *userdata) { + pa_idxset *set = p; + + pa_assert(set); + + pa_idxset_free(set, free_listened_object_name_cb, NULL); +} + +int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + + if (!(conn_entry = pa_hashmap_remove(p->connections, conn))) + return -1; + + unregister_all_objects(p, conn); + + dbus_connection_unref(conn_entry->connection); + pa_idxset_free(conn_entry->all_signals_objects, free_listened_object_name_cb, NULL); + pa_hashmap_free(conn_entry->listening_signals, free_listening_signals_idxset_cb, NULL); + pa_xfree(conn_entry); + + return 0; +} + +pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + + if (!(conn_entry = pa_hashmap_get(p->connections, conn))) + return NULL; + + return conn_entry->client; +} + +void pa_dbus_protocol_add_signal_listener( + pa_dbus_protocol *p, + DBusConnection *conn, + const char *signal, + char **objects, + unsigned n_objects) { + struct connection_entry *conn_entry; + pa_idxset *object_set; + char *object_path; + unsigned i; + + pa_assert(p); + pa_assert(conn); + pa_assert(objects || n_objects == 0); + + pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn))); + + /* all_signals_objects will either be emptied or replaced with new objects, + * so we empty it here unconditionally. If listening_for_all_signals is + * currently FALSE, the idxset is empty already. */ + while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL))) + pa_xfree(object_path); + + if (signal) { + conn_entry->listening_for_all_signals = FALSE; + + /* Replace the old object list with a new one. */ + if ((object_set = pa_hashmap_remove(conn_entry->listening_signals, signal))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + object_set = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < n_objects; ++i) + pa_idxset_put(object_set, pa_xstrdup(objects[i]), NULL); + + pa_hashmap_put(conn_entry->listening_signals, signal, object_set); + + } else { + conn_entry->listening_for_all_signals = TRUE; + + /* We're not interested in individual signals anymore, so let's empty + * listening_signals. */ + while ((object_set = pa_hashmap_steal_first(conn_entry->listening_signals))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + + for (i = 0; i < n_objects; ++i) + pa_idxset_put(conn_entry->all_signals_objects, pa_xstrdup(objects[i]), NULL); + } +} + +void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal) { + struct connection_entry *conn_entry; + pa_idxset *object_set; + + pa_assert(p); + pa_assert(conn); + + pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn))); + + if (signal) { + if ((object_set = pa_hashmap_get(conn_entry->listening_signals, signal))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + + } else { + char *object_path; + + conn_entry->listening_for_all_signals = FALSE; + + while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL))) + pa_xfree(object_path); + + while ((object_set = pa_hashmap_steal_first(conn_entry->listening_signals))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + } +} + +void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal) { + struct connection_entry *conn_entry; + void *state = NULL; + pa_idxset *object_set; + DBusMessage *signal_copy; + char *signal_string; + + pa_assert(p); + pa_assert(signal); + pa_assert(dbus_message_get_type(signal) == DBUS_MESSAGE_TYPE_SIGNAL); + pa_assert_se(dbus_message_get_interface(signal)); + pa_assert_se(dbus_message_get_member(signal)); + + signal_string = pa_sprintf_malloc("%s.%s", dbus_message_get_interface(signal), dbus_message_get_member(signal)); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) { + if ((conn_entry->listening_for_all_signals /* Case 1: listening for all signals */ + && (pa_idxset_get_by_data(conn_entry->all_signals_objects, dbus_message_get_path(signal), NULL) + || pa_idxset_isempty(conn_entry->all_signals_objects))) + + || (!conn_entry->listening_for_all_signals /* Case 2: not listening for all signals */ + && (object_set = pa_hashmap_get(conn_entry->listening_signals, signal_string)) + && (pa_idxset_get_by_data(object_set, dbus_message_get_path(signal), NULL) + || pa_idxset_isempty(object_set)))) { + + pa_assert_se(signal_copy = dbus_message_copy(signal)); + pa_assert_se(dbus_connection_send(conn_entry->connection, signal_copy, NULL)); + dbus_message_unref(signal_copy); + } + } + + pa_xfree(signal_string); +} + +const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n) { + const char **extensions; + const char *ext_name; + void *state = NULL; + unsigned i = 0; + + pa_assert(p); + pa_assert(n); + + *n = pa_idxset_size(p->extensions); + + if (*n <= 0) + return NULL; + + extensions = pa_xnew(const char *, *n); + + while ((ext_name = pa_idxset_iterate(p->extensions, &state, NULL))) + extensions[i++] = ext_name; + + return extensions; +} + +int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name) { + char *internal_name; + + pa_assert(p); + pa_assert(name); + + internal_name = pa_xstrdup(name); + + if (pa_idxset_put(p->extensions, internal_name, NULL) < 0) { + pa_xfree(internal_name); + return -1; + } + + pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED], internal_name); + + return 0; +} + +int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name) { + char *internal_name; + + pa_assert(p); + pa_assert(name); + + if (!(internal_name = pa_idxset_remove_by_data(p->extensions, name, NULL))) + return -1; + + pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED], internal_name); + + pa_xfree(internal_name); + + return 0; +} + +pa_hook_slot *pa_dbus_protocol_hook_connect( + pa_dbus_protocol *p, + pa_dbus_protocol_hook_t hook, + pa_hook_priority_t prio, + pa_hook_cb_t cb, + void *data) { + pa_assert(p); + pa_assert(hook < PA_DBUS_PROTOCOL_HOOK_MAX); + pa_assert(cb); + + return pa_hook_connect(&p->hooks[hook], prio, cb, data); +} diff --git a/src/pulsecore/protocol-dbus.h b/src/pulsecore/protocol-dbus.h new file mode 100644 index 00000000..6d100f7c --- /dev/null +++ b/src/pulsecore/protocol-dbus.h @@ -0,0 +1,217 @@ +#ifndef fooprotocoldbushfoo +#define fooprotocoldbushfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/core.h> +#include <pulsecore/macro.h> + +#define PA_DBUS_DEFAULT_PORT 24883 +#define PA_DBUS_SOCKET_NAME "dbus-socket" + +#define PA_DBUS_SYSTEM_SOCKET_PATH PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_DBUS_SOCKET_NAME + +#define PA_DBUS_CORE_INTERFACE "org.PulseAudio.Core1" +#define PA_DBUS_CORE_OBJECT_PATH "/org/pulseaudio/core1" + +#define PA_DBUS_ERROR_NO_SUCH_PROPERTY PA_DBUS_CORE_INTERFACE ".NoSuchPropertyError" +#define PA_DBUS_ERROR_NOT_FOUND PA_DBUS_CORE_INTERFACE ".NotFoundError" + +/* Returns the default address of the server type in the escaped form. For + * PA_SERVER_TYPE_NONE an empty string is returned. The caller frees the + * string. */ +char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type); + +typedef struct pa_dbus_protocol pa_dbus_protocol; + +/* This function either creates a new pa_dbus_protocol object, or if one + * already exists, increases the reference count. */ +pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c); + +pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p); +void pa_dbus_protocol_unref(pa_dbus_protocol *p); + +/* Called when a received message needs handling. Completely ignoring the + * message isn't a good idea; if you can't handle the message, reply with an + * error. + * + * The message signature is already checked against the introspection data, so + * you don't have to do that yourself. + * + * All messages are method calls. */ +typedef void (*pa_dbus_receive_cb_t)(DBusConnection *conn, DBusMessage *msg, void *userdata); + +/* A specialized version of pa_dbus_receive_cb_t: the additional iterator + * argument points to the element inside the new value variant. + * + * The new value signature is checked against the introspection data, so you + * don't have to do that yourself. */ +typedef void (*pa_dbus_set_property_cb_t)(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); + +typedef struct pa_dbus_arg_info { + const char *name; + const char *type; + const char *direction; /* NULL for signal arguments. */ +} pa_dbus_arg_info; + +typedef struct pa_dbus_signal_info { + const char *name; + const pa_dbus_arg_info *arguments; /* NULL, if the signal has no args. */ + unsigned n_arguments; +} pa_dbus_signal_info; + +typedef struct pa_dbus_method_handler { + const char *method_name; + const pa_dbus_arg_info *arguments; /* NULL, if the method has no args. */ + unsigned n_arguments; + pa_dbus_receive_cb_t receive_cb; +} pa_dbus_method_handler; + +typedef struct pa_dbus_property_handler { + const char *property_name; + const char *type; + + /* The access mode for the property is determined by checking whether + * get_cb or set_cb is NULL. */ + pa_dbus_receive_cb_t get_cb; + pa_dbus_set_property_cb_t set_cb; +} pa_dbus_property_handler; + +typedef struct pa_dbus_interface_info { + const char* name; + const pa_dbus_method_handler *method_handlers; /* NULL, if the interface has no methods. */ + unsigned n_method_handlers; + const pa_dbus_property_handler *property_handlers; /* NULL, if the interface has no properties. */ + unsigned n_property_handlers; + const pa_dbus_receive_cb_t get_all_properties_cb; /* May be NULL, in which case GetAll returns an error. */ + const pa_dbus_signal_info *signals; /* NULL, if the interface has no signals. */ + unsigned n_signals; +} pa_dbus_interface_info; + + +/* The following functions may only be called from the main thread. */ + +/* Registers the given interface to the given object path. It doesn't matter + * whether or not the object has already been registered; if it is, then its + * interface set is extended. + * + * Introspection requests are handled automatically. + * + * Userdata is passed to all the callbacks. + * + * Fails and returns a negative number if the object already has the interface + * registered. */ +int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, const char *path, const pa_dbus_interface_info *info, void *userdata); + +/* Returns a negative number if the given object doesn't have the given + * interface registered. */ +int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface); + +/* Fails and returns a negative number if the connection is already + * registered. */ +int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client); + +/* Returns a negative number if the connection isn't registered. */ +int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn); + +/* Returns NULL if the connection isn't registered. */ +pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn); + +/* Enables signal receiving for the given connection. The connection must have + * been registered earlier. The signal string must contain both the signal + * interface and the signal name, concatenated using a period as the separator. + * + * If the signal argument is NULL, all signals will be sent to the connection, + * otherwise calling this function only adds the given signal to the list of + * signals that will be delivered to the connection. + * + * The objects argument is a list of object paths. If the list is not empty, + * only signals from the given objects are delivered. If this function is + * called multiple time for the same connection and signal, the latest call + * always replaces the previous object list. */ +void pa_dbus_protocol_add_signal_listener( + pa_dbus_protocol *p, + DBusConnection *conn, + const char *signal, + char **objects, + unsigned n_objects); + +/* Disables the delivery of the signal for the given connection. The connection + * must have been registered. If signal is NULL, all signals are disabled. If + * signal is non-NULL and _add_signal_listener() was previously called with + * NULL signal (causing all signals to be enabled), this function doesn't do + * anything. Also, if the signal wasn't enabled before, this function doesn't + * do anything in that case either. */ +void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal); + +/* Sends the given signal to all interested clients. By default no signals are + * sent - clients have to explicitly to request signals by calling + * .Core1.ListenForSignal. That method's handler then calls + * pa_dbus_protocol_add_signal_listener(). */ +void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal); + +/* Returns an array of extension identifier strings. The strings pointers point + * to the internal copies, so don't free the strings. The caller must free the + * array, however. Also, do not save the returned pointer or any of the string + * pointers, because the contained strings may be freed at any time. If you + * need to save the array, copy it. */ +const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n); + +/* Modules that want to provide a D-Bus interface for clients should register + * an identifier that the clients can use to check whether the additional + * functionality is available. + * + * This function registers the extension with the given name. It is recommended + * that the name follows the D-Bus interface naming convention, so that the + * names remain unique in case there will be at some point in the future + * extensions that aren't included with the main PulseAudio source tree. For + * in-tree extensions the convention is to use the org.PulseAudio.Ext + * namespace. + * + * It is suggested that the name contains a version number, and whenever the + * extension interface is modified in non-backwards compatible way, the version + * number is incremented. + * + * Fails and returns a negative number if the extension is already registered. + */ +int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name); + +/* Returns a negative number if the extension isn't registered. */ +int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name); + +/* All hooks have the pa_dbus_protocol object as hook data. */ +typedef enum pa_dbus_protocol_hook { + PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, /* Extension name as call data. */ + PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, /* Extension name as call data. */ + PA_DBUS_PROTOCOL_HOOK_MAX +} pa_dbus_protocol_hook_t; + +pa_hook_slot *pa_dbus_protocol_hook_connect( + pa_dbus_protocol *p, + pa_dbus_protocol_hook_t hook, + pa_hook_priority_t prio, + pa_hook_cb_t cb, + void *data); + +#endif diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index f64552aa..2326eb3a 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -120,9 +120,8 @@ typedef struct connection { pa_time_event *auth_timeout_event; } connection; -PA_DECLARE_CLASS(connection); +PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject); #define CONNECTION(o) (connection_cast(o)) -static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject); struct pa_esound_protocol { PA_REFCNT_DECLARE; @@ -430,7 +429,7 @@ static int esd_proto_stream_play(connection *c, esd_proto_t request, const void sdata.sink = sink; pa_sink_input_new_data_set_sample_spec(&sdata, &ss); - pa_sink_input_new(&c->sink_input, c->protocol->core, &sdata, 0); + pa_sink_input_new(&c->sink_input, c->protocol->core, &sdata); pa_sink_input_new_data_done(&sdata); CHECK_VALIDITY(c->sink_input, "Failed to create sink input."); @@ -526,7 +525,7 @@ static int esd_proto_stream_record(connection *c, esd_proto_t request, const voi sdata.source = source; pa_source_output_new_data_set_sample_spec(&sdata, &ss); - pa_source_output_new(&c->source_output, c->protocol->core, &sdata, 0); + pa_source_output_new(&c->source_output, c->protocol->core, &sdata); pa_source_output_new_data_done(&sdata); CHECK_VALIDITY(c->source_output, "Failed to create source output."); @@ -772,7 +771,6 @@ static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void * memcpy(&rvolume, data, sizeof(uint32_t)); rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume); - data = (const char*)data + sizeof(uint32_t); if ((conn = pa_idxset_get_by_index(c->protocol->connections, idx)) && conn->sink_input) { pa_cvolume volume; @@ -810,7 +808,6 @@ static int esd_proto_sample_pan(connection *c, esd_proto_t request, const void * memcpy(&rvolume, data, sizeof(uint32_t)); rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume); - data = (const char*)data + sizeof(uint32_t); volume.values[0] = (lvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; @@ -1124,7 +1121,7 @@ static int do_read(connection *c) { ssize_t r; size_t l; void *p; - size_t space; + size_t space = 0; pa_assert(c->input_memblockq); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index 5220cc91..c09e5348 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -533,7 +533,7 @@ static void handle_listen_prefix(struct connection *c, const char *source_name) pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); - pa_source_output_new(&c->source_output, c->protocol->core, &data, 0); + pa_source_output_new(&c->source_output, c->protocol->core, &data); pa_source_output_new_data_done(&data); if (!c->source_output) { diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 9a37c565..bb29a196 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -98,17 +98,15 @@ typedef struct record_stream { pa_usec_t current_source_latency; } record_stream; -PA_DECLARE_CLASS(record_stream); #define RECORD_STREAM(o) (record_stream_cast(o)) -static PA_DEFINE_CHECK_TYPE(record_stream, pa_msgobject); +PA_DEFINE_PRIVATE_CLASS(record_stream, pa_msgobject); typedef struct output_stream { pa_msgobject parent; } output_stream; -PA_DECLARE_CLASS(output_stream); #define OUTPUT_STREAM(o) (output_stream_cast(o)) -static PA_DEFINE_CHECK_TYPE(output_stream, pa_msgobject); +PA_DEFINE_PRIVATE_CLASS(output_stream, pa_msgobject); typedef struct playback_stream { output_stream parent; @@ -138,9 +136,8 @@ typedef struct playback_stream { uint64_t playing_for, underrun_for; } playback_stream; -PA_DECLARE_CLASS(playback_stream); #define PLAYBACK_STREAM(o) (playback_stream_cast(o)) -static PA_DEFINE_CHECK_TYPE(playback_stream, output_stream); +PA_DEFINE_PRIVATE_CLASS(playback_stream, output_stream); typedef struct upload_stream { output_stream parent; @@ -156,9 +153,8 @@ typedef struct upload_stream { pa_proplist *proplist; } upload_stream; -PA_DECLARE_CLASS(upload_stream); #define UPLOAD_STREAM(o) (upload_stream_cast(o)) -static PA_DEFINE_CHECK_TYPE(upload_stream, output_stream); +PA_DEFINE_PRIVATE_CLASS(upload_stream, output_stream); struct pa_native_connection { pa_msgobject parent; @@ -176,9 +172,8 @@ struct pa_native_connection { pa_time_event *auth_timeout_event; }; -PA_DECLARE_CLASS(pa_native_connection); #define PA_NATIVE_CONNECTION(o) (pa_native_connection_cast(o)) -static PA_DEFINE_CHECK_TYPE(pa_native_connection, pa_msgobject); +PA_DEFINE_PRIVATE_CLASS(pa_native_connection, pa_msgobject); struct pa_native_protocol { PA_REFCNT_DECLARE; @@ -633,7 +628,6 @@ static record_stream* record_stream_new( record_stream *s; pa_source_output *source_output = NULL; - size_t base; pa_source_output_new_data data; pa_assert(c); @@ -653,8 +647,9 @@ static record_stream* record_stream_new( pa_source_output_new_data_set_channel_map(&data, map); if (peak_detect) data.resample_method = PA_RESAMPLER_PEAKS; + data.flags = flags; - *ret = -pa_source_output_new(&source_output, c->protocol->core, &data, flags); + *ret = -pa_source_output_new(&source_output, c->protocol->core, &data); pa_source_output_new_data_done(&data); @@ -686,7 +681,7 @@ static record_stream* record_stream_new( 0, s->buffer_attr.maxlength, 0, - base = pa_frame_size(&source_output->sample_spec), + pa_frame_size(&source_output->sample_spec), 1, 0, 0, @@ -762,6 +757,7 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, return -1; switch (code) { + case PLAYBACK_STREAM_MESSAGE_REQUEST_DATA: { pa_tagstruct *t; int l = 0; @@ -830,24 +826,26 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata)); break; - case PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH: { - pa_tagstruct *t; + case PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH: s->buffer_attr.tlength = (uint32_t) offset; - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED); - pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ - pa_tagstruct_putu32(t, s->index); - pa_tagstruct_putu32(t, s->buffer_attr.maxlength); - pa_tagstruct_putu32(t, s->buffer_attr.tlength); - pa_tagstruct_putu32(t, s->buffer_attr.prebuf); - pa_tagstruct_putu32(t, s->buffer_attr.minreq); - pa_tagstruct_put_usec(t, s->configured_sink_latency); - pa_pstream_send_tagstruct(s->connection->pstream, t); + if (s->connection->version >= 15) { + pa_tagstruct *t; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.tlength); + pa_tagstruct_putu32(t, s->buffer_attr.prebuf); + pa_tagstruct_putu32(t, s->buffer_attr.minreq); + pa_tagstruct_put_usec(t, s->configured_sink_latency); + pa_pstream_send_tagstruct(s->connection->pstream, t); + } break; - } } return 0; @@ -1004,6 +1002,7 @@ static playback_stream* playback_stream_new( pa_proplist *p, pa_bool_t adjust_latency, pa_bool_t early_requests, + pa_bool_t relative_volume, int *ret) { playback_stream *s, *ssync; @@ -1046,16 +1045,25 @@ static playback_stream* playback_stream_new( data.driver = __FILE__; data.module = c->options->module; data.client = c->client; - data.sink = sink; + if (sink) { + data.sink = sink; + data.save_sink = TRUE; + } pa_sink_input_new_data_set_sample_spec(&data, ss); pa_sink_input_new_data_set_channel_map(&data, map); - if (volume) + if (volume) { pa_sink_input_new_data_set_volume(&data, volume); - if (muted_set) + data.volume_is_absolute = !relative_volume; + data.save_volume = TRUE; + } + if (muted_set) { pa_sink_input_new_data_set_muted(&data, muted); + data.save_muted = TRUE; + } data.sync_base = ssync ? ssync->sink_input : NULL; + data.flags = flags; - *ret = -pa_sink_input_new(&sink_input, c->protocol->core, &data, flags); + *ret = -pa_sink_input_new(&sink_input, c->protocol->core, &data); pa_sink_input_new_data_done(&data); @@ -1130,6 +1138,12 @@ static void playback_stream_request_bytes(playback_stream *s) { m = pa_memblockq_pop_missing(s->memblockq); + /* pa_log("request_bytes(%lu) (tlength=%lu minreq=%lu length=%lu)", */ + /* (unsigned long) m, */ + /* pa_memblockq_get_tlength(s->memblockq), */ + /* pa_memblockq_get_minreq(s->memblockq), */ + /* pa_memblockq_get_length(s->memblockq)); */ + if (m <= 0) return; @@ -1143,7 +1157,6 @@ static void playback_stream_request_bytes(playback_stream *s) { pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); } - /* Called from main context */ static void playback_stream_send_killed(playback_stream *p) { pa_tagstruct *t; @@ -1288,7 +1301,8 @@ static void handle_seek(playback_stream *s, int64_t indexw) { pa_log_debug("Requesting rewind due to end of underrun."); pa_sink_input_request_rewind(s->sink_input, - (size_t) (s->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : s->sink_input->thread_info.underrun_for), + (size_t) (s->sink_input->thread_info.underrun_for == (uint64_t) -1 ? 0 : + s->sink_input->thread_info.underrun_for), FALSE, TRUE, FALSE); } @@ -1345,7 +1359,9 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int /* pa_log("sink input post: %lu %lli", (unsigned long) chunk->length, (long long) windex); */ if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { - pa_log_warn("Failed to push data into queue"); + + if (pa_log_ratelimit()) + pa_log_warn("Failed to push data into queue"); pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, TRUE); } @@ -1617,6 +1633,9 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { s = PLAYBACK_STREAM(i->userdata); playback_stream_assert_ref(s); + if (!dest) + return; + fix_playback_buffer_attr(s); pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); @@ -1752,6 +1771,9 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { s = RECORD_STREAM(o->userdata); record_stream_assert_ref(s); + if (!dest) + return; + fix_record_buffer_attr_pre(s); pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength); pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); @@ -1825,7 +1847,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u early_requests = FALSE, dont_inhibit_auto_suspend = FALSE, muted_set = FALSE, - fail_on_suspend = FALSE; + fail_on_suspend = FALSE, + relative_volume = FALSE; pa_sink_input_flags_t flags = 0; pa_proplist *p; pa_bool_t volume_set = TRUE; @@ -1856,7 +1879,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name(sink_name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); @@ -1918,6 +1941,15 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u } } + if (c->version >= 17) { + + if (pa_tagstruct_get_boolean(t, &relative_volume) < 0) { + protocol_error(c); + pa_proplist_free(p); + return; + } + } + if (!pa_tagstruct_eof(t)) { protocol_error(c); pa_proplist_free(p); @@ -1951,13 +1983,13 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u (no_move ? PA_SINK_INPUT_DONT_MOVE : 0) | (variable_rate ? PA_SINK_INPUT_VARIABLE_RATE : 0) | (dont_inhibit_auto_suspend ? PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | - (fail_on_suspend ? PA_SINK_INPUT_FAIL_ON_SUSPEND : 0); + (fail_on_suspend ? PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND : 0); /* Only since protocol version 15 there's a seperate muted_set * flag. For older versions we synthesize it here */ muted_set = muted_set || muted; - s = playback_stream_new(c, sink, &ss, &map, &attr, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, &ret); + s = playback_stream_new(c, sink, &ss, &map, &attr, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, relative_volume, &ret); pa_proplist_free(p); CHECK_VALIDITY(c->pstream, s, tag, ret); @@ -2105,7 +2137,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !source_name || pa_namereg_is_valid_name(source_name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !source_name || pa_namereg_is_valid_name_or_wildcard(source_name, PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); @@ -2207,7 +2239,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin (no_move ? PA_SOURCE_OUTPUT_DONT_MOVE : 0) | (variable_rate ? PA_SOURCE_OUTPUT_VARIABLE_RATE : 0) | (dont_inhibit_auto_suspend ? PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | - (fail_on_suspend ? PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND : 0); + (fail_on_suspend ? PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND|PA_SOURCE_OUTPUT_KILL_ON_SUSPEND : 0); s = record_stream_new(c, source, &ss, &map, peak_detect, &attr, flags, p, adjust_latency, direct_on_input, early_requests, &ret); pa_proplist_free(p); @@ -2262,6 +2294,8 @@ static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta ret = pa_core_exit(c->protocol->core, FALSE, 0); CHECK_VALIDITY(c->pstream, ret >= 0, tag, PA_ERR_ACCESS); + pa_log_debug("Client %s asks us to terminate.", pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY))); + pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */ } @@ -2451,7 +2485,7 @@ static void command_lookup(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_LOOKUP_SINK ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); if (command == PA_COMMAND_LOOKUP_SINK) { pa_sink *sink; @@ -2722,7 +2756,7 @@ static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag return; } - CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name(sink_name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); @@ -2826,7 +2860,7 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin PA_TAG_SAMPLE_SPEC, &fixed_ss, PA_TAG_CHANNEL_MAP, &sink->channel_map, PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX, - PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE, FALSE), + PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE), PA_TAG_BOOLEAN, pa_sink_get_mute(sink, FALSE), PA_TAG_U32, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL, @@ -3096,7 +3130,12 @@ static void command_get_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, p } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || + (command == PA_COMMAND_GET_SINK_INFO && + pa_namereg_is_valid_name_or_wildcard(name, PA_NAMEREG_SINK)) || + (command == PA_COMMAND_GET_SOURCE_INFO && + pa_namereg_is_valid_name_or_wildcard(name, PA_NAMEREG_SOURCE)) || + pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !name || idx == PA_INVALID_INDEX, tag, PA_ERR_INVALID); @@ -3323,6 +3362,7 @@ static void command_set_volume( pa_source *source = NULL; pa_sink_input *si = NULL; const char *name = NULL; + const char *client_name; pa_native_connection_assert_ref(c); pa_assert(t); @@ -3337,7 +3377,7 @@ static void command_set_volume( } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_VOLUME ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !name || idx == PA_INVALID_INDEX, tag, PA_ERR_INVALID); @@ -3369,12 +3409,26 @@ static void command_set_volume( CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY); - if (sink) - pa_sink_set_volume(sink, &volume, TRUE, TRUE, TRUE, TRUE); - else if (source) + client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + + if (sink) { + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &sink->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of sink %s.", client_name, sink->name); + pa_sink_set_volume(sink, &volume, TRUE, TRUE); + } else if (source) { + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &source->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of source %s.", client_name, source->name); pa_source_set_volume(source, &volume, TRUE); - else if (si) + } else if (si) { + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &si->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of sink input %s.", + client_name, + pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME))); pa_sink_input_set_volume(si, &volume, TRUE, TRUE); + } pa_pstream_send_simple_ack(c->pstream, tag); } @@ -3392,7 +3446,7 @@ static void command_set_mute( pa_sink *sink = NULL; pa_source *source = NULL; pa_sink_input *si = NULL; - const char *name = NULL; + const char *name = NULL, *client_name; pa_native_connection_assert_ref(c); pa_assert(t); @@ -3407,7 +3461,7 @@ static void command_set_mute( } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_MUTE ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !name || idx == PA_INVALID_INDEX, tag, PA_ERR_INVALID); @@ -3415,7 +3469,6 @@ static void command_set_mute( switch (command) { case PA_COMMAND_SET_SINK_MUTE: - if (idx != PA_INVALID_INDEX) sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); else @@ -3441,12 +3494,20 @@ static void command_set_mute( CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY); - if (sink) + client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + + if (sink) { + pa_log_debug("Client %s changes mute of sink %s.", client_name, sink->name); pa_sink_set_mute(sink, mute, TRUE); - else if (source) + } else if (source) { + pa_log_debug("Client %s changes mute of source %s.", client_name, source->name); pa_source_set_mute(source, mute, TRUE); - else if (si) + } else if (si) { + pa_log_debug("Client %s changes mute of sink input %s.", + client_name, + pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME))); pa_sink_input_set_mute(si, mute, TRUE); + } pa_pstream_send_simple_ack(c->pstream, tag); } @@ -4065,7 +4126,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); - CHECK_VALIDITY(c->pstream, !name_device || pa_namereg_is_valid_name(name_device), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name_device || pa_namereg_is_valid_name_or_wildcard(name_device, command == PA_COMMAND_MOVE_SINK_INPUT ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx_device != PA_INVALID_INDEX || name_device, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx_device == PA_INVALID_INDEX || !name_device, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !name_device || idx_device == PA_INVALID_INDEX, tag, PA_ERR_INVALID); @@ -4129,7 +4190,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name) || *name == 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SUSPEND_SINK ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE) || *name == 0, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !name || idx == PA_INVALID_INDEX, tag, PA_ERR_INVALID); @@ -4289,7 +4350,7 @@ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_PORT ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, !name || idx == PA_INVALID_INDEX, tag, PA_ERR_INVALID); @@ -4824,3 +4885,9 @@ pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c) { return c->pstream; } + +pa_client* pa_native_connection_get_client(pa_native_connection *c) { + pa_native_connection_assert_ref(c); + + return c->client; +} diff --git a/src/pulsecore/protocol-native.h b/src/pulsecore/protocol-native.h index 8a8d601c..97126274 100644 --- a/src/pulsecore/protocol-native.h +++ b/src/pulsecore/protocol-native.h @@ -47,7 +47,6 @@ typedef struct pa_native_options { char *auth_group; pa_ip_acl *auth_ip_acl; pa_auth_cookie *auth_cookie; - } pa_native_options; typedef enum pa_native_hook { @@ -80,6 +79,7 @@ int pa_native_protocol_install_ext(pa_native_protocol *p, pa_module *m, pa_nativ void pa_native_protocol_remove_ext(pa_native_protocol *p, pa_module *m); pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c); +pa_client* pa_native_connection_get_client(pa_native_connection *c); pa_native_options* pa_native_options_new(void); pa_native_options* pa_native_options_ref(pa_native_options *o); diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index 776d74b6..a9f73896 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -69,9 +69,8 @@ typedef struct connection { } playback; } connection; -PA_DECLARE_CLASS(connection); +PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject); #define CONNECTION(o) (connection_cast(o)) -static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject); struct pa_simple_protocol { PA_REFCNT_DECLARE; @@ -155,7 +154,7 @@ static int do_read(connection *c) { ssize_t r; size_t l; void *p; - size_t space; + size_t space = 0; connection_assert_ref(c); @@ -542,7 +541,7 @@ void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simp pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); pa_sink_input_new_data_set_sample_spec(&data, &o->sample_spec); - pa_sink_input_new(&c->sink_input, p->core, &data, 0); + pa_sink_input_new(&c->sink_input, p->core, &data); pa_sink_input_new_data_done(&data); if (!c->sink_input) { @@ -594,7 +593,7 @@ void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simp pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); pa_source_output_new_data_set_sample_spec(&data, &o->sample_spec); - pa_source_output_new(&c->source_output, p->core, &data, 0); + pa_source_output_new(&c->source_output, p->core, &data); pa_source_output_new_data_done(&data); if (!c->source_output) { @@ -628,8 +627,7 @@ void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simp return; fail: - if (c) - connection_unlink(c); + connection_unlink(c); } void pa_simple_protocol_disconnect(pa_simple_protocol *p, pa_module *m) { diff --git a/src/pulsecore/random.c b/src/pulsecore/random.c index 518c281a..a87d24e3 100644 --- a/src/pulsecore/random.c +++ b/src/pulsecore/random.c @@ -62,11 +62,7 @@ static int random_proper(void *ret_data, size_t length) { while (*device) { ret = 0; - if ((fd = open(*device, O_RDONLY -#ifdef O_NOCTTY - | O_NOCTTY -#endif - )) >= 0) { + if ((fd = pa_open_cloexec(*device, O_RDONLY, 0)) >= 0) { if ((r = pa_loop_read(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) ret = -1; diff --git a/src/pulsecore/ratelimit.h b/src/pulsecore/ratelimit.h index ec3b5a38..9857a291 100644 --- a/src/pulsecore/ratelimit.h +++ b/src/pulsecore/ratelimit.h @@ -26,21 +26,31 @@ #include <pulsecore/macro.h> typedef struct pa_ratelimit { - const pa_usec_t interval; - const unsigned burst; + pa_usec_t interval; + unsigned burst; unsigned n_printed, n_missed; pa_usec_t begin; } pa_ratelimit; #define PA_DEFINE_RATELIMIT(_name, _interval, _burst) \ pa_ratelimit _name = { \ - .interval = _interval, \ - .burst = _burst, \ + .interval = (_interval), \ + .burst = (_burst), \ .n_printed = 0, \ .n_missed = 0, \ .begin = 0 \ } +#define PA_INIT_RATELIMIT(v, _interval, _burst) \ + do { \ + pa_ratelimit *r = &(v); \ + r->interval = (_interval); \ + r->burst = (_burst); \ + r->n_printed = 0; \ + r->n_missed = 0; \ + r->begin = 0; \ + } while (FALSE); + pa_bool_t pa_ratelimit_test(pa_ratelimit *r); #endif diff --git a/src/pulsecore/remap.c b/src/pulsecore/remap.c new file mode 100644 index 00000000..a0fc85b9 --- /dev/null +++ b/src/pulsecore/remap.c @@ -0,0 +1,204 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "remap.h" + +static void remap_mono_to_stereo_c (pa_remap_t *m, void *dst, const void *src, unsigned n) { + unsigned i; + + switch (*m->format) { + case PA_SAMPLE_FLOAT32NE: + { + float *d, *s; + + d = (float *) dst; + s = (float *) src; + + for (i = n >> 2; i; i--) { + d[0] = d[1] = s[0]; + d[2] = d[3] = s[1]; + d[4] = d[5] = s[2]; + d[6] = d[7] = s[3]; + s += 4; + d += 8; + } + for (i = n & 3; i; i--) { + d[0] = d[1] = s[0]; + s++; + d += 2; + } + break; + } + case PA_SAMPLE_S16NE: + { + int16_t *d, *s; + + d = (int16_t *) dst; + s = (int16_t *) src; + + for (i = n >> 2; i; i--) { + d[0] = d[1] = s[0]; + d[2] = d[3] = s[1]; + d[4] = d[5] = s[2]; + d[6] = d[7] = s[3]; + s += 4; + d += 8; + } + for (i = n & 3; i; i--) { + d[0] = d[1] = s[0]; + s++; + d += 2; + } + break; + } + default: + pa_assert_not_reached(); + } +} + +static void remap_channels_matrix_c (pa_remap_t *m, void *dst, const void *src, unsigned n) { + unsigned oc, ic, i; + unsigned n_ic, n_oc; + + n_ic = m->i_ss->channels; + n_oc = m->o_ss->channels; + + switch (*m->format) { + case PA_SAMPLE_FLOAT32NE: + { + float *d, *s; + + memset(dst, 0, n * sizeof (float) * n_oc); + + for (oc = 0; oc < n_oc; oc++) { + + for (ic = 0; ic < n_ic; ic++) { + float vol; + + vol = m->map_table_f[oc][ic]; + + if (vol <= 0.0) + continue; + + d = (float *)dst + oc; + s = (float *)src + ic; + + if (vol >= 1.0) { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s; + } else { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s * vol; + } + } + } + + break; + } + case PA_SAMPLE_S16NE: + { + int16_t *d, *s; + + memset(dst, 0, n * sizeof (int16_t) * n_oc); + + for (oc = 0; oc < n_oc; oc++) { + + for (ic = 0; ic < n_ic; ic++) { + int32_t vol; + + vol = m->map_table_i[oc][ic]; + + if (vol <= 0) + continue; + + d = (int16_t *)dst + oc; + s = (int16_t *)src + ic; + + if (vol >= 0x10000) { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s; + } else { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += (int16_t) (((int32_t)*s * vol) >> 16); + } + } + } + break; + } + default: + pa_assert_not_reached(); + } +} + +/* set the function that will execute the remapping based on the matrices */ +static void init_remap_c (pa_remap_t *m) { + unsigned n_oc, n_ic; + + n_oc = m->o_ss->channels; + n_ic = m->i_ss->channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_f[0][0] >= 1.0 && m->map_table_f[1][0] >= 1.0) { + m->do_remap = (pa_do_remap_func_t) remap_mono_to_stereo_c; + pa_log_info("Using mono to stereo remapping"); + } else { + m->do_remap = (pa_do_remap_func_t) remap_channels_matrix_c; + pa_log_info("Using generic matrix remapping"); + } +} + + +/* default C implementation */ +static pa_init_remap_func_t remap_func = init_remap_c; + +void pa_init_remap (pa_remap_t *m) { + pa_assert (remap_func); + + m->do_remap = NULL; + + /* call the installed remap init function */ + remap_func (m); + + if (m->do_remap == NULL) { + /* nothing was installed, fallback to C version */ + init_remap_c (m); + } +} + +pa_init_remap_func_t pa_get_init_remap_func(void) { + return remap_func; +} + +void pa_set_init_remap_func(pa_init_remap_func_t func) { + remap_func = func; +} diff --git a/src/pulsecore/remap.h b/src/pulsecore/remap.h new file mode 100644 index 00000000..32a67cdd --- /dev/null +++ b/src/pulsecore/remap.h @@ -0,0 +1,48 @@ +#ifndef fooremapfoo +#define fooremapfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/sample.h> + +typedef struct pa_remap pa_remap_t; + +typedef void (*pa_do_remap_func_t) (pa_remap_t *m, void *d, const void *s, unsigned n); + +struct pa_remap { + pa_sample_format_t *format; + pa_sample_spec *i_ss, *o_ss; + float map_table_f[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; + int32_t map_table_i[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; + pa_do_remap_func_t do_remap; +}; + +void pa_init_remap (pa_remap_t *m); + +/* custom installation of init functions */ +typedef void (*pa_init_remap_func_t) (pa_remap_t *m); + +pa_init_remap_func_t pa_get_init_remap_func(void); +void pa_set_init_remap_func(pa_init_remap_func_t func); + +#endif /* fooremapfoo */ diff --git a/src/pulsecore/remap_mmx.c b/src/pulsecore/remap_mmx.c new file mode 100644 index 00000000..d358a58b --- /dev/null +++ b/src/pulsecore/remap_mmx.c @@ -0,0 +1,161 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu-x86.h" +#include "remap.h" + +#define LOAD_SAMPLES \ + " movq (%1), %%mm0 \n\t" \ + " movq 8(%1), %%mm2 \n\t" \ + " movq 16(%1), %%mm4 \n\t" \ + " movq 24(%1), %%mm6 \n\t" \ + " movq %%mm0, %%mm1 \n\t" \ + " movq %%mm2, %%mm3 \n\t" \ + " movq %%mm4, %%mm5 \n\t" \ + " movq %%mm6, %%mm7 \n\t" + +#define UNPACK_SAMPLES(s) \ + " punpckl"#s" %%mm0, %%mm0 \n\t" \ + " punpckh"#s" %%mm1, %%mm1 \n\t" \ + " punpckl"#s" %%mm2, %%mm2 \n\t" \ + " punpckh"#s" %%mm3, %%mm3 \n\t" \ + " punpckl"#s" %%mm4, %%mm4 \n\t" \ + " punpckh"#s" %%mm5, %%mm5 \n\t" \ + " punpckl"#s" %%mm6, %%mm6 \n\t" \ + " punpckh"#s" %%mm7, %%mm7 \n\t" + +#define STORE_SAMPLES \ + " movq %%mm0, (%0) \n\t" \ + " movq %%mm1, 8(%0) \n\t" \ + " movq %%mm2, 16(%0) \n\t" \ + " movq %%mm3, 24(%0) \n\t" \ + " movq %%mm4, 32(%0) \n\t" \ + " movq %%mm5, 40(%0) \n\t" \ + " movq %%mm6, 48(%0) \n\t" \ + " movq %%mm7, 56(%0) \n\t" \ + " add $32, %1 \n\t" \ + " add $64, %0 \n\t" + +#define HANDLE_SINGLE_dq() \ + " movd (%1), %%mm0 \n\t" \ + " punpckldq %%mm0, %%mm0 \n\t" \ + " movq %%mm0, (%0) \n\t" \ + " add $4, %1 \n\t" \ + " add $8, %0 \n\t" + +#define HANDLE_SINGLE_wd() \ + " movw (%1), %w3 \n\t" \ + " movd %3, %%mm0 \n\t" \ + " punpcklwd %%mm0, %%mm0 \n\t" \ + " movd %%mm0, (%0) \n\t" \ + " add $2, %1 \n\t" \ + " add $4, %0 \n\t" + +#define MONO_TO_STEREO(s,shift,mask) \ + " mov %4, %2 \n\t" \ + " sar $"#shift", %2 \n\t" \ + " cmp $0, %2 \n\t" \ + " je 2f \n\t" \ + "1: \n\t" \ + LOAD_SAMPLES \ + UNPACK_SAMPLES(s) \ + STORE_SAMPLES \ + " dec %2 \n\t" \ + " jne 1b \n\t" \ + "2: \n\t" \ + " mov %4, %2 \n\t" \ + " and $"#mask", %2 \n\t" \ + " je 4f \n\t" \ + "3: \n\t" \ + HANDLE_SINGLE_##s() \ + " dec %2 \n\t" \ + " jne 3b \n\t" \ + "4: \n\t" \ + " emms \n\t" + +#if defined (__i386__) || defined (__amd64__) +static void remap_mono_to_stereo_mmx (pa_remap_t *m, void *dst, const void *src, unsigned n) { + pa_reg_x86 temp, temp2; + + switch (*m->format) { + case PA_SAMPLE_FLOAT32NE: + { + __asm__ __volatile__ ( + MONO_TO_STEREO(dq,3,7) /* do doubles to quads */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); + break; + } + case PA_SAMPLE_S16NE: + { + __asm__ __volatile__ ( + MONO_TO_STEREO(wd,4,15) /* do words to doubles */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); + break; + } + default: + pa_assert_not_reached(); + } +} + +/* set the function that will execute the remapping based on the matrices */ +static void init_remap_mmx (pa_remap_t *m) { + unsigned n_oc, n_ic; + + n_oc = m->o_ss->channels; + n_ic = m->i_ss->channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_f[0][0] >= 1.0 && m->map_table_f[1][0] >= 1.0) { + m->do_remap = (pa_do_remap_func_t) remap_mono_to_stereo_mmx; + pa_log_info("Using MMX mono to stereo remapping"); + } +} +#endif /* defined (__i386__) || defined (__amd64__) */ + +void pa_remap_func_init_mmx (pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + + if (flags & PA_CPU_X86_MMX) { + pa_log_info("Initialising MMX optimized remappers."); + + pa_set_init_remap_func ((pa_init_remap_func_t) init_remap_mmx); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/remap_sse.c b/src/pulsecore/remap_sse.c new file mode 100644 index 00000000..0ccf3161 --- /dev/null +++ b/src/pulsecore/remap_sse.c @@ -0,0 +1,159 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu-x86.h" +#include "remap.h" + +#define LOAD_SAMPLES \ + " movdqu (%1), %%xmm0 \n\t" \ + " movdqu 16(%1), %%xmm2 \n\t" \ + " movdqu 32(%1), %%xmm4 \n\t" \ + " movdqu 48(%1), %%xmm6 \n\t" \ + " movdqa %%xmm0, %%xmm1 \n\t" \ + " movdqa %%xmm2, %%xmm3 \n\t" \ + " movdqa %%xmm4, %%xmm5 \n\t" \ + " movdqa %%xmm6, %%xmm7 \n\t" + +#define UNPACK_SAMPLES(s) \ + " punpckl"#s" %%xmm0, %%xmm0 \n\t" \ + " punpckh"#s" %%xmm1, %%xmm1 \n\t" \ + " punpckl"#s" %%xmm2, %%xmm2 \n\t" \ + " punpckh"#s" %%xmm3, %%xmm3 \n\t" \ + " punpckl"#s" %%xmm4, %%xmm4 \n\t" \ + " punpckh"#s" %%xmm5, %%xmm5 \n\t" \ + " punpckl"#s" %%xmm6, %%xmm6 \n\t" \ + " punpckh"#s" %%xmm7, %%xmm7 \n\t" + +#define STORE_SAMPLES \ + " movdqu %%xmm0, (%0) \n\t" \ + " movdqu %%xmm1, 16(%0) \n\t" \ + " movdqu %%xmm2, 32(%0) \n\t" \ + " movdqu %%xmm3, 48(%0) \n\t" \ + " movdqu %%xmm4, 64(%0) \n\t" \ + " movdqu %%xmm5, 80(%0) \n\t" \ + " movdqu %%xmm6, 96(%0) \n\t" \ + " movdqu %%xmm7, 112(%0) \n\t" \ + " add $64, %1 \n\t" \ + " add $128, %0 \n\t" + +#define HANDLE_SINGLE_dq() \ + " movd (%1), %%xmm0 \n\t" \ + " punpckldq %%xmm0, %%xmm0 \n\t" \ + " movq %%xmm0, (%0) \n\t" \ + " add $4, %1 \n\t" \ + " add $8, %0 \n\t" + +#define HANDLE_SINGLE_wd() \ + " movw (%1), %w3 \n\t" \ + " movd %3, %%xmm0 \n\t" \ + " punpcklwd %%xmm0, %%xmm0 \n\t" \ + " movd %%xmm0, (%0) \n\t" \ + " add $2, %1 \n\t" \ + " add $4, %0 \n\t" + +#define MONO_TO_STEREO(s,shift,mask) \ + " mov %4, %2 \n\t" \ + " sar $"#shift", %2 \n\t" \ + " cmp $0, %2 \n\t" \ + " je 2f \n\t" \ + "1: \n\t" \ + LOAD_SAMPLES \ + UNPACK_SAMPLES(s) \ + STORE_SAMPLES \ + " dec %2 \n\t" \ + " jne 1b \n\t" \ + "2: \n\t" \ + " mov %4, %2 \n\t" \ + " and $"#mask", %2 \n\t" \ + " je 4f \n\t" \ + "3: \n\t" \ + HANDLE_SINGLE_##s() \ + " dec %2 \n\t" \ + " jne 3b \n\t" \ + "4: \n\t" + +#if defined (__i386__) || defined (__amd64__) +static void remap_mono_to_stereo_sse2 (pa_remap_t *m, void *dst, const void *src, unsigned n) { + pa_reg_x86 temp, temp2; + + switch (*m->format) { + case PA_SAMPLE_FLOAT32NE: + { + __asm__ __volatile__ ( + MONO_TO_STEREO(dq, 4, 15) /* do doubles to quads */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); + break; + } + case PA_SAMPLE_S16NE: + { + __asm__ __volatile__ ( + MONO_TO_STEREO(wd, 5, 31) /* do words to doubles */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); + break; + } + default: + pa_assert_not_reached(); + } +} + +/* set the function that will execute the remapping based on the matrices */ +static void init_remap_sse2 (pa_remap_t *m) { + unsigned n_oc, n_ic; + + n_oc = m->o_ss->channels; + n_ic = m->i_ss->channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_f[0][0] >= 1.0 && m->map_table_f[1][0] >= 1.0) { + m->do_remap = (pa_do_remap_func_t) remap_mono_to_stereo_sse2; + pa_log_info("Using SSE mono to stereo remapping"); + } +} +#endif /* defined (__i386__) || defined (__amd64__) */ + +void pa_remap_func_init_sse (pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + + if (flags & PA_CPU_X86_SSE2) { + pa_log_info("Initialising SSE2 optimized remappers."); + pa_set_init_remap_func ((pa_init_remap_func_t) init_remap_sse2); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c index 17fb8480..bed5a20d 100644 --- a/src/pulsecore/resampler.c +++ b/src/pulsecore/resampler.c @@ -31,9 +31,6 @@ #include <speex/speex_resampler.h> -#include <liboil/liboilfuncs.h> -#include <liboil/liboil.h> - #include <pulse/xmalloc.h> #include <pulsecore/sconv.h> #include <pulsecore/log.h> @@ -43,6 +40,7 @@ #include "ffmpeg/avcodec.h" #include "resampler.h" +#include "remap.h" /* Number of samples of extra space we allow the resamplers to return */ #define EXTRA_FRAMES 128 @@ -64,7 +62,7 @@ struct pa_resampler { pa_convert_func_t to_work_format_func; pa_convert_func_t from_work_format_func; - float map_table[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; + pa_remap_t remap; pa_bool_t map_required; void (*impl_free)(pa_resampler *r); @@ -214,6 +212,11 @@ pa_resampler* pa_resampler_new( r->i_ss = *a; r->o_ss = *b; + /* set up the remap structure */ + r->remap.i_ss = &r->i_ss; + r->remap.o_ss = &r->o_ss; + r->remap.format = &r->work_format; + if (am) r->i_cm = *am; else if (!pa_channel_map_init_auto(&r->i_cm, r->i_ss.channels, PA_CHANNEL_MAP_DEFAULT)) @@ -296,8 +299,7 @@ pa_resampler* pa_resampler_new( return r; fail: - if (r) - pa_xfree(r); + pa_xfree(r); return NULL; } @@ -347,13 +349,17 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) { size_t pa_resampler_request(pa_resampler *r, size_t out_length) { pa_assert(r); - return (((out_length / r->o_fz)*r->i_ss.rate)/r->o_ss.rate) * r->i_fz; + /* Let's round up here */ + + return (((((out_length + r->o_fz-1) / r->o_fz) * r->i_ss.rate) + r->o_ss.rate-1) / r->o_ss.rate) * r->i_fz; } size_t pa_resampler_result(pa_resampler *r, size_t in_length) { pa_assert(r); - return (((in_length / r->i_fz)*r->o_ss.rate)/r->i_ss.rate) * r->o_fz; + /* Let's round up here */ + + return (((((in_length + r->i_fz-1) / r->i_fz) * r->o_ss.rate) + r->i_ss.rate-1) / r->i_ss.rate) * r->o_fz; } size_t pa_resampler_max_block_size(pa_resampler *r) { @@ -576,32 +582,41 @@ static int front_rear_side(pa_channel_position_t p) { static void calc_map_table(pa_resampler *r) { unsigned oc, ic; + unsigned n_oc, n_ic; pa_bool_t ic_connected[PA_CHANNELS_MAX]; pa_bool_t remix; pa_strbuf *s; char *t; + pa_remap_t *m; pa_assert(r); if (!(r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) && !pa_channel_map_equal(&r->i_cm, &r->o_cm))))) return; - memset(r->map_table, 0, sizeof(r->map_table)); + m = &r->remap; + + n_oc = r->o_ss.channels; + n_ic = r->i_ss.channels; + + memset(m->map_table_f, 0, sizeof(m->map_table_f)); + memset(m->map_table_i, 0, sizeof(m->map_table_i)); + memset(ic_connected, 0, sizeof(ic_connected)); remix = (r->flags & (PA_RESAMPLER_NO_REMAP|PA_RESAMPLER_NO_REMIX)) == 0; - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { pa_bool_t oc_connected = FALSE; pa_channel_position_t b = r->o_cm.map[oc]; - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { pa_channel_position_t a = r->i_cm.map[ic]; if (r->flags & PA_RESAMPLER_NO_REMAP) { /* We shall not do any remapping. Hence, just check by index */ if (ic == oc) - r->map_table[oc][ic] = 1.0; + m->map_table_f[oc][ic] = 1.0; continue; } @@ -610,7 +625,7 @@ static void calc_map_table(pa_resampler *r) { /* We shall not do any remixing. Hence, just check by name */ if (a == b) - r->map_table[oc][ic] = 1.0; + m->map_table_f[oc][ic] = 1.0; continue; } @@ -685,7 +700,7 @@ static void calc_map_table(pa_resampler *r) { */ if (a == b || a == PA_CHANNEL_POSITION_MONO || b == PA_CHANNEL_POSITION_MONO) { - r->map_table[oc][ic] = 1.0; + m->map_table_f[oc][ic] = 1.0; oc_connected = TRUE; ic_connected[ic] = TRUE; @@ -703,14 +718,14 @@ static void calc_map_table(pa_resampler *r) { /* We are not connected and on the left side, let's * average all left side input channels. */ - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_left(r->i_cm.map[ic])) n++; if (n > 0) - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_left(r->i_cm.map[ic])) { - r->map_table[oc][ic] = 1.0f / (float) n; + m->map_table_f[oc][ic] = 1.0f / (float) n; ic_connected[ic] = TRUE; } @@ -724,14 +739,14 @@ static void calc_map_table(pa_resampler *r) { /* We are not connected and on the right side, let's * average all right side input channels. */ - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_right(r->i_cm.map[ic])) n++; if (n > 0) - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_right(r->i_cm.map[ic])) { - r->map_table[oc][ic] = 1.0f / (float) n; + m->map_table_f[oc][ic] = 1.0f / (float) n; ic_connected[ic] = TRUE; } @@ -745,14 +760,14 @@ static void calc_map_table(pa_resampler *r) { /* We are not connected and at the center. Let's * average all center input channels. */ - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_center(r->i_cm.map[ic])) n++; if (n > 0) { - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_center(r->i_cm.map[ic])) { - r->map_table[oc][ic] = 1.0f / (float) n; + m->map_table_f[oc][ic] = 1.0f / (float) n; ic_connected[ic] = TRUE; } } else { @@ -762,14 +777,14 @@ static void calc_map_table(pa_resampler *r) { n = 0; - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) n++; if (n > 0) - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) { - r->map_table[oc][ic] = 1.0f / (float) n; + m->map_table_f[oc][ic] = 1.0f / (float) n; ic_connected[ic] = TRUE; } @@ -783,12 +798,12 @@ static void calc_map_table(pa_resampler *r) { /* We are not connected and an LFE. Let's average all * channels for LFE. */ - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (!(r->flags & PA_RESAMPLER_NO_LFE)) - r->map_table[oc][ic] = 1.0f / (float) r->i_ss.channels; + m->map_table_f[oc][ic] = 1.0f / (float) n_ic; else - r->map_table[oc][ic] = 0; + m->map_table_f[oc][ic] = 0; /* Please note that a channel connected to LFE * doesn't really count as connected. */ @@ -804,7 +819,7 @@ static void calc_map_table(pa_resampler *r) { ic_unconnected_center = 0, ic_unconnected_lfe = 0; - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { pa_channel_position_t a = r->i_cm.map[ic]; if (ic_connected[ic]) @@ -827,20 +842,20 @@ static void calc_map_table(pa_resampler *r) { * the left side by .9 and add in our averaged unconnected * channels multplied by .1 */ - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { if (!on_left(r->o_cm.map[oc])) continue; - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (ic_connected[ic]) { - r->map_table[oc][ic] *= .9f; + m->map_table_f[oc][ic] *= .9f; continue; } if (on_left(r->i_cm.map[ic])) - r->map_table[oc][ic] = .1f / (float) ic_unconnected_left; + m->map_table_f[oc][ic] = .1f / (float) ic_unconnected_left; } } } @@ -852,20 +867,20 @@ static void calc_map_table(pa_resampler *r) { * the right side by .9 and add in our averaged unconnected * channels multplied by .1 */ - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { if (!on_right(r->o_cm.map[oc])) continue; - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (ic_connected[ic]) { - r->map_table[oc][ic] *= .9f; + m->map_table_f[oc][ic] *= .9f; continue; } if (on_right(r->i_cm.map[ic])) - r->map_table[oc][ic] = .1f / (float) ic_unconnected_right; + m->map_table_f[oc][ic] = .1f / (float) ic_unconnected_right; } } } @@ -878,20 +893,20 @@ static void calc_map_table(pa_resampler *r) { * the center side by .9 and add in our averaged unconnected * channels multplied by .1 */ - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { if (!on_center(r->o_cm.map[oc])) continue; - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (ic_connected[ic]) { - r->map_table[oc][ic] *= .9f; + m->map_table_f[oc][ic] *= .9f; continue; } if (on_center(r->i_cm.map[ic])) { - r->map_table[oc][ic] = .1f / (float) ic_unconnected_center; + m->map_table_f[oc][ic] = .1f / (float) ic_unconnected_center; mixed_in = TRUE; } } @@ -909,7 +924,7 @@ static void calc_map_table(pa_resampler *r) { it into left and right. Using .375 and 0.75 as factors. */ - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (ic_connected[ic]) continue; @@ -917,7 +932,7 @@ static void calc_map_table(pa_resampler *r) { if (!on_center(r->i_cm.map[ic])) continue; - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) continue; @@ -928,7 +943,7 @@ static void calc_map_table(pa_resampler *r) { } } - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) continue; @@ -938,7 +953,7 @@ static void calc_map_table(pa_resampler *r) { } } - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) continue; @@ -946,10 +961,10 @@ static void calc_map_table(pa_resampler *r) { if (ncenter[oc] <= 0) continue; - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (ic_connected[ic]) { - r->map_table[oc][ic] *= .75f; + m->map_table_f[oc][ic] *= .75f; continue; } @@ -957,7 +972,7 @@ static void calc_map_table(pa_resampler *r) { continue; if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc])) - r->map_table[oc][ic] = .375f / (float) ncenter[oc]; + m->map_table_f[oc][ic] = .375f / (float) ncenter[oc]; } } } @@ -968,40 +983,46 @@ static void calc_map_table(pa_resampler *r) { /* OK, so there is an unconnected LFE channel. Let's mix * it into all channels, with factor 0.375 */ - for (ic = 0; ic < r->i_ss.channels; ic++) { + for (ic = 0; ic < n_ic; ic++) { if (!on_lfe(r->i_cm.map[ic])) continue; - for (oc = 0; oc < r->o_ss.channels; oc++) - r->map_table[oc][ic] = 0.375f / (float) ic_unconnected_lfe; + for (oc = 0; oc < n_oc; oc++) + m->map_table_f[oc][ic] = 0.375f / (float) ic_unconnected_lfe; } } } - + /* make an 16:16 int version of the matrix */ + for (oc = 0; oc < n_oc; oc++) + for (ic = 0; ic < n_ic; ic++) + m->map_table_i[oc][ic] = (int32_t) (m->map_table_f[oc][ic] * 0x10000); s = pa_strbuf_new(); pa_strbuf_printf(s, " "); - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) pa_strbuf_printf(s, " I%02u ", ic); pa_strbuf_puts(s, "\n +"); - for (ic = 0; ic < r->i_ss.channels; ic++) + for (ic = 0; ic < n_ic; ic++) pa_strbuf_printf(s, "------"); pa_strbuf_puts(s, "\n"); - for (oc = 0; oc < r->o_ss.channels; oc++) { + for (oc = 0; oc < n_oc; oc++) { pa_strbuf_printf(s, "O%02u |", oc); - for (ic = 0; ic < r->i_ss.channels; ic++) - pa_strbuf_printf(s, " %1.3f", r->map_table[oc][ic]); + for (ic = 0; ic < n_ic; ic++) + pa_strbuf_printf(s, " %1.3f", m->map_table_f[oc][ic]); pa_strbuf_puts(s, "\n"); } pa_log_debug("Channel matrix:\n%s", t = pa_strbuf_tostring_free(s)); pa_xfree(t); + + /* initialize the remapping function */ + pa_init_remap (m); } static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) { @@ -1041,41 +1062,10 @@ static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) return &r->buf1; } -static void vectoradd_s16_with_fraction( - int16_t *d, int dstr, - const int16_t *s1, int sstr1, - const int16_t *s2, int sstr2, - int n, - float s3, float s4) { - - int32_t i3, i4; - - i3 = (int32_t) (s3 * 0x10000); - i4 = (int32_t) (s4 * 0x10000); - - for (; n > 0; n--) { - int32_t a, b; - - a = *s1; - b = *s2; - - a = (a * i3) / 0x10000; - b = (b * i4) / 0x10000; - - *d = (int16_t) (a + b); - - s1 = (const int16_t*) ((const uint8_t*) s1 + sstr1); - s2 = (const int16_t*) ((const uint8_t*) s2 + sstr2); - d = (int16_t*) ((uint8_t*) d + dstr); - - } -} - static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) { unsigned in_n_samples, out_n_samples, n_frames; - int i_skip, o_skip; - unsigned oc; void *src, *dst; + pa_remap_t *remap; pa_assert(r); pa_assert(input); @@ -1104,76 +1094,14 @@ static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) { src = ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); dst = pa_memblock_acquire(r->buf2.memblock); - memset(dst, 0, r->buf2.length); - - o_skip = (int) (r->w_sz * r->o_ss.channels); - i_skip = (int) (r->w_sz * r->i_ss.channels); - - switch (r->work_format) { - case PA_SAMPLE_FLOAT32NE: - - for (oc = 0; oc < r->o_ss.channels; oc++) { - unsigned ic; - static const float one = 1.0; + remap = &r->remap; - for (ic = 0; ic < r->i_ss.channels; ic++) { - - if (r->map_table[oc][ic] <= 0.0) - continue; - - oil_vectoradd_f32( - (float*) dst + oc, o_skip, - (float*) dst + oc, o_skip, - (float*) src + ic, i_skip, - (int) n_frames, - &one, &r->map_table[oc][ic]); - } - } - - break; - - case PA_SAMPLE_S16NE: - - for (oc = 0; oc < r->o_ss.channels; oc++) { - unsigned ic; - - for (ic = 0; ic < r->i_ss.channels; ic++) { - - if (r->map_table[oc][ic] <= 0.0) - continue; - - if (r->map_table[oc][ic] >= 1.0) { - static const int16_t one = 1; - - oil_vectoradd_s16( - (int16_t*) dst + oc, o_skip, - (int16_t*) dst + oc, o_skip, - (int16_t*) src + ic, i_skip, - (int) n_frames, - &one, &one); - - } else - - vectoradd_s16_with_fraction( - (int16_t*) dst + oc, o_skip, - (int16_t*) dst + oc, o_skip, - (int16_t*) src + ic, i_skip, - (int) n_frames, - 1.0f, r->map_table[oc][ic]); - } - } - - break; - - default: - pa_assert_not_reached(); - } + pa_assert (remap->do_remap); + remap->do_remap (remap, dst, src, n_frames); pa_memblock_release(input->memblock); pa_memblock_release(r->buf2.memblock); - r->buf2.length = out_n_samples * r->w_sz; - return &r->buf2; } @@ -1465,7 +1393,7 @@ static void trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned pa_assert(o_index * fz < pa_memblock_get_length(output->memblock)); - oil_memcpy((uint8_t*) dst + fz * o_index, + memcpy((uint8_t*) dst + fz * o_index, (uint8_t*) src + fz * j, (int) fz); } diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c index 42708a8a..666cbc98 100644 --- a/src/pulsecore/rtpoll.c +++ b/src/pulsecore/rtpoll.c @@ -63,6 +63,7 @@ struct pa_rtpoll { pa_bool_t running:1; pa_bool_t rebuild_needed:1; pa_bool_t quit:1; + pa_bool_t timer_elapsed:1; #ifdef DEBUG_TIMING pa_usec_t timestamp; @@ -94,26 +95,14 @@ PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); pa_rtpoll *pa_rtpoll_new(void) { pa_rtpoll *p; - p = pa_xnew(pa_rtpoll, 1); + p = pa_xnew0(pa_rtpoll, 1); p->n_pollfd_alloc = 32; p->pollfd = pa_xnew(struct pollfd, p->n_pollfd_alloc); p->pollfd2 = pa_xnew(struct pollfd, p->n_pollfd_alloc); - p->n_pollfd_used = 0; - - pa_zero(p->next_elapse); - p->timer_enabled = FALSE; - - p->running = FALSE; - p->scan_for_dead = FALSE; - p->rebuild_needed = FALSE; - p->quit = FALSE; - - PA_LLIST_HEAD_INIT(pa_rtpoll_item, p->items); #ifdef DEBUG_TIMING p->timestamp = pa_rtclock_now(); - p->slept = p->awake = 0; #endif return p; @@ -229,6 +218,7 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait_op) { pa_assert(!p->running); p->running = TRUE; + p->timer_elapsed = FALSE; /* First, let's do some work */ for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { @@ -286,7 +276,7 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait_op) { if (p->rebuild_needed) rtpoll_rebuild(p); - memset(&timeout, 0, sizeof(timeout)); + pa_zero(timeout); /* Calculate timeout */ if (wait_op && !p->quit && p->timer_enabled) { @@ -314,9 +304,11 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait_op) { r = ppoll(p->pollfd, p->n_pollfd_used, (!wait_op || p->quit || p->timer_enabled) ? &ts : NULL, NULL); } #else - r = poll(p->pollfd, p->n_pollfd_used, (!wait_op || p->quit || p->timer_enabled) ? (int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000)) : -1); + r = poll(p->pollfd, p->n_pollfd_used, (!wait_op || p->quit || p->timer_enabled) ? (int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000)) : -1); #endif + p->timer_elapsed = r == 0; + #ifdef DEBUG_TIMING { pa_usec_t now = pa_rtclock_now(); @@ -628,3 +620,9 @@ void pa_rtpoll_quit(pa_rtpoll *p) { p->quit = TRUE; } + +pa_bool_t pa_rtpoll_timer_elapsed(pa_rtpoll *p) { + pa_assert(p); + + return p->timer_elapsed; +} diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h index d2d69cad..b2a87fca 100644 --- a/src/pulsecore/rtpoll.h +++ b/src/pulsecore/rtpoll.h @@ -73,6 +73,10 @@ void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec); void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec); void pa_rtpoll_set_timer_disabled(pa_rtpoll *p); +/* Return TRUE when the elapsed timer was the reason for + * the last pa_rtpoll_run() invocation to finish */ +pa_bool_t pa_rtpoll_timer_elapsed(pa_rtpoll *p); + /* A new fd wakeup item for pa_rtpoll */ pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds); void pa_rtpoll_item_free(pa_rtpoll_item *i); diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c index 5b8ccf59..74600dec 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -30,9 +30,6 @@ #include <stdio.h> #include <errno.h> -#include <liboil/liboilfuncs.h> -#include <liboil/liboil.h> - #include <pulse/timeval.h> #include <pulsecore/log.h> @@ -106,29 +103,41 @@ void* pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) { return p; } +#define VOLUME_PADDING 32 + static void calc_linear_integer_volume(int32_t linear[], const pa_cvolume *volume) { - unsigned channel; + unsigned channel, nchannels, padding; pa_assert(linear); pa_assert(volume); - for (channel = 0; channel < volume->channels; channel++) + nchannels = volume->channels; + + for (channel = 0; channel < nchannels; channel++) linear[channel] = (int32_t) lrint(pa_sw_volume_to_linear(volume->values[channel]) * 0x10000); + + for (padding = 0; padding < VOLUME_PADDING; padding++, channel++) + linear[channel] = linear[padding]; } static void calc_linear_float_volume(float linear[], const pa_cvolume *volume) { - unsigned channel; + unsigned channel, nchannels, padding; pa_assert(linear); pa_assert(volume); - for (channel = 0; channel < volume->channels; channel++) + nchannels = volume->channels; + + for (channel = 0; channel < nchannels; channel++) linear[channel] = (float) pa_sw_volume_to_linear(volume->values[channel]); + + for (padding = 0; padding < VOLUME_PADDING; padding++, channel++) + linear[channel] = linear[padding]; } static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec) { unsigned k, channel; - float linear[PA_CHANNELS_MAX]; + float linear[PA_CHANNELS_MAX + VOLUME_PADDING]; pa_assert(streams); pa_assert(spec); @@ -147,7 +156,7 @@ static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned n static void calc_linear_float_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec) { unsigned k, channel; - float linear[PA_CHANNELS_MAX]; + float linear[PA_CHANNELS_MAX + VOLUME_PADDING]; pa_assert(streams); pa_assert(spec); @@ -690,6 +699,28 @@ size_t pa_mix( return length; } +typedef union { + float f; + uint32_t i; +} volume_val; + +typedef void (*pa_calc_volume_func_t) (void *volumes, const pa_cvolume *volume); + +static const pa_calc_volume_func_t calc_volume_table[] = { + [PA_SAMPLE_U8] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_ALAW] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_ULAW] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S16LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S16BE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_FLOAT32LE] = (pa_calc_volume_func_t) calc_linear_float_volume, + [PA_SAMPLE_FLOAT32BE] = (pa_calc_volume_func_t) calc_linear_float_volume, + [PA_SAMPLE_S32LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S32BE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24BE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24_32LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24_32BE] = (pa_calc_volume_func_t) calc_linear_integer_volume +}; void pa_volume_memchunk( pa_memchunk*c, @@ -697,6 +728,8 @@ void pa_volume_memchunk( const pa_cvolume *volume) { void *ptr; + volume_val linear[PA_CHANNELS_MAX + VOLUME_PADDING]; + pa_do_volume_func_t do_volume; pa_assert(c); pa_assert(spec); @@ -714,337 +747,19 @@ void pa_volume_memchunk( return; } - ptr = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index; - - switch (spec->format) { - - case PA_SAMPLE_S16NE: { - int16_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (int16_t*) ptr + c->length/sizeof(int16_t); - - for (channel = 0, d = ptr; d < e; d++) { - int32_t t, hi, lo; - - /* Multiplying the 32bit volume factor with the 16bit - * sample might result in an 48bit value. We want to - * do without 64 bit integers and hence do the - * multiplication independantly for the HI and LO part - * of the volume. */ - - hi = linear[channel] >> 16; - lo = linear[channel] & 0xFFFF; - - t = (int32_t)(*d); - t = ((t * lo) >> 16) + (t * hi); - t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); - *d = (int16_t) t; - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - - break; - } - - case PA_SAMPLE_S16RE: { - int16_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (int16_t*) ptr + c->length/sizeof(int16_t); - - for (channel = 0, d = ptr; d < e; d++) { - int32_t t, hi, lo; - - hi = linear[channel] >> 16; - lo = linear[channel] & 0xFFFF; - - t = (int32_t) PA_INT16_SWAP(*d); - t = ((t * lo) >> 16) + (t * hi); - t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); - *d = PA_INT16_SWAP((int16_t) t); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - - break; - } - - case PA_SAMPLE_S32NE: { - int32_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (int32_t*) ptr + c->length/sizeof(int32_t); - - for (channel = 0, d = ptr; d < e; d++) { - int64_t t; - - t = (int64_t)(*d); - t = (t * linear[channel]) >> 16; - t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); - *d = (int32_t) t; - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_S32RE: { - int32_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (int32_t*) ptr + c->length/sizeof(int32_t); - - for (channel = 0, d = ptr; d < e; d++) { - int64_t t; - - t = (int64_t) PA_INT32_SWAP(*d); - t = (t * linear[channel]) >> 16; - t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); - *d = PA_INT32_SWAP((int32_t) t); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_S24NE: { - uint8_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint8_t*) ptr + c->length; - - for (channel = 0, d = ptr; d < e; d += 3) { - int64_t t; - - t = (int64_t)((int32_t) (PA_READ24NE(d) << 8)); - t = (t * linear[channel]) >> 16; - t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); - PA_WRITE24NE(d, ((uint32_t) (int32_t) t) >> 8); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_S24RE: { - uint8_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint8_t*) ptr + c->length; - - for (channel = 0, d = ptr; d < e; d += 3) { - int64_t t; - - t = (int64_t)((int32_t) (PA_READ24RE(d) << 8)); - t = (t * linear[channel]) >> 16; - t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); - PA_WRITE24RE(d, ((uint32_t) (int32_t) t) >> 8); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_S24_32NE: { - uint32_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint32_t*) ptr + c->length/sizeof(uint32_t); - - for (channel = 0, d = ptr; d < e; d++) { - int64_t t; - - t = (int64_t) ((int32_t) (*d << 8)); - t = (t * linear[channel]) >> 16; - t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); - *d = ((uint32_t) ((int32_t) t)) >> 8; - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_S24_32RE: { - uint32_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint32_t*) ptr + c->length/sizeof(uint32_t); - - for (channel = 0, d = ptr; d < e; d++) { - int64_t t; - - t = (int64_t) ((int32_t) (PA_UINT32_SWAP(*d) << 8)); - t = (t * linear[channel]) >> 16; - t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); - *d = PA_UINT32_SWAP(((uint32_t) ((int32_t) t)) >> 8); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_U8: { - uint8_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint8_t*) ptr + c->length; - - for (channel = 0, d = ptr; d < e; d++) { - int32_t t, hi, lo; - - hi = linear[channel] >> 16; - lo = linear[channel] & 0xFFFF; - - t = (int32_t) *d - 0x80; - t = ((t * lo) >> 16) + (t * hi); - t = PA_CLAMP_UNLIKELY(t, -0x80, 0x7F); - *d = (uint8_t) (t + 0x80); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_ULAW: { - uint8_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint8_t*) ptr + c->length; - - for (channel = 0, d = ptr; d < e; d++) { - int32_t t, hi, lo; - - hi = linear[channel] >> 16; - lo = linear[channel] & 0xFFFF; - - t = (int32_t) st_ulaw2linear16(*d); - t = ((t * lo) >> 16) + (t * hi); - t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); - *d = (uint8_t) st_14linear2ulaw((int16_t) t >> 2); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_ALAW: { - uint8_t *d, *e; - unsigned channel; - int32_t linear[PA_CHANNELS_MAX]; - - calc_linear_integer_volume(linear, volume); - - e = (uint8_t*) ptr + c->length; - - for (channel = 0, d = ptr; d < e; d++) { - int32_t t, hi, lo; - - hi = linear[channel] >> 16; - lo = linear[channel] & 0xFFFF; - - t = (int32_t) st_alaw2linear16(*d); - t = ((t * lo) >> 16) + (t * hi); - t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); - *d = (uint8_t) st_13linear2alaw((int16_t) t >> 3); - - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - break; - } - - case PA_SAMPLE_FLOAT32NE: { - float *d; - int skip; - unsigned n; - unsigned channel; - - d = ptr; - skip = (int) (spec->channels * sizeof(float)); - n = (unsigned) (c->length/sizeof(float)/spec->channels); - - for (channel = 0; channel < spec->channels; channel ++) { - float v, *t; - - if (PA_UNLIKELY(volume->values[channel] == PA_VOLUME_NORM)) - continue; - - v = (float) pa_sw_volume_to_linear(volume->values[channel]); - t = d + channel; - oil_scalarmult_f32(t, skip, t, skip, &v, (int) n); - } - break; - } - - case PA_SAMPLE_FLOAT32RE: { - float *d, *e; - unsigned channel; - float linear[PA_CHANNELS_MAX]; - - calc_linear_float_volume(linear, volume); - - e = (float*) ptr + c->length/sizeof(float); - - for (channel = 0, d = ptr; d < e; d++) { - float t; + if (spec->format < 0 || spec->format > PA_SAMPLE_MAX) { + pa_log_warn(" Unable to change volume of format %s.", pa_sample_format_to_string(spec->format)); + return; + } - t = PA_FLOAT32_SWAP(*d); - t *= linear[channel]; - *d = PA_FLOAT32_SWAP(t); + do_volume = pa_get_volume_func (spec->format); + pa_assert(do_volume); - if (PA_UNLIKELY(++channel >= spec->channels)) - channel = 0; - } - - break; - } + calc_volume_table[spec->format] ((void *)linear, volume); + ptr = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index; - default: - pa_log_warn(" Unable to change volume of format %s.", pa_sample_format_to_string(spec->format)); - /* If we cannot change the volume, we just don't do it */ - } + do_volume (ptr, (void *)linear, spec->channels, c->length); pa_memblock_release(c->memblock); } @@ -1090,7 +805,7 @@ void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, u d = (uint8_t*) dst + c * ss; for (j = 0; j < n; j ++) { - oil_memcpy(d, s, (int) ss); + memcpy(d, s, (int) ss); s = (uint8_t*) s + ss; d = (uint8_t*) d + fs; } @@ -1118,7 +833,7 @@ void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, d = dst[c]; for (j = 0; j < n; j ++) { - oil_memcpy(d, s, (int) ss); + memcpy(d, s, (int) ss); s = (uint8_t*) s + fs; d = (uint8_t*) d + ss; } @@ -1227,10 +942,15 @@ void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const vo s = src; d = dst; if (format == PA_SAMPLE_FLOAT32NE) { + for (; n > 0; n--) { + float f; - float minus_one = -1.0, plus_one = 1.0; - oil_clip_f32(d, (int) dstr, s, (int) sstr, (int) n, &minus_one, &plus_one); + f = *s; + *d = PA_CLAMP_UNLIKELY(f, -1.0f, 1.0f); + s = (const float*) ((const uint8_t*) s + sstr); + d = (float*) ((uint8_t*) d + dstr); + } } else { pa_assert(format == PA_SAMPLE_FLOAT32RE); @@ -1287,7 +1007,7 @@ void pa_memchunk_dump_to_file(pa_memchunk *c, const char *fn) { /* Only for debugging purposes */ - f = fopen(fn, "a"); + f = pa_fopen_cloexec(fn, "a"); if (!f) { pa_log_warn("Failed to open '%s': %s", fn, pa_cstrerror(errno)); @@ -1336,3 +1056,13 @@ void pa_memchunk_sine(pa_memchunk *c, pa_mempool *pool, unsigned rate, unsigned calc_sine(p, c->length, freq * l / rate); pa_memblock_release(c->memblock); } + +size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to) { + pa_usec_t usec; + + pa_assert(from); + pa_assert(to); + + usec = pa_bytes_to_usec_round_up(size, from); + return pa_usec_to_bytes_round_up(usec, to); +} diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h index 6a306c11..d0235d60 100644 --- a/src/pulsecore/sample-util.h +++ b/src/pulsecore/sample-util.h @@ -86,6 +86,13 @@ void pa_memchunk_dump_to_file(pa_memchunk *c, const char *fn); void pa_memchunk_sine(pa_memchunk *c, pa_mempool *pool, unsigned rate, unsigned freq); +typedef void (*pa_do_volume_func_t) (void *samples, void *volumes, unsigned channels, unsigned length); + +pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f); +void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func); + +size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to); + #define PA_CHANNEL_POSITION_MASK_LEFT \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c index 43b8cb3e..0fefdf1c 100644 --- a/src/pulsecore/sconv-s16le.c +++ b/src/pulsecore/sconv-s16le.c @@ -28,8 +28,6 @@ #include <inttypes.h> #include <stdio.h> -#include <liboil/liboilfuncs.h> - #include <pulsecore/sconv.h> #include <pulsecore/macro.h> #include <pulsecore/log.h> @@ -86,17 +84,13 @@ void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b) { pa_assert(b); #if SWAP_WORDS == 1 - for (; n > 0; n--) { int16_t s = *(a++); *(b++) = ((float) INT16_FROM(s))/(float) 0x7FFF; } - #else -{ - static const double add = 0, factor = 1.0/0x7FFF; - oil_scaleconv_f32_s16(b, a, (int) n, &add, &factor); -} + for (; n > 0; n--) + *(b++) = ((float) (*(a++)))/(float) 0x7FFF; #endif } @@ -105,17 +99,13 @@ void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b) { pa_assert(b); #if SWAP_WORDS == 1 - for (; n > 0; n--) { int32_t s = *(a++); *(b++) = (float) (((double) INT32_FROM(s))/0x7FFFFFFF); } - #else -{ - static const double add = 0, factor = 1.0/0x7FFFFFFF; - oil_scaleconv_f32_s32(b, a, (int) n, &add, &factor); -} + for (; n > 0; n--) + *(b++) = (float) (((double) (*(a++)))/0x7FFFFFFF); #endif } @@ -124,7 +114,6 @@ void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b) { pa_assert(b); #if SWAP_WORDS == 1 - for (; n > 0; n--) { int16_t s; float v = *(a++); @@ -133,12 +122,13 @@ void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b) { s = (int16_t) lrintf(v * 0x7FFF); *(b++) = INT16_TO(s); } - #else -{ - static const double add = 0, factor = 0x7FFF; - oil_scaleconv_s16_f32(b, a, (int) n, &add, &factor); -} + for (; n > 0; n--) { + float v = *(a++); + + v = PA_CLAMP_UNLIKELY(v, -1.0f, 1.f); + *(b++) = (int16_t) lrintf(v * 0x7FFF); + } #endif } @@ -147,7 +137,6 @@ void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b) { pa_assert(b); #if SWAP_WORDS == 1 - for (; n > 0; n--) { int32_t s; float v = *(a++); @@ -156,12 +145,13 @@ void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b) { s = (int32_t) lrint((double) v * (double) 0x7FFFFFFF); *(b++) = INT32_TO(s); } - #else -{ - static const double add = 0, factor = 0x7FFFFFFF; - oil_scaleconv_s32_f32(b, a, (int) n, &add, &factor); -} + for (; n > 0; n--) { + float v = *(a++); + + v = PA_CLAMP_UNLIKELY(v, -1.0f, 1.0f); + *(b++) = (int32_t) lrint((double) v * (double) 0x7FFFFFFF); + } #endif } diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c index d89f4283..301f08b4 100644 --- a/src/pulsecore/sconv.c +++ b/src/pulsecore/sconv.c @@ -27,9 +27,6 @@ #include <stdio.h> #include <stdlib.h> -#include <liboil/liboilfuncs.h> -#include <liboil/liboil.h> - #include <pulsecore/g711.h> #include <pulsecore/macro.h> @@ -41,32 +38,31 @@ /* u8 */ static void u8_to_float32ne(unsigned n, const uint8_t *a, float *b) { - static const double add = -1, factor = 1.0/128.0; - pa_assert(a); pa_assert(b); - oil_scaleconv_f32_u8(b, a, (int) n, &add, &factor); + for (; n > 0; n--, a++, b++) + *b = (*a * 1.0/128.0) - 1.0; } static void u8_from_float32ne(unsigned n, const float *a, uint8_t *b) { - static const double add = 128, factor = 127.0; - pa_assert(a); pa_assert(b); - oil_scaleconv_u8_f32(b, a, (int) n, &add, &factor); + for (; n > 0; n--, a++, b++) { + float v; + v = (*a * 127.0) + 128.0; + v = PA_CLAMP_UNLIKELY (v, 0.0, 255.0); + *b = rint (v); + } } static void u8_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { - static const int16_t add = -0x80, factor = 0x100; - pa_assert(a); pa_assert(b); - oil_conv_s16_u8(b, 2, a, 1, (int) n); - oil_scalaradd_s16(b, 2, b, 2, &add, (int) n); - oil_scalarmult_s16(b, 2, b, 2, &factor, (int) n); + for (; n > 0; n--, a++, b++) + *b = (((int16_t)*a) - 128) << 8; } static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { @@ -84,7 +80,7 @@ static void float32ne_to_float32ne(unsigned n, const float *a, float *b) { pa_assert(a); pa_assert(b); - oil_memcpy(b, a, (int) (sizeof(float) * n)); + memcpy(b, a, (int) (sizeof(float) * n)); } static void float32re_to_float32ne(unsigned n, const float *a, float *b) { @@ -101,7 +97,7 @@ static void s16ne_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { pa_assert(a); pa_assert(b); - oil_memcpy(b, a, (int) (sizeof(int16_t) * n)); + memcpy(b, a, (int) (sizeof(int16_t) * n)); } static void s16re_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { @@ -188,98 +184,130 @@ static void alaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { *b = st_13linear2alaw(*a >> 3); } +static pa_convert_func_t to_float32ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_float32ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_float32ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_float32ne, + [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_to_float32ne, + [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_to_float32ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_float32ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_float32ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_float32ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_float32ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_float32ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_float32ne, + [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, + [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, +}; + pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) { - static const pa_convert_func_t table[] = { - [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_float32ne, - [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_float32ne, - [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_float32ne, - [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_to_float32ne, - [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_to_float32ne, - [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_float32ne, - [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_float32ne, - [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_float32ne, - [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_float32ne, - [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_float32ne, - [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_float32ne, - [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, - [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, - }; + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return to_float32ne_table[f]; +} + +void pa_set_convert_to_float32ne_function(pa_sample_format_t f, pa_convert_func_t func) { pa_assert(f >= 0); pa_assert(f < PA_SAMPLE_MAX); - return table[f]; + to_float32ne_table[f] = func; } +static pa_convert_func_t from_float32ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_float32ne, + [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_from_float32ne, + [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_from_float32ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_float32ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_float32ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_float32ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_float32ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_float32ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_float32ne, + [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, + [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_float32ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_float32ne +}; + pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) { - static const pa_convert_func_t table[] = { - [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_float32ne, - [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_from_float32ne, - [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_from_float32ne, - [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_float32ne, - [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_float32ne, - [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_float32ne, - [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_float32ne, - [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_float32ne, - [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_float32ne, - [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, - [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, - [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_float32ne, - [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_float32ne - }; + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return from_float32ne_table[f]; +} + +void pa_set_convert_from_float32ne_function(pa_sample_format_t f, pa_convert_func_t func) { pa_assert(f >= 0); pa_assert(f < PA_SAMPLE_MAX); - return table[f]; + from_float32ne_table[f] = func; } +static pa_convert_func_t to_s16ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_s16ne, + [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, + [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, + [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_to_s16ne, + [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_to_s16ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_s16ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_s16ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_s16ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_s16ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_s16ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_s16ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_s16ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_s16ne +}; + pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) { - static const pa_convert_func_t table[] = { - [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_s16ne, - [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, - [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, - [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_to_s16ne, - [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_to_s16ne, - [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_s16ne, - [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_s16ne, - [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_s16ne, - [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_s16ne, - [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_s16ne, - [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_s16ne, - [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_s16ne, - [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_s16ne - }; + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return to_s16ne_table[f]; +} + +void pa_set_convert_to_s16ne_function(pa_sample_format_t f, pa_convert_func_t func) { pa_assert(f >= 0); pa_assert(f < PA_SAMPLE_MAX); - return table[f]; + to_s16ne_table[f] = func; } +static pa_convert_func_t from_s16ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_s16ne, + [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, + [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, + [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_from_s16ne, + [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_from_s16ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_s16ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_s16ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_s16ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_s16ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_s16ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_s16ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_s16ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_s16ne, +}; + pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) { - static const pa_convert_func_t table[] = { - [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_s16ne, - [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, - [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, - [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_from_s16ne, - [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_from_s16ne, - [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_s16ne, - [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_s16ne, - [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_s16ne, - [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_s16ne, - [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_s16ne, - [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_s16ne, - [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_s16ne, - [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_s16ne, - }; + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return from_s16ne_table[f]; +} + +void pa_set_convert_from_s16ne_function(pa_sample_format_t f, pa_convert_func_t func) { pa_assert(f >= 0); pa_assert(f < PA_SAMPLE_MAX); - return table[f]; + from_s16ne_table[f] = func; } diff --git a/src/pulsecore/sconv.h b/src/pulsecore/sconv.h index b00a16a4..cd937559 100644 --- a/src/pulsecore/sconv.h +++ b/src/pulsecore/sconv.h @@ -33,4 +33,10 @@ pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) P pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) PA_GCC_PURE; pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) PA_GCC_PURE; +void pa_set_convert_to_float32ne_function(pa_sample_format_t f, pa_convert_func_t func); +void pa_set_convert_from_float32ne_function(pa_sample_format_t f, pa_convert_func_t func); + +void pa_set_convert_to_s16ne_function(pa_sample_format_t f, pa_convert_func_t func); +void pa_set_convert_from_s16ne_function(pa_sample_format_t f, pa_convert_func_t func); + #endif diff --git a/src/pulsecore/sconv_sse.c b/src/pulsecore/sconv_sse.c new file mode 100644 index 00000000..3737af2a --- /dev/null +++ b/src/pulsecore/sconv_sse.c @@ -0,0 +1,235 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulsecore/g711.h> +#include <pulsecore/macro.h> + +#include "endianmacros.h" + +#include "cpu-x86.h" +#include "sconv.h" + +#if defined (__i386__) || defined (__amd64__) + +static const PA_DECLARE_ALIGNED (16, float, one[4]) = { 1.0, 1.0, 1.0, 1.0 }; +static const PA_DECLARE_ALIGNED (16, float, mone[4]) = { -1.0, -1.0, -1.0, -1.0 }; +static const PA_DECLARE_ALIGNED (16, float, scale[4]) = { 0x7fff, 0x7fff, 0x7fff, 0x7fff }; + +static void pa_sconv_s16le_from_f32ne_sse(unsigned n, const float *a, int16_t *b) { + pa_reg_x86 temp, i; + + __asm__ __volatile__ ( + " movaps %5, %%xmm5 \n\t" + " movaps %6, %%xmm6 \n\t" + " movaps %7, %%xmm7 \n\t" + " xor %0, %0 \n\t" + + " mov %4, %1 \n\t" + " sar $3, %1 \n\t" /* 8 floats at a time */ + " cmp $0, %1 \n\t" + " je 2f \n\t" + + "1: \n\t" + " movups (%2, %0, 2), %%xmm0 \n\t" /* read 8 floats */ + " movups 16(%2, %0, 2), %%xmm2 \n\t" + " minps %%xmm5, %%xmm0 \n\t" /* clamp to 1.0 */ + " minps %%xmm5, %%xmm2 \n\t" + " maxps %%xmm6, %%xmm0 \n\t" /* clamp to -1.0 */ + " maxps %%xmm6, %%xmm2 \n\t" + " mulps %%xmm7, %%xmm0 \n\t" /* *= 0x7fff */ + " mulps %%xmm7, %%xmm2 \n\t" + + " cvtps2pi %%xmm0, %%mm0 \n\t" /* low part to int */ + " cvtps2pi %%xmm2, %%mm2 \n\t" + " movhlps %%xmm0, %%xmm0 \n\t" /* bring high part in position */ + " movhlps %%xmm2, %%xmm2 \n\t" + " cvtps2pi %%xmm0, %%mm1 \n\t" /* high part to int */ + " cvtps2pi %%xmm2, %%mm3 \n\t" + + " packssdw %%mm1, %%mm0 \n\t" /* pack parts */ + " packssdw %%mm3, %%mm2 \n\t" + " movq %%mm0, (%3, %0) \n\t" + " movq %%mm2, 8(%3, %0) \n\t" + + " add $16, %0 \n\t" + " dec %1 \n\t" + " jne 1b \n\t" + + "2: \n\t" + " mov %4, %1 \n\t" /* prepare for leftovers */ + " and $7, %1 \n\t" + " je 4f \n\t" + + "3: \n\t" + " movss (%2, %0, 2), %%xmm0 \n\t" + " minss %%xmm5, %%xmm0 \n\t" + " maxss %%xmm6, %%xmm0 \n\t" + " mulss %%xmm7, %%xmm0 \n\t" + " cvtss2si %%xmm0, %4 \n\t" + " movw %w4, (%3, %0) \n\t" + " add $2, %0 \n\t" + " dec %1 \n\t" + " jne 3b \n\t" + + "4: \n\t" + " emms \n\t" + + : "=&r" (i), "=&r" (temp) + : "r" (a), "r" (b), "r" ((pa_reg_x86)n), "m" (*one), "m" (*mone), "m" (*scale) + : "cc", "memory" + ); +} + +static void pa_sconv_s16le_from_f32ne_sse2(unsigned n, const float *a, int16_t *b) { + pa_reg_x86 temp, i; + + __asm__ __volatile__ ( + " movaps %5, %%xmm5 \n\t" + " movaps %6, %%xmm6 \n\t" + " movaps %7, %%xmm7 \n\t" + " xor %0, %0 \n\t" + + " mov %4, %1 \n\t" + " sar $3, %1 \n\t" /* 8 floats at a time */ + " cmp $0, %1 \n\t" + " je 2f \n\t" + + "1: \n\t" + " movups (%2, %0, 2), %%xmm0 \n\t" /* read 8 floats */ + " movups 16(%2, %0, 2), %%xmm2 \n\t" + " minps %%xmm5, %%xmm0 \n\t" /* clamp to 1.0 */ + " minps %%xmm5, %%xmm2 \n\t" + " maxps %%xmm6, %%xmm0 \n\t" /* clamp to -1.0 */ + " maxps %%xmm6, %%xmm2 \n\t" + " mulps %%xmm7, %%xmm0 \n\t" /* *= 0x7fff */ + " mulps %%xmm7, %%xmm2 \n\t" + + " cvtps2dq %%xmm0, %%xmm0 \n\t" + " cvtps2dq %%xmm2, %%xmm2 \n\t" + + " packssdw %%xmm2, %%xmm0 \n\t" + " movdqu %%xmm0, (%3, %0) \n\t" + + " add $16, %0 \n\t" + " dec %1 \n\t" + " jne 1b \n\t" + + "2: \n\t" + " mov %4, %1 \n\t" /* prepare for leftovers */ + " and $7, %1 \n\t" + " je 4f \n\t" + + "3: \n\t" + " movss (%2, %0, 2), %%xmm0 \n\t" + " minss %%xmm5, %%xmm0 \n\t" + " maxss %%xmm6, %%xmm0 \n\t" + " mulss %%xmm7, %%xmm0 \n\t" + " cvtss2si %%xmm0, %4 \n\t" + " movw %w4, (%3, %0) \n\t" + " add $2, %0 \n\t" + " dec %1 \n\t" + " jne 3b \n\t" + + "4: \n\t" + + : "=&r" (i), "=&r" (temp) + : "r" (a), "r" (b), "r" ((pa_reg_x86)n), "m" (*one), "m" (*mone), "m" (*scale) + : "cc", "memory" + ); +} + +#undef RUN_TEST + +#ifdef RUN_TEST +#define SAMPLES 1019 +#define TIMES 1000 + +static void run_test (void) { + int16_t samples[SAMPLES]; + int16_t samples_ref[SAMPLES]; + float floats[SAMPLES]; + int i; + pa_usec_t start, stop; + pa_convert_func_t func; + + printf ("checking SSE %zd\n", sizeof (samples)); + + memset (samples_ref, 0, sizeof (samples_ref)); + memset (samples, 0, sizeof (samples)); + + for (i = 0; i < SAMPLES; i++) { + floats[i] = (rand()/(RAND_MAX+2.2)) - 1.1; + } + + func = pa_get_convert_from_float32ne_function (PA_SAMPLE_S16LE); + func (SAMPLES, floats, samples_ref); + pa_sconv_s16le_from_f32ne_sse2 (SAMPLES, floats, samples); + + for (i = 0; i < SAMPLES; i++) { + if (samples[i] != samples_ref[i]) { + printf ("%d: %04x != %04x (%f)\n", i, samples[i], samples_ref[i], + floats[i]); + } + } + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + pa_sconv_s16le_from_f32ne_sse2 (SAMPLES, floats, samples); + } + stop = pa_rtclock_now(); + pa_log_info("SSE: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + func (SAMPLES, floats, samples_ref); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); +} +#endif +#endif /* defined (__i386__) || defined (__amd64__) */ + + +void pa_convert_func_init_sse (pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + +#ifdef RUN_TEST + run_test (); +#endif + + if (flags & PA_CPU_X86_SSE2) { + pa_log_info("Initialising SSE2 optimized conversions."); + pa_set_convert_from_float32ne_function (PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse2); + } else { + pa_log_info("Initialising SSE optimized conversions."); + pa_set_convert_from_float32ne_function (PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/semaphore-osx.c b/src/pulsecore/semaphore-osx.c new file mode 100644 index 00000000..73f43559 --- /dev/null +++ b/src/pulsecore/semaphore-osx.c @@ -0,0 +1,63 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Kim Lester <kim@dfusion.com.au> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <Multiprocessing.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "semaphore.h" + +struct pa_semaphore +{ + MPSemaphoreID sema; +}; + +pa_semaphore* pa_semaphore_new(unsigned int value) { + /* NOTE: Can't assume boolean - ie value = 0,1, so use UINT_MAX (boolean more efficient ?) */ + pa_semaphore *s; + + s = pa_xnew(pa_semaphore, 1); + pa_assert_se(MPCreateSemaphore(UINT_MAX, value, &s->sema) == 0); + + return s; +} + +void pa_semaphore_free(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(MPDeleteSemaphore(s->sema) == 0); + pa_xfree(s); +} + +void pa_semaphore_post(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(MPSignalSemaphore(s->sema) == 0); +} + +void pa_semaphore_wait(pa_semaphore *s) { + pa_assert(s); + /* should probably check return value (-ve is error), noErr is ok. */ + pa_assert_se(MPWaitOnSemaphore(s->sema, kDurationForever) == 0); +} diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index 6e428426..fbf777a4 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -60,7 +60,8 @@ #define MADV_REMOVE 9 #endif -#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*64)) +/* 1 GiB at max */ +#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*1024)) #ifdef __linux__ /* On Linux we know that the shared memory blocks are files in diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 2acaa08f..216edd41 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -45,9 +45,10 @@ #define MEMBLOCKQ_MAXLENGTH (32*1024*1024) #define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE) -static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject); +PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject); static void sink_input_free(pa_object *o); +static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v); static void sink_input_set_ramping_info(pa_sink_input* i, pa_volume_t pre_virtual_volume, pa_volume_t target_virtual_volume, pa_usec_t t); static void sink_input_set_ramping_info_for_mute(pa_sink_input* i, pa_bool_t mute, pa_usec_t t); static void sink_input_volume_ramping(pa_sink_input* i, pa_memchunk* chunk); @@ -57,7 +58,7 @@ static void sink_input_release_envelope(pa_sink_input *i); pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) { pa_assert(data); - memset(data, 0, sizeof(*data)); + pa_zero(*data); data->resample_method = PA_RESAMPLER_INVALID; data->proplist = pa_proplist_new(); @@ -97,6 +98,18 @@ void pa_sink_input_new_data_apply_volume_factor(pa_sink_input_new_data *data, co } } +void pa_sink_input_new_data_apply_volume_factor_sink(pa_sink_input_new_data *data, const pa_cvolume *volume_factor) { + pa_assert(data); + pa_assert(volume_factor); + + if (data->volume_factor_sink_is_set) + pa_sw_cvolume_multiply(&data->volume_factor_sink, &data->volume_factor_sink, volume_factor); + else { + data->volume_factor_sink_is_set = TRUE; + data->volume_factor_sink = *volume_factor; + } +} + void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) { pa_assert(data); @@ -120,6 +133,7 @@ static void reset_callbacks(pa_sink_input *i) { i->update_max_request = NULL; i->update_sink_requested_latency = NULL; i->update_sink_latency_range = NULL; + i->update_sink_fixed_latency = NULL; i->attach = NULL; i->detach = NULL; i->suspend = NULL; @@ -130,24 +144,27 @@ static void reset_callbacks(pa_sink_input *i) { i->state_change = NULL; i->may_move_to = NULL; i->send_event = NULL; + i->volume_changed = NULL; + i->mute_changed = NULL; } /* Called from main context */ int pa_sink_input_new( pa_sink_input **_i, pa_core *core, - pa_sink_input_new_data *data, - pa_sink_input_flags_t flags) { + pa_sink_input_new_data *data) { pa_sink_input *i; pa_resampler *resampler = NULL; char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_channel_map original_cm; int r; + char *pt; pa_assert(_i); pa_assert(core); pa_assert(data); + pa_assert_ctl_context(); if (data->client) pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist); @@ -178,7 +195,6 @@ int pa_sink_input_new( pa_channel_map_init_extend(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); } - pa_return_val_if_fail(pa_channel_map_valid(&data->channel_map), -PA_ERR_INVALID); pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID); if (!data->volume_is_set) { @@ -187,27 +203,30 @@ int pa_sink_input_new( data->save_volume = FALSE; } - pa_return_val_if_fail(pa_cvolume_valid(&data->volume), -PA_ERR_INVALID); pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID); if (!data->volume_factor_is_set) pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels); - pa_return_val_if_fail(pa_cvolume_valid(&data->volume_factor), -PA_ERR_INVALID); pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID); + if (!data->volume_factor_sink_is_set) + pa_cvolume_reset(&data->volume_factor_sink, data->sink->sample_spec.channels); + + pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_sink, &data->sink->sample_spec), -PA_ERR_INVALID); + if (!data->muted_is_set) data->muted = FALSE; - if (flags & PA_SINK_INPUT_FIX_FORMAT) + if (data->flags & PA_SINK_INPUT_FIX_FORMAT) data->sample_spec.format = data->sink->sample_spec.format; - if (flags & PA_SINK_INPUT_FIX_RATE) + if (data->flags & PA_SINK_INPUT_FIX_RATE) data->sample_spec.rate = data->sink->sample_spec.rate; original_cm = data->channel_map; - if (flags & PA_SINK_INPUT_FIX_CHANNELS) { + if (data->flags & PA_SINK_INPUT_FIX_CHANNELS) { data->sample_spec.channels = data->sink->sample_spec.channels; data->channel_map = data->sink->channel_map; } @@ -226,7 +245,7 @@ int pa_sink_input_new( if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0) return r; - if ((flags & PA_SINK_INPUT_FAIL_ON_SUSPEND) && + if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) && pa_sink_get_state(data->sink) == PA_SINK_SUSPENDED) { pa_log_warn("Failed to create sink input: sink is suspended."); return -PA_ERR_BADSTATE; @@ -237,7 +256,7 @@ int pa_sink_input_new( return -PA_ERR_TOOLARGE; } - if ((flags & PA_SINK_INPUT_VARIABLE_RATE) || + if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) || !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) || !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { @@ -246,9 +265,9 @@ int pa_sink_input_new( &data->sample_spec, &data->channel_map, &data->sink->sample_spec, &data->sink->channel_map, data->resample_method, - ((flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | - ((flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | - (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { pa_log_warn("Unsupported resampling operation."); return -PA_ERR_NOTSUPPORTED; @@ -261,7 +280,7 @@ int pa_sink_input_new( i->core = core; i->state = PA_SINK_INPUT_INIT; - i->flags = flags; + i->flags = data->flags; i->proplist = pa_proplist_copy(data->proplist); i->driver = pa_xstrdup(pa_path_get_filename(data->driver)); i->module = data->module; @@ -274,18 +293,21 @@ int pa_sink_input_new( i->channel_map = data->channel_map; if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !data->volume_is_absolute) { + pa_cvolume remapped; + /* When the 'absolute' bool is not set then we'll treat the volume * as relative to the sink volume even in flat volume mode */ - - pa_cvolume v = data->sink->reference_volume; - pa_cvolume_remap(&v, &data->sink->channel_map, &data->channel_map); - pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &v); + remapped = data->sink->reference_volume; + pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map); + pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped); } else - i->virtual_volume = data->volume; + i->volume = data->volume; i->volume_factor = data->volume_factor; - pa_cvolume_init(&i->soft_volume); - memset(i->relative_volume, 0, sizeof(i->relative_volume)); + i->volume_factor_sink = data->volume_factor_sink; + i->real_ratio = i->reference_ratio = data->volume; + pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels); + pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels); i->save_volume = data->save_volume; i->save_sink = data->save_sink; i->save_muted = data->save_muted; @@ -348,12 +370,15 @@ int pa_sink_input_new( if (i->client) pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0); - pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s", + pt = pa_proplist_to_string_sep(i->proplist, "\n "); + pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s\n %s", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), i->sink->name, pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map)); + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + pt); + pa_xfree(pt); /* Don't forget to call pa_sink_input_put! */ @@ -364,6 +389,7 @@ int pa_sink_input_new( /* Called from main context */ static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) { pa_assert(i); + pa_assert_ctl_context(); if (!i->sink) return; @@ -378,6 +404,7 @@ static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) { static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) { pa_sink_input *ssync; pa_assert(i); + pa_assert_ctl_context(); if (state == PA_SINK_INPUT_DRAINED) state = PA_SINK_INPUT_RUNNING; @@ -416,7 +443,9 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) void pa_sink_input_unlink(pa_sink_input *i) { pa_bool_t linked; pa_source_output *o, *p = NULL; + pa_assert(i); + pa_assert_ctl_context(); /* See pa_sink_unlink() for a couple of comments how this function * works */ @@ -455,11 +484,8 @@ void pa_sink_input_unlink(pa_sink_input *i) { if (linked && i->sink) { /* We might need to update the sink's volume if we are in flat volume mode. */ - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE); - } + if (i->sink->flags & PA_SINK_FLAT_VOLUME) + pa_sink_set_volume(i->sink, NULL, FALSE, FALSE); if (i->sink->asyncmsgq) pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0); @@ -487,6 +513,7 @@ static void sink_input_free(pa_object *o) { pa_sink_input* i = PA_SINK_INPUT(o); pa_assert(i); + pa_assert_ctl_context(); pa_assert(pa_sink_input_refcnt(i) == 0); if (PA_SINK_INPUT_IS_LINKED(i->state)) @@ -494,7 +521,10 @@ static void sink_input_free(pa_object *o) { pa_log_info("Freeing input %u \"%s\"", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME))); - pa_assert(!i->thread_info.attached); + /* Side note: this function must be able to destruct properly any + * kind of sink input in any state, even those which are + * "half-moved" or are connected to sinks that have no asyncmsgq + * and are hence half-destructed themselves! */ if (i->thread_info.ramp_info.envelope) { pa_log_debug ("Freeing envelope\n"); @@ -524,7 +554,9 @@ static void sink_input_free(pa_object *o) { /* Called from main context */ void pa_sink_input_put(pa_sink_input *i) { pa_sink_input_state_t state; + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(i->state == PA_SINK_INPUT_INIT); @@ -539,12 +571,10 @@ void pa_sink_input_put(pa_sink_input *i) { i->state = state; /* We might need to update the sink's volume if we are in flat volume mode. */ - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE); - } else - pa_sink_input_set_relative_volume(i, &i->virtual_volume); + if (i->sink->flags & PA_SINK_FLAT_VOLUME) + pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume); + else + set_real_ratio(i, &i->volume); i->thread_info.soft_volume = i->soft_volume; i->thread_info.muted = i->muted; @@ -560,6 +590,7 @@ void pa_sink_input_put(pa_sink_input *i) { /* Called from main context */ void pa_sink_input_kill(pa_sink_input*i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); i->kill(i); @@ -570,6 +601,7 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) { pa_usec_t r[2] = { 0, 0 }; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); @@ -585,13 +617,14 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) { /* Called from thread context */ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) { - pa_bool_t do_volume_adj_here; + pa_bool_t do_volume_adj_here, need_volume_factor_sink; pa_bool_t volume_is_norm; pa_bool_t ramping; size_t block_size_max_sink, block_size_max_sink_input; size_t ilength; pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec)); pa_assert(chunk); @@ -633,6 +666,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map) || i->thread_info.ramp_info.is_ramping; volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted; + need_volume_factor_sink = !pa_cvolume_is_norm(&i->volume_factor_sink); while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) { pa_memchunk tchunk; @@ -664,6 +698,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p while (tchunk.length > 0) { pa_memchunk wchunk; + pa_bool_t nvfs = need_volume_factor_sink; wchunk = tchunk; pa_memblock_ref(wchunk.memblock); @@ -675,18 +710,41 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p if (do_volume_adj_here && !volume_is_norm && !i->thread_info.ramp_info.is_ramping) { pa_memchunk_make_writable(&wchunk, 0); - if (i->thread_info.muted) + if (i->thread_info.muted) { pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec); - else + nvfs = FALSE; + + } else if (!i->thread_info.resampler && nvfs) { + pa_cvolume v; + + /* If we don't need a resampler we can merge the + * post and the pre volume adjustment into one */ + + pa_sw_cvolume_multiply(&v, &i->thread_info.soft_volume, &i->volume_factor_sink); + pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &v); + nvfs = FALSE; + + } else pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_volume); } - if (!i->thread_info.resampler) + if (!i->thread_info.resampler) { + + if (nvfs) { + pa_memchunk_make_writable(&wchunk, 0); + pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &i->volume_factor_sink); + } + pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk); - else { + } else { pa_memchunk rchunk; pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk); + if (nvfs) { + pa_memchunk_make_writable(&rchunk, 0); + pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &i->volume_factor_sink); + } + /* pa_log_debug("pushing %lu", (unsigned long) rchunk.length); */ if (rchunk.memblock) { @@ -746,8 +804,9 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p /* Called from thread context */ void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { - pa_sink_input_assert_ref(i); + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); pa_assert(nbytes > 0); @@ -761,8 +820,9 @@ void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec * void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { size_t lbq; pa_bool_t called = FALSE; - pa_sink_input_assert_ref(i); + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); @@ -831,8 +891,28 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam } /* Called from thread context */ +size_t pa_sink_input_get_max_rewind(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_rewind) : i->sink->thread_info.max_rewind; +} + +/* Called from thread context */ +size_t pa_sink_input_get_max_request(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + /* We're not verifying the status here, to allow this to be called + * in the state change handler between _INIT and _RUNNING */ + + return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_request) : i->sink->thread_info.max_request; +} + +/* Called from thread context */ void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); @@ -845,6 +925,7 @@ void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the /* Called from thread context */ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); @@ -855,15 +936,16 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the /* Called from thread context */ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) - usec = i->sink->fixed_latency; + usec = i->sink->thread_info.fixed_latency; if (usec != (pa_usec_t) -1) usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); i->thread_info.requested_sink_latency = usec; - pa_sink_invalidate_requested_latency(i->sink); + pa_sink_invalidate_requested_latency(i->sink, TRUE); return usec; } @@ -871,6 +953,7 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa /* Called from main context */ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); @@ -882,7 +965,7 @@ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) if (i->sink) { if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) - usec = i->sink->fixed_latency; + usec = pa_sink_get_fixed_latency(i->sink); if (usec != (pa_usec_t) -1) { pa_usec_t min_latency, max_latency; @@ -899,6 +982,7 @@ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) /* Called from main context */ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { pa_usec_t usec = 0; @@ -913,73 +997,44 @@ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { } /* Called from main context */ -void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) { - /* test ramping -> return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 2000 * PA_USEC_PER_MSEC); */ - return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 0); -} - -/* Called from main context */ -pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute) { +static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec)); - if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) { - pa_cvolume v = i->sink->reference_volume; - pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); - pa_sw_cvolume_divide(volume, &i->virtual_volume, &v); - } else - *volume = i->virtual_volume; + /* This basically calculates: + * + * i->real_ratio := v + * i->soft_volume := i->real_ratio * i->volume_factor */ - return volume; + if (v) + i->real_ratio = *v; + else + pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels); + + pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor); + /* We don't copy the data to the thread_info data. That's left for someone else to do */ } /* Called from main context */ -pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v) { - unsigned c; - - pa_sink_input_assert_ref(i); - pa_assert(v); - pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - - /* This always returns the relative volume. Converts the float - * version into a pa_cvolume */ - - v->channels = i->sample_spec.channels; - - for (c = 0; c < v->channels; c++) - v->values[c] = pa_sw_volume_from_linear(i->relative_volume[c]); - - return v; +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) { + /* test ramping -> return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 2000 * PA_USEC_PER_MSEC); */ + return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 0); } /* Called from main context */ -void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v) { - unsigned c; - pa_cvolume _v; - +pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec)); - if (!v) - v = pa_cvolume_reset(&_v, i->sample_spec.channels); - - /* This basically calculates: - * - * i->relative_volume := v - * i->soft_volume := i->relative_volume * i->volume_factor */ - - i->soft_volume.channels = i->sample_spec.channels; - - for (c = 0; c < i->sample_spec.channels; c++) { - i->relative_volume[c] = pa_sw_volume_to_linear(v->values[c]); - - i->soft_volume.values[c] = pa_sw_volume_from_linear( - i->relative_volume[c] * - pa_sw_volume_to_linear(i->volume_factor.values[c])); - } + if (absolute || !(i->sink->flags & PA_SINK_FLAT_VOLUME)) + *volume = i->volume; + else + *volume = i->reference_ratio; - /* We don't copy the data to the thread_info data. That's left for someone else to do */ + return volume; } /* Called from main context */ @@ -991,6 +1046,7 @@ void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save) { /* Called from main context */ pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); return i->muted; @@ -999,6 +1055,7 @@ pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) { /* Called from main thread */ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (p) pa_proplist_update(i->proplist, mode, p); @@ -1012,6 +1069,7 @@ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_p /* Called from main context */ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING); @@ -1020,6 +1078,7 @@ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) { /* Called from main context */ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE); @@ -1038,13 +1097,14 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { 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 && !strcmp(old, name)) + if (old && name && pa_streq(old, name)) return; if (name) @@ -1061,6 +1121,7 @@ void pa_sink_input_set_name(pa_sink_input *i, const char *name) { /* 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(); return i->actual_resample_method; } @@ -1068,6 +1129,7 @@ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { /* Called from main context */ pa_bool_t pa_sink_input_may_move(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); if (i->flags & PA_SINK_INPUT_DONT_MOVE) @@ -1084,6 +1146,7 @@ pa_bool_t pa_sink_input_may_move(pa_sink_input *i) { /* Called from main context */ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_sink_assert_ref(dest); @@ -1108,10 +1171,10 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { /* Called from main context */ int pa_sink_input_start_move(pa_sink_input *i) { pa_source_output *o, *p = NULL; - pa_sink *origin; int r; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(i->sink); @@ -1121,8 +1184,6 @@ int pa_sink_input_start_move(pa_sink_input *i) { if ((r = pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], i)) < 0) return r; - origin = i->sink; - /* Kill directly connected outputs */ while ((o = pa_idxset_first(i->direct_outputs, NULL))) { pa_assert(o != p); @@ -1136,24 +1197,15 @@ int pa_sink_input_start_move(pa_sink_input *i) { if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) pa_assert_se(i->sink->n_corked-- >= 1); - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - - /* Make the virtual volume relative */ - pa_sink_input_get_relative_volume(i, &i->virtual_volume); - - /* And reset the the relative volume */ - pa_sink_input_set_relative_volume(i, NULL); - + if (i->sink->flags & PA_SINK_FLAT_VOLUME) /* We might need to update the sink's volume if we are in flat * volume mode. */ - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE); - } + pa_sink_set_volume(i->sink, NULL, FALSE, FALSE); pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); pa_sink_update_status(i->sink); + pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map); i->sink = NULL; pa_sink_input_unref(i); @@ -1166,6 +1218,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_resampler *new_resampler; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(!i->sink); pa_sink_assert_ref(dest); @@ -1207,6 +1260,8 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { i->save_sink = save; pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL); + pa_cvolume_remap(&i->volume_factor_sink, &i->channel_map, &i->sink->channel_map); + if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) i->sink->n_corked++; @@ -1232,16 +1287,15 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_sink_update_status(dest); if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; + pa_cvolume remapped; - /* Make relative volume absolute again */ - pa_cvolume t = dest->reference_volume; - pa_cvolume_remap(&t, &dest->channel_map, &i->channel_map); - pa_sw_cvolume_multiply(&i->virtual_volume, &i->virtual_volume, &t); + /* Make relative volumes absolute */ + remapped = dest->reference_volume; + pa_cvolume_remap(&remapped, &dest->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&i->volume, &i->reference_ratio, &remapped); /* We might need to update the sink's volume if we are in flat volume mode. */ - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE); + pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume); } pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); @@ -1250,16 +1304,39 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { /* Notify everyone */ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i); + + if (i->volume_changed) + i->volume_changed(i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); return 0; } /* Called from main context */ +void pa_sink_input_fail_move(pa_sink_input *i) { + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!i->sink); + + /* Check if someone wants this sink input? */ + if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP) + return; + + if (i->moving) + i->moving(i, NULL); + + pa_sink_input_kill(i); +} + +/* Called from main context */ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { int r; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(i->sink); pa_sink_assert_ref(dest); @@ -1278,6 +1355,7 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { } if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) { + pa_sink_input_fail_move(i); pa_sink_input_unref(i); return r; } @@ -1290,7 +1368,9 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { /* Called from IO thread context */ void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) { pa_bool_t corking, uncorking; + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); if (state == i->thread_info.state) return; @@ -1428,6 +1508,7 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t /* Called from main thread */ pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED) return pa_atomic_load(&i->thread_info.drained) ? PA_SINK_INPUT_DRAINED : PA_SINK_INPUT_RUNNING; @@ -1438,6 +1519,7 @@ pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) { /* Called from IO context */ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) return pa_memblockq_is_empty(i->thread_info.render_memblockq); @@ -1446,7 +1528,13 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) { } /* Called from IO context */ -void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sample spec */, pa_bool_t rewrite, pa_bool_t flush, pa_bool_t dont_rewind_render) { +void pa_sink_input_request_rewind( + pa_sink_input *i, + size_t nbytes /* in our sample spec */, + pa_bool_t rewrite, + pa_bool_t flush, + pa_bool_t dont_rewind_render) { + size_t lbq; /* If 'rewrite' is TRUE the sink is rewound as far as requested @@ -1461,18 +1549,20 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sam * dont_rewind_render is TRUE then the render memblockq is not * rewound. */ - pa_sink_input_assert_ref(i); - - nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes); + /* nbytes = 0 means maximum rewind request */ -/* pa_log_debug("request rewrite %lu", (unsigned long) nbytes); */ + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(rewrite || flush); + pa_assert(!dont_rewind_render || !rewrite); /* We don't take rewind requests while we are corked */ if (i->thread_info.state == PA_SINK_INPUT_CORKED) return; - pa_assert(rewrite || flush); - pa_assert(!dont_rewind_render || !rewrite); + nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes); + + /* pa_log_debug("request rewrite %zu", nbytes); */ /* Calculate how much we can rewind locally without having to * touch the sink */ @@ -1492,6 +1582,7 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sam nbytes = pa_resampler_request(i->thread_info.resampler, nbytes); } + /* Remember how much we actually want to rewrite */ if (i->thread_info.rewrite_nbytes != (size_t) -1) { if (rewrite) { /* Make sure to not overwrite over underruns */ @@ -1528,8 +1619,11 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sam /* Called from main context */ pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(ret); + /* FIXME: Shouldn't access resampler object from main context! */ + pa_silence_memchunk_get( &i->core->silence_cache, i->core->mempool, @@ -1546,6 +1640,7 @@ void pa_sink_input_send_event(pa_sink_input *i, const char *event, pa_proplist * pa_sink_input_send_event_hook_data hook_data; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(event); if (!i->send_event) @@ -1701,29 +1796,42 @@ static void sink_input_rewind_ramp_info(pa_sink_input *i, size_t nbytes) { void pa_sink_input_set_volume_with_ramping(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute, pa_usec_t t){ pa_cvolume v; pa_volume_t previous_virtual_volume, target_virtual_volume; - pa_sink_input_assert_ref(i); + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(volume); pa_assert(pa_cvolume_valid(volume)); - pa_assert(pa_cvolume_compatible(volume, &i->sample_spec)); + pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec)); if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) { v = i->sink->reference_volume; pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); - volume = pa_sw_cvolume_multiply(&v, &v, volume); + + if (pa_cvolume_compatible(volume, &i->sample_spec)) + volume = pa_sw_cvolume_multiply(&v, &v, volume); + else + volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume)); + } else { + if (!pa_cvolume_compatible(volume, &i->sample_spec)) { + v = i->volume; + volume = pa_cvolume_scale(&v, pa_cvolume_max(volume)); + } } - if (pa_cvolume_equal(volume, &i->virtual_volume)) + if (pa_cvolume_equal(volume, &i->volume)) { + i->save_volume = i->save_volume || save; return; + } - previous_virtual_volume = pa_cvolume_avg(&i->virtual_volume); + previous_virtual_volume = pa_cvolume_avg(&i->volume); target_virtual_volume = pa_cvolume_avg(volume); + if (t > 0 && target_virtual_volume > 0) pa_log_debug("SetVolumeWithRamping: Virtual Volume From %u=%f to %u=%f\n", previous_virtual_volume, pa_sw_volume_to_linear(previous_virtual_volume), target_virtual_volume, pa_sw_volume_to_linear(target_virtual_volume)); - i->virtual_volume = *volume; + i->volume = *volume; i->save_volume = save; /* Set this flag before the following code modify i->thread_info.soft_volume */ @@ -1731,22 +1839,15 @@ void pa_sink_input_set_volume_with_ramping(pa_sink_input *i, const pa_cvolume *v pa_atomic_store(&i->before_ramping_v, 1); if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - /* We are in flat volume mode, so let's update all sink input * volumes and update the flat volume of the sink */ - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE, FALSE, FALSE); + pa_sink_set_volume(i->sink, NULL, TRUE, save); } else { - /* OK, we are in normal volume mode. The volume only affects * ourselves */ - pa_sink_input_set_relative_volume(i, volume); - - /* Hooks have the ability to play games with i->soft_volume */ - pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + set_real_ratio(i, volume); /* Copy the new soft_volume to the thread_info struct */ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); @@ -1755,6 +1856,10 @@ void pa_sink_input_set_volume_with_ramping(pa_sink_input *i, const pa_cvolume *v if (t > 0 && target_virtual_volume > 0) sink_input_set_ramping_info(i, previous_virtual_volume, target_virtual_volume, t); + /* The volume changed, let's tell people so */ + if (i->volume_changed) + i->volume_changed(i); + /* The virtual volume changed, let's tell people so */ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); } diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 036806fc..56ac3d60 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -43,6 +43,7 @@ typedef enum pa_sink_input_state { PA_SINK_INPUT_RUNNING, /*< The stream is alive and kicking */ PA_SINK_INPUT_CORKED, /*< The stream was corked on user request */ PA_SINK_INPUT_UNLINKED /*< The stream is dead */ + /* FIXME: we need a state for MOVING here */ } pa_sink_input_state_t; static inline pa_bool_t PA_SINK_INPUT_IS_LINKED(pa_sink_input_state_t x) { @@ -59,7 +60,8 @@ typedef enum pa_sink_input_flags { PA_SINK_INPUT_FIX_RATE = 64, PA_SINK_INPUT_FIX_CHANNELS = 128, PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND = 256, - PA_SINK_INPUT_FAIL_ON_SUSPEND = 512 + PA_SINK_INPUT_NO_CREATE_ON_SUSPEND = 512, + PA_SINK_INPUT_KILL_ON_SUSPEND = 1024 } pa_sink_input_flags_t; struct pa_sink_input { @@ -93,10 +95,14 @@ struct pa_sink_input { pa_sink_input *sync_prev, *sync_next; /* Also see http://pulseaudio.org/wiki/InternalVolumes */ - pa_cvolume virtual_volume; /* The volume clients are informed about */ - pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */ - double relative_volume[PA_CHANNELS_MAX]; /* The calculated volume relative to the sink volume as linear factors. */ - pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as relative_volume * volume_factor */ + pa_cvolume volume; /* The volume clients are informed about */ + pa_cvolume reference_ratio; /* The ratio of the stream's volume to the sink's reference volume */ + pa_cvolume real_ratio; /* The ratio of the stream's volume to the sink's real volume */ + pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */ + + pa_cvolume volume_factor_sink; /* A second volume factor in format of the sink this stream is connected to */ + pa_bool_t muted:1; /* if TRUE then the source we are connected to and/or the volume @@ -138,6 +144,10 @@ struct pa_sink_input { * from IO context. */ void (*update_sink_latency_range) (pa_sink_input *i); /* may be NULL */ + /* Called whenver the fixed latency of the sink changes, if there + * is one. Called from IO context. */ + void (*update_sink_fixed_latency) (pa_sink_input *i); /* may be NULL */ + /* If non-NULL this function is called when the input is first * connected to a sink or when the rtpoll/asyncmsgq fields * change. You usually don't need to implement this function @@ -160,7 +170,9 @@ struct pa_sink_input { /* If non-NULL called whenever the sink input is moved to a new * sink. Called from main context after the sink input has been * detached from the old sink and before it has been attached to - * the new sink. */ + * the new sink. If dest is NULL the move was executed in two + * phases and the second one failed; the stream will be destroyed + * after this call. */ void (*moving) (pa_sink_input *i, pa_sink *dest); /* may be NULL */ /* Supposed to unlink and destroy this stream. Called from main @@ -183,8 +195,16 @@ struct pa_sink_input { pa_bool_t (*may_move_to) (pa_sink_input *i, pa_sink *s); /* may be NULL */ /* If non-NULL this function is used to dispatch asynchronous - * control events. */ - void (*send_event)(pa_sink_input *i, const char *event, pa_proplist* data); + * control events. Called from main context. */ + void (*send_event)(pa_sink_input *i, const char *event, pa_proplist* data); /* may be NULL */ + + /* If non-NULL this function is called whenever the sink input + * volume changes. Called from main context */ + void (*volume_changed)(pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called whenever the sink input + * mute status changes. Called from main context */ + void (*mute_changed)(pa_sink_input *i); /* may be NULL */ struct { pa_sink_input_state_t state; @@ -195,7 +215,7 @@ struct pa_sink_input { pa_bool_t attached:1; /* True only between ->attach() and ->detach() calls */ - /* 0: rewrite nothing, (size_t) -1: rewrite everything, otherwise how many bytes to rewrite */ + /* rewrite_nbytes: 0: rewrite nothing, (size_t) -1: rewrite everything, otherwise how many bytes to rewrite */ pa_bool_t rewrite_flush:1, dont_rewind_render:1; size_t rewrite_nbytes; uint64_t underrun_for, playing_for; @@ -233,7 +253,7 @@ struct pa_sink_input { void *userdata; }; -PA_DECLARE_CLASS(pa_sink_input); +PA_DECLARE_PUBLIC_CLASS(pa_sink_input); #define PA_SINK_INPUT(o) pa_sink_input_cast(o) enum { @@ -255,6 +275,8 @@ typedef struct pa_sink_input_send_event_hook_data { } pa_sink_input_send_event_hook_data; typedef struct pa_sink_input_new_data { + pa_sink_input_flags_t flags; + pa_proplist *proplist; const char *driver; @@ -270,13 +292,13 @@ typedef struct pa_sink_input_new_data { pa_sample_spec sample_spec; pa_channel_map channel_map; - pa_cvolume volume, volume_factor; + pa_cvolume volume, volume_factor, volume_factor_sink; pa_bool_t muted:1; pa_bool_t sample_spec_is_set:1; pa_bool_t channel_map_is_set:1; - pa_bool_t volume_is_set:1, volume_factor_is_set:1; + pa_bool_t volume_is_set:1, volume_factor_is_set:1, volume_factor_sink_is_set:1; pa_bool_t muted_is_set:1; pa_bool_t volume_is_absolute:1; @@ -289,6 +311,7 @@ void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map); void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume); void pa_sink_input_new_data_apply_volume_factor(pa_sink_input_new_data *data, const pa_cvolume *volume_factor); +void pa_sink_input_new_data_apply_volume_factor_sink(pa_sink_input_new_data *data, const pa_cvolume *volume_factor); void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute); void pa_sink_input_new_data_done(pa_sink_input_new_data *data); @@ -297,8 +320,7 @@ void pa_sink_input_new_data_done(pa_sink_input_new_data *data); int pa_sink_input_new( pa_sink_input **i, pa_core *core, - pa_sink_input_new_data *data, - pa_sink_input_flags_t flags); + pa_sink_input_new_data *data); void pa_sink_input_put(pa_sink_input *i); void pa_sink_input_unlink(pa_sink_input* i); @@ -320,6 +342,10 @@ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b); int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate); +/* This returns the sink's fields converted into out sample type */ +size_t pa_sink_input_get_max_rewind(pa_sink_input *i); +size_t pa_sink_input_get_max_request(pa_sink_input *i); + /* Callable by everyone from main thread*/ /* External code may request disconnection with this function */ @@ -330,8 +356,6 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency); void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute); pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute); -pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v); - void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save); pa_bool_t pa_sink_input_get_mute(pa_sink_input *i); @@ -350,6 +374,7 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest); /* may thi * new sink */ int pa_sink_input_start_move(pa_sink_input *i); int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save); +void pa_sink_input_fail_move(pa_sink_input *i); pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i); @@ -373,8 +398,8 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i); pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret); -/* To be used by sink.c only */ -void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v); +#define pa_sink_input_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state)) /* Volume ramping*/ void pa_sink_input_set_volume_with_ramping(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute, pa_usec_t t); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 29500718..24fad34d 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -52,14 +52,14 @@ #define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) #define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) -static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject); +PA_DEFINE_PUBLIC_CLASS(pa_sink, pa_msgobject); static void sink_free(pa_object *s); pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) { pa_assert(data); - memset(data, 0, sizeof(*data)); + pa_zero(*data); data->proplist = pa_proplist_new(); return data; @@ -177,6 +177,7 @@ pa_sink* pa_sink_new( pa_assert(core); pa_assert(data); pa_assert(data->name); + pa_assert_ctl_context(); s = pa_msgobject_new(pa_sink); @@ -211,7 +212,7 @@ pa_sink* pa_sink_new( pa_cvolume_reset(&data->volume, data->sample_spec.channels); pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); - pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels); + pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec)); if (!data->muted_is_set) data->muted = FALSE; @@ -235,6 +236,7 @@ pa_sink* pa_sink_new( s->core = core; s->state = PA_SINK_INIT; s->flags = flags; + s->priority = 0; s->suspend_cause = 0; s->name = pa_xstrdup(name); s->proplist = pa_proplist_copy(data->proplist); @@ -242,26 +244,25 @@ pa_sink* pa_sink_new( s->module = data->module; s->card = data->card; + s->priority = pa_device_init_priority(s->proplist); + s->sample_spec = data->sample_spec; s->channel_map = data->channel_map; s->inputs = pa_idxset_new(NULL, NULL); s->n_corked = 0; - s->reference_volume = s->virtual_volume = data->volume; + s->reference_volume = s->real_volume = data->volume; pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); s->base_volume = PA_VOLUME_NORM; s->n_volume_steps = PA_VOLUME_NORM+1; s->muted = data->muted; s->refresh_volume = s->refresh_muted = FALSE; - s->fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; - reset_callbacks(s); s->userdata = NULL; s->asyncmsgq = NULL; - s->rtpoll = NULL; /* As a minor optimization we just steal the list instead of * copying it here */ @@ -294,6 +295,7 @@ pa_sink* pa_sink_new( &s->sample_spec, 0); + s->thread_info.rtpoll = NULL; s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); s->thread_info.soft_volume = s->soft_volume; s->thread_info.soft_muted = s->muted; @@ -306,7 +308,9 @@ pa_sink* pa_sink_new( s->thread_info.requested_latency = 0; s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY; + s->thread_info.fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + /* FIXME: This should probably be moved to pa_sink_put() */ pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); if (s->card) @@ -348,6 +352,7 @@ pa_sink* pa_sink_new( s->monitor_source->monitor_of = s; pa_source_set_latency_range(s->monitor_source, s->thread_info.min_latency, s->thread_info.max_latency); + pa_source_set_fixed_latency(s->monitor_source, s->thread_info.fixed_latency); pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); return s; @@ -360,6 +365,7 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { pa_sink_state_t original_state; pa_assert(s); + pa_assert_ctl_context(); if (s->state == state) return 0; @@ -396,9 +402,9 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { /* We're suspending or resuming, tell everyone about it */ - for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) + PA_IDXSET_FOREACH(i, s->inputs, idx) if (s->state == PA_SINK_SUSPENDED && - (i->flags & PA_SINK_INPUT_FAIL_ON_SUSPEND)) + (i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND)) pa_sink_input_kill(i); else if (i->suspend) i->suspend(i, state == PA_SINK_SUSPENDED); @@ -413,12 +419,12 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { /* Called from main context */ void pa_sink_put(pa_sink* s) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(s->state == PA_SINK_INIT); /* The following fields must be initialized properly when calling _put() */ pa_assert(s->asyncmsgq); - pa_assert(s->rtpoll); pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); /* Generally, flags should be initialized via pa_sink_new(). As a @@ -431,16 +437,21 @@ void pa_sink_put(pa_sink* s) { if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes) s->flags |= PA_SINK_FLAT_VOLUME; + /* We assume that if the sink implementor changed the default + * volume he did so in real_volume, because that is the usual + * place where he is supposed to place his changes. */ + s->reference_volume = s->real_volume; + s->thread_info.soft_volume = s->soft_volume; s->thread_info.soft_muted = s->muted; pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SINK_DECIBEL_VOLUME)); pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1); - pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->fixed_latency != 0)); + pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0)); pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY)); pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY)); - pa_assert(s->monitor_source->fixed_latency == s->fixed_latency); + pa_assert(s->monitor_source->thread_info.fixed_latency == s->thread_info.fixed_latency); pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency); pa_assert(s->monitor_source->thread_info.max_latency == s->thread_info.max_latency); @@ -458,6 +469,7 @@ void pa_sink_unlink(pa_sink* s) { pa_sink_input *i, *j = NULL; pa_assert(s); + pa_assert_ctl_context(); /* Please note that pa_sink_unlink() does more than simply * reversing pa_sink_put(). It also undoes the registrations @@ -507,6 +519,7 @@ static void sink_free(pa_object *o) { pa_sink_input *i; pa_assert(s); + pa_assert_ctl_context(); pa_assert(pa_sink_refcnt(s) == 0); if (PA_SINK_IS_LINKED(s->state)) @@ -547,9 +560,10 @@ static void sink_free(pa_object *o) { pa_xfree(s); } -/* Called from main context */ +/* Called from main context, and not while the IO thread is active, please */ void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); s->asyncmsgq = q; @@ -557,11 +571,32 @@ void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { pa_source_set_asyncmsgq(s->monitor_source, q); } -/* Called from main context */ +/* Called from main context, and not while the IO thread is active, please */ +void pa_sink_update_flags(pa_sink *s, pa_sink_flags_t mask, pa_sink_flags_t value) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (mask == 0) + return; + + /* For now, allow only a minimal set of flags to be changed. */ + pa_assert((mask & ~(PA_SINK_DYNAMIC_LATENCY|PA_SINK_LATENCY)) == 0); + + s->flags = (s->flags & ~mask) | (value & mask); + + pa_source_update_flags(s->monitor_source, + ((mask & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | + ((mask & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0), + ((value & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | + ((value & PA_SINK_DYNAMIC_LATENCY) ? PA_SINK_DYNAMIC_LATENCY : 0)); +} + +/* Called from IO context, or before _put() from main context */ void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); - s->rtpoll = p; + s->thread_info.rtpoll = p; if (s->monitor_source) pa_source_set_rtpoll(s->monitor_source, p); @@ -570,6 +605,7 @@ void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { /* Called from main context */ int pa_sink_update_status(pa_sink*s) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); if (s->state == PA_SINK_SUSPENDED) @@ -581,6 +617,7 @@ int pa_sink_update_status(pa_sink*s) { /* Called from main context */ int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(cause != 0); @@ -609,6 +646,7 @@ pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q) { uint32_t idx; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); if (!q) @@ -633,12 +671,13 @@ void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, pa_bool_t save) { pa_sink_input *i; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(q); while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) { if (pa_sink_input_finish_move(i, s, save) < 0) - pa_sink_input_kill(i); + pa_sink_input_fail_move(i); pa_sink_input_unref(i); } @@ -649,13 +688,13 @@ void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, pa_bool_t save) { /* Called from main context */ void pa_sink_move_all_fail(pa_queue *q) { pa_sink_input *i; + + pa_assert_ctl_context(); pa_assert(q); while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) { - if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_OK) { - pa_sink_input_kill(i); - pa_sink_input_unref(i); - } + pa_sink_input_fail_move(i); + pa_sink_input_unref(i); } pa_queue_free(q, NULL, NULL); @@ -665,11 +704,15 @@ void pa_sink_move_all_fail(pa_queue *q) { void pa_sink_process_rewind(pa_sink *s, size_t nbytes) { pa_sink_input *i; void *state = NULL; + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); /* If nobody requested this and this is actually no real rewind - * then we can short cut this */ + * then we can short cut this. Please note that this means that + * not all rewind requests triggered upstream will always be + * translated in actual requests! */ if (!s->thread_info.rewind_requested && nbytes <= 0) return; @@ -682,7 +725,7 @@ void pa_sink_process_rewind(pa_sink *s, size_t nbytes) { if (nbytes > 0) pa_log_debug("Processing rewind..."); - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { pa_sink_input_assert_ref(i); pa_sink_input_process_rewind(i, nbytes); } @@ -700,6 +743,7 @@ static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, uns size_t mixlength = *length; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(info); while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) { @@ -734,18 +778,19 @@ static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, uns /* Called from IO thread context */ static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned n, pa_memchunk *result) { pa_sink_input *i; - void *state = NULL; + void *state; unsigned p = 0; unsigned n_unreffed = 0; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(result); pa_assert(result->memblock); pa_assert(result->length > 0); /* We optimize for the case where the order of the inputs has not changed */ - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { unsigned j; pa_mix_info* m = NULL; @@ -834,12 +879,11 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { size_t block_size_max; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); pa_assert(pa_frame_aligned(length, &s->sample_spec)); pa_assert(result); - pa_sink_ref(s); - pa_assert(!s->thread_info.rewind_requested); pa_assert(s->thread_info.rewind_nbytes == 0); @@ -850,6 +894,8 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { return; } + pa_sink_ref(s); + if (length <= 0) length = pa_frame_align(MIX_BUFFER_LENGTH, &s->sample_spec); @@ -880,18 +926,16 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); - if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { - if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) { - pa_memblock_unref(result->memblock); - pa_silence_memchunk_get(&s->core->silence_cache, - s->core->mempool, - result, - &s->sample_spec, - result->length); - } else { - pa_memchunk_make_writable(result, 0); - pa_volume_memchunk(result, &s->sample_spec, &volume); - } + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) { + pa_memblock_unref(result->memblock); + pa_silence_memchunk_get(&s->core->silence_cache, + s->core->mempool, + result, + &s->sample_spec, + result->length); + } else if (!pa_cvolume_is_norm(&volume)) { + pa_memchunk_make_writable(result, 0); + pa_volume_memchunk(result, &s->sample_spec, &volume); } } else { void *ptr; @@ -920,14 +964,13 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { size_t length, block_size_max; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); pa_assert(target); pa_assert(target->memblock); pa_assert(target->length > 0); pa_assert(pa_frame_aligned(target->length, &s->sample_spec)); - pa_sink_ref(s); - pa_assert(!s->thread_info.rewind_requested); pa_assert(s->thread_info.rewind_nbytes == 0); @@ -936,6 +979,8 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { return; } + pa_sink_ref(s); + length = target->length; block_size_max = pa_mempool_block_size_max(s->core->mempool); if (length > block_size_max) @@ -1003,17 +1048,23 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { size_t l, d; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); pa_assert(target); pa_assert(target->memblock); pa_assert(target->length > 0); pa_assert(pa_frame_aligned(target->length, &s->sample_spec)); - pa_sink_ref(s); - pa_assert(!s->thread_info.rewind_requested); pa_assert(s->thread_info.rewind_nbytes == 0); + if (s->thread_info.state == PA_SINK_SUSPENDED) { + pa_silence_memchunk(target, &s->sample_spec); + return; + } + + pa_sink_ref(s); + l = target->length; d = 0; while (l > 0) { @@ -1032,91 +1083,31 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { /* Called from IO thread context */ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { - pa_mix_info info[MAX_MIX_CHANNELS]; - size_t length1st = length; - unsigned n; - pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); pa_assert(length > 0); pa_assert(pa_frame_aligned(length, &s->sample_spec)); pa_assert(result); - pa_sink_ref(s); - pa_assert(!s->thread_info.rewind_requested); pa_assert(s->thread_info.rewind_nbytes == 0); - pa_assert(length > 0); - - n = fill_mix_info(s, &length1st, info, MAX_MIX_CHANNELS); - - if (n == 0) { - pa_silence_memchunk_get(&s->core->silence_cache, - s->core->mempool, - result, - &s->sample_spec, - length1st); - } else if (n == 1) { - pa_cvolume volume; - - *result = info[0].chunk; - pa_memblock_ref(result->memblock); - - if (result->length > length) - result->length = length; - - pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); - - if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { - if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) { - pa_memblock_unref(result->memblock); - pa_silence_memchunk_get(&s->core->silence_cache, - s->core->mempool, - result, - &s->sample_spec, - result->length); - } else { - pa_memchunk_make_writable(result, length); - pa_volume_memchunk(result, &s->sample_spec, &volume); - } - } - } else { - void *ptr; - - result->index = 0; - result->memblock = pa_memblock_new(s->core->mempool, length); - - ptr = pa_memblock_acquire(result->memblock); - - result->length = pa_mix(info, n, - (uint8_t*) ptr + result->index, length1st, - &s->sample_spec, - &s->thread_info.soft_volume, - s->thread_info.soft_muted); + pa_sink_ref(s); - pa_memblock_release(result->memblock); - } - - inputs_drop(s, info, n, result); + pa_sink_render(s, length, result); if (result->length < length) { pa_memchunk chunk; - size_t l, d; + pa_memchunk_make_writable(result, length); - l = length - result->length; - d = result->index + result->length; - while (l > 0) { - chunk = *result; - chunk.index = d; - chunk.length = l; + chunk.memblock = result->memblock; + chunk.index = result->index + result->length; + chunk.length = length - result->length; - pa_sink_render_into(s, &chunk); + pa_sink_render_into_full(s, &chunk); - d += chunk.length; - l -= chunk.length; - } result->length = length; } @@ -1128,6 +1119,7 @@ pa_usec_t pa_sink_get_latency(pa_sink *s) { pa_usec_t usec = 0; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); /* The returned value is supposed to be in the time domain of the sound card! */ @@ -1149,6 +1141,7 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) { pa_msgobject *o; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); /* The returned value is supposed to be in the time domain of the sound card! */ @@ -1161,7 +1154,7 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) { o = PA_MSGOBJECT(s); - /* We probably should make this a proper vtable callback instead of going through process_msg() */ + /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */ if (o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) return -1; @@ -1169,108 +1162,149 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) { return usec; } -static void compute_new_soft_volume(pa_sink_input *i, const pa_cvolume *new_volume) { - unsigned c; +/* Called from main context */ +static void compute_reference_ratios(pa_sink *s) { + uint32_t idx; + pa_sink_input *i; - pa_sink_input_assert_ref(i); - pa_assert(new_volume->channels == i->sample_spec.channels); + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(s->flags & PA_SINK_FLAT_VOLUME); - /* - * This basically calculates: - * - * i->relative_volume := i->virtual_volume / new_volume - * i->soft_volume := i->relative_volume * i->volume_factor - */ + PA_IDXSET_FOREACH(i, s->inputs, idx) { + unsigned c; + pa_cvolume remapped; - /* The new sink volume passed in here must already be remapped to - * the sink input's channel map! */ + /* + * Calculates the reference volume from the sink's reference + * volume. This basically calculates: + * + * i->reference_ratio = i->volume / s->reference_volume + */ - i->soft_volume.channels = i->sample_spec.channels; + remapped = s->reference_volume; + pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map); - for (c = 0; c < i->sample_spec.channels; c++) + i->reference_ratio.channels = i->sample_spec.channels; - if (new_volume->values[c] <= PA_VOLUME_MUTED) - /* We leave i->relative_volume untouched */ - i->soft_volume.values[c] = PA_VOLUME_MUTED; - else { - i->relative_volume[c] = - pa_sw_volume_to_linear(i->virtual_volume.values[c]) / - pa_sw_volume_to_linear(new_volume->values[c]); + for (c = 0; c < i->sample_spec.channels; c++) { - i->soft_volume.values[c] = pa_sw_volume_from_linear( - i->relative_volume[c] * - pa_sw_volume_to_linear(i->volume_factor.values[c])); + /* We don't update when the sink volume is 0 anyway */ + if (remapped.values[c] <= PA_VOLUME_MUTED) + continue; + + /* Don't update the reference ratio unless necessary */ + if (pa_sw_volume_multiply( + i->reference_ratio.values[c], + remapped.values[c]) == i->volume.values[c]) + continue; + + i->reference_ratio.values[c] = pa_sw_volume_divide( + i->volume.values[c], + remapped.values[c]); } + } +} + +/* Called from main context */ +static void compute_real_ratios(pa_sink *s) { + pa_sink_input *i; + uint32_t idx; - /* Hooks have the ability to play games with i->soft_volume */ - pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(s->flags & PA_SINK_FLAT_VOLUME); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + unsigned c; + pa_cvolume remapped; + + /* + * This basically calculates: + * + * i->real_ratio := i->volume / s->real_volume + * i->soft_volume := i->real_ratio * i->volume_factor + */ + + remapped = s->real_volume; + pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map); + + i->real_ratio.channels = i->sample_spec.channels; + i->soft_volume.channels = i->sample_spec.channels; + + for (c = 0; c < i->sample_spec.channels; c++) { + + if (remapped.values[c] <= PA_VOLUME_MUTED) { + /* We leave i->real_ratio untouched */ + i->soft_volume.values[c] = PA_VOLUME_MUTED; + continue; + } + + /* Don't lose accuracy unless necessary */ + if (pa_sw_volume_multiply( + i->real_ratio.values[c], + remapped.values[c]) != i->volume.values[c]) + + i->real_ratio.values[c] = pa_sw_volume_divide( + i->volume.values[c], + remapped.values[c]); - /* We don't copy the soft_volume to the thread_info data - * here. That must be done by the caller */ + i->soft_volume.values[c] = pa_sw_volume_multiply( + i->real_ratio.values[c], + i->volume_factor.values[c]); + } + + /* We don't copy the soft_volume to the thread_info data + * here. That must be done by the caller */ + } } /* Called from main thread */ -void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { +static void compute_real_volume(pa_sink *s) { pa_sink_input *i; uint32_t idx; pa_sink_assert_ref(s); - pa_assert(new_volume); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(s->flags & PA_SINK_FLAT_VOLUME); - /* This is called whenever a sink input volume changes or a sink - * input is added/removed and we might need to fix up the sink - * volume accordingly. Please note that we don't actually update - * the sinks volume here, we only return how it needs to be - * updated. The caller should then call pa_sink_set_volume().*/ + /* This determines the maximum volume of all streams and sets + * s->real_volume accordingly. */ if (pa_idxset_isempty(s->inputs)) { /* In the special case that we have no sink input we leave the * volume unmodified. */ - *new_volume = s->reference_volume; + s->real_volume = s->reference_volume; return; } - pa_cvolume_mute(new_volume, s->channel_map.channels); + pa_cvolume_mute(&s->real_volume, s->channel_map.channels); /* First let's determine the new maximum volume of all inputs * connected to this sink */ - for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - unsigned c; - pa_cvolume remapped_volume; - - remapped_volume = i->virtual_volume; - pa_cvolume_remap(&remapped_volume, &i->channel_map, &s->channel_map); + PA_IDXSET_FOREACH(i, s->inputs, idx) { + pa_cvolume remapped; - for (c = 0; c < new_volume->channels; c++) - if (remapped_volume.values[c] > new_volume->values[c]) - new_volume->values[c] = remapped_volume.values[c]; + remapped = i->volume; + pa_cvolume_remap(&remapped, &i->channel_map, &s->channel_map); + pa_cvolume_merge(&s->real_volume, &s->real_volume, &remapped); } - /* Then, let's update the soft volumes of all inputs connected - * to this sink */ - for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - pa_cvolume remapped_new_volume; - - remapped_new_volume = *new_volume; - pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); - compute_new_soft_volume(i, &remapped_new_volume); - - /* We don't copy soft_volume to the thread_info data here - * (i.e. issue PA_SINK_INPUT_MESSAGE_SET_VOLUME) because we - * want the update to be atomically with the sink volume - * update, hence we do it within the pa_sink_set_volume() call - * below */ - } + /* Then, let's update the real ratios/soft volumes of all inputs + * connected to this sink */ + compute_real_ratios(s); } /* Called from main thread */ -void pa_sink_propagate_flat_volume(pa_sink *s) { +static void propagate_reference_volume(pa_sink *s) { pa_sink_input *i; uint32_t idx; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(s->flags & PA_SINK_FLAT_VOLUME); @@ -1278,63 +1312,88 @@ void pa_sink_propagate_flat_volume(pa_sink *s) { * caused by a sink input volume change. We need to fix up the * sink input volumes accordingly */ - for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - pa_cvolume sink_volume, new_virtual_volume; - unsigned c; - - /* This basically calculates i->virtual_volume := i->relative_volume * s->virtual_volume */ + PA_IDXSET_FOREACH(i, s->inputs, idx) { + pa_cvolume old_volume, remapped; - sink_volume = s->virtual_volume; - pa_cvolume_remap(&sink_volume, &s->channel_map, &i->channel_map); + old_volume = i->volume; - for (c = 0; c < i->sample_spec.channels; c++) - new_virtual_volume.values[c] = pa_sw_volume_from_linear( - i->relative_volume[c] * - pa_sw_volume_to_linear(sink_volume.values[c])); + /* This basically calculates: + * + * i->volume := s->reference_volume * i->reference_ratio */ - new_virtual_volume.channels = i->sample_spec.channels; + remapped = s->reference_volume; + pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio); - if (!pa_cvolume_equal(&new_virtual_volume, &i->virtual_volume)) { - i->virtual_volume = new_virtual_volume; + /* The volume changed, let's tell people so */ + if (!pa_cvolume_equal(&old_volume, &i->volume)) { - /* Hmm, the soft volume might no longer actually match - * what has been chosen as new virtual volume here, - * especially when the old volume was - * PA_VOLUME_MUTED. Hence let's recalculate the soft - * volumes here. */ - compute_new_soft_volume(i, &sink_volume); + if (i->volume_changed) + i->volume_changed(i); - /* The virtual volume changed, let's tell people so */ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); } } - - /* If the soft_volume of any of the sink inputs got changed, let's - * make sure the thread copies are synced up. */ - pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SYNC_VOLUMES, NULL, 0, NULL) == 0); } /* Called from main thread */ -void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg, pa_bool_t become_reference, pa_bool_t save) { - pa_bool_t virtual_volume_changed; +void pa_sink_set_volume( + pa_sink *s, + const pa_cvolume *volume, + pa_bool_t send_msg, + pa_bool_t save) { + + pa_cvolume old_reference_volume; + pa_bool_t reference_changed; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); - pa_assert(volume); - pa_assert(pa_cvolume_valid(volume)); - pa_assert(pa_cvolume_compatible(volume, &s->sample_spec)); + pa_assert(!volume || pa_cvolume_valid(volume)); + pa_assert(volume || (s->flags & PA_SINK_FLAT_VOLUME)); + pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec)); - virtual_volume_changed = !pa_cvolume_equal(volume, &s->virtual_volume); - s->virtual_volume = *volume; - s->save_volume = (!virtual_volume_changed && s->save_volume) || save; + /* As a special exception we accept mono volumes on all sinks -- + * even on those with more complex channel maps */ - if (become_reference) - s->reference_volume = s->virtual_volume; + /* If volume is NULL we synchronize the sink's real and reference + * volumes with the stream volumes. If it is not NULL we update + * the reference_volume with it. */ - /* Propagate this volume change back to the inputs */ - if (virtual_volume_changed) - if (propagate && (s->flags & PA_SINK_FLAT_VOLUME)) - pa_sink_propagate_flat_volume(s); + old_reference_volume = s->reference_volume; + + if (volume) { + + if (pa_cvolume_compatible(volume, &s->sample_spec)) + s->reference_volume = *volume; + else + pa_cvolume_scale(&s->reference_volume, pa_cvolume_max(volume)); + + if (s->flags & PA_SINK_FLAT_VOLUME) { + /* OK, propagate this volume change back to the inputs */ + propagate_reference_volume(s); + + /* And now recalculate the real volume */ + compute_real_volume(s); + } else + s->real_volume = s->reference_volume; + + } else { + pa_assert(s->flags & PA_SINK_FLAT_VOLUME); + + /* Ok, let's determine the new real volume */ + compute_real_volume(s); + + /* Let's 'push' the reference volume if necessary */ + pa_cvolume_merge(&s->reference_volume, &s->reference_volume, &s->real_volume); + + /* We need to fix the reference ratios of all streams now that + * we changed the reference volume */ + compute_reference_ratios(s); + } + + reference_changed = !pa_cvolume_equal(&old_reference_volume, &s->reference_volume); + s->save_volume = (!reference_changed && s->save_volume) || save; if (s->set_volume) { /* If we have a function set_volume(), then we do not apply a @@ -1347,72 +1406,133 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat } else /* If we have no function set_volume(), then the soft volume * becomes the virtual volume */ - s->soft_volume = s->virtual_volume; + s->soft_volume = s->real_volume; /* This tells the sink that soft and/or virtual volume changed */ - if (sendmsg) + if (send_msg) pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0); - if (virtual_volume_changed) + if (reference_changed) pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } /* Called from main thread. Only to be called by sink implementor */ void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) { pa_sink_assert_ref(s); - pa_assert(volume); + pa_assert_ctl_context(); - s->soft_volume = *volume; + if (!volume) + pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); + else + s->soft_volume = *volume; if (PA_SINK_IS_LINKED(s->state)) pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0); else - s->thread_info.soft_volume = *volume; + s->thread_info.soft_volume = s->soft_volume; } -/* Called from main thread */ -const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh, pa_bool_t reference) { +static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume) { + pa_sink_input *i; + uint32_t idx; + pa_cvolume old_reference_volume; + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); - if (s->refresh_volume || force_refresh) { - struct pa_cvolume old_virtual_volume = s->virtual_volume; + /* This is called when the hardware's real volume changes due to + * some external event. We copy the real volume into our + * reference volume and then rebuild the stream volumes based on + * i->real_ratio which should stay fixed. */ - if (s->get_volume) - s->get_volume(s); + if (pa_cvolume_equal(old_real_volume, &s->real_volume)) + return; - pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0); + old_reference_volume = s->reference_volume; - if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) { + /* 1. Make the real volume the reference volume */ + s->reference_volume = s->real_volume; - s->reference_volume = s->virtual_volume; + if (s->flags & PA_SINK_FLAT_VOLUME) { - if (s->flags & PA_SINK_FLAT_VOLUME) - pa_sink_propagate_flat_volume(s); + PA_IDXSET_FOREACH(i, s->inputs, idx) { + pa_cvolume old_volume, remapped; - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + old_volume = i->volume; + + /* 2. Since the sink's reference and real volumes are equal + * now our ratios should be too. */ + i->reference_ratio = i->real_ratio; + + /* 3. Recalculate the new stream reference volume based on the + * reference ratio and the sink's reference volume. + * + * This basically calculates: + * + * i->volume = s->reference_volume * i->reference_ratio + * + * This is identical to propagate_reference_volume() */ + remapped = s->reference_volume; + pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio); + + /* Notify if something changed */ + if (!pa_cvolume_equal(&old_volume, &i->volume)) { + + if (i->volume_changed) + i->volume_changed(i); + + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } } } - return reference ? &s->reference_volume : &s->virtual_volume; + /* Something got changed in the hardware. It probably makes sense + * to save changed hw settings given that hw volume changes not + * triggered by PA are almost certainly done by the user. */ + s->save_volume = TRUE; + + if (!pa_cvolume_equal(&old_reference_volume, &s->reference_volume)) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } /* Called from main thread */ -void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume, pa_bool_t save) { +const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); - /* The sink implementor may call this if the volume changed to make sure everyone is notified */ - if (pa_cvolume_equal(&s->virtual_volume, new_volume)) { - s->save_volume = s->save_volume || save; - return; + if (s->refresh_volume || force_refresh) { + struct pa_cvolume old_real_volume; + + old_real_volume = s->real_volume; + + if (s->get_volume) + s->get_volume(s); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0); + + propagate_real_volume(s, &old_real_volume); } - s->reference_volume = s->virtual_volume = *new_volume; - s->save_volume = save; + return &s->reference_volume; +} + +/* Called from main thread */ +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_real_volume) { + pa_cvolume old_real_volume; - if (s->flags & PA_SINK_FLAT_VOLUME) - pa_sink_propagate_flat_volume(s); + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + /* The sink implementor may call this if the volume changed to make sure everyone is notified */ + + old_real_volume = s->real_volume; + s->real_volume = *new_real_volume; + + propagate_real_volume(s, &old_real_volume); } /* Called from main thread */ @@ -1420,6 +1540,7 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute, pa_bool_t save) { pa_bool_t old_muted; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); old_muted = s->muted; @@ -1439,6 +1560,8 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute, pa_bool_t save) { pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); if (s->refresh_muted || force_refresh) { pa_bool_t old_muted = s->muted; @@ -1449,6 +1572,8 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) { pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0); if (old_muted != s->muted) { + s->save_muted = TRUE; + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); /* Make sure the soft mute status stays in sync */ @@ -1460,18 +1585,18 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) { } /* Called from main thread */ -void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted, pa_bool_t save) { +void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); /* The sink implementor may call this if the volume changed to make sure everyone is notified */ - if (s->muted == new_muted) { - s->save_muted = s->save_muted || save; + if (s->muted == new_muted) return; - } s->muted = new_muted; - s->save_muted = save; + s->save_muted = TRUE; pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } @@ -1479,6 +1604,7 @@ void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted, pa_bool_t save) { /* Called from main thread */ pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (p) pa_proplist_update(s->proplist, mode, p); @@ -1492,16 +1618,18 @@ pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist } /* Called from main thread */ +/* FIXME -- this should be dropped and be merged into pa_sink_update_proplist() */ void pa_sink_set_description(pa_sink *s, const char *description) { const char *old; pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) return; old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); - if (old && description && !strcmp(old, description)) + if (old && description && pa_streq(old, description)) return; if (description) @@ -1528,6 +1656,7 @@ unsigned pa_sink_linked_by(pa_sink *s) { unsigned ret; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); ret = pa_idxset_size(s->inputs); @@ -1546,6 +1675,7 @@ unsigned pa_sink_used_by(pa_sink *s) { unsigned ret; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); ret = pa_idxset_size(s->inputs); @@ -1564,6 +1694,7 @@ unsigned pa_sink_check_suspend(pa_sink *s) { uint32_t idx; pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (!PA_SINK_IS_LINKED(s->state)) return 0; @@ -1597,8 +1728,9 @@ static void sync_input_volumes_within_thread(pa_sink *s) { void *state = NULL; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); - while ((i = PA_SINK_INPUT(pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))) { + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { if (pa_atomic_load(&i->before_ramping_v)) i->thread_info.future_soft_volume = i->soft_volume; @@ -1705,7 +1837,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index))) pa_sink_input_unref(i); - pa_sink_invalidate_requested_latency(s); + pa_sink_invalidate_requested_latency(s, TRUE); pa_sink_request_rewind(s, (size_t) -1); /* In flat volume mode we need to update the volume as @@ -1727,10 +1859,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse size_t sink_nbytes, total_nbytes; /* Get the latency of the sink */ - if (!(s->flags & PA_SINK_LATENCY) || - PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - usec = 0; - + usec = pa_sink_get_latency_within_thread(s); sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec); total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq); @@ -1751,7 +1880,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index))) pa_sink_input_unref(i); - pa_sink_invalidate_requested_latency(s); + pa_sink_invalidate_requested_latency(s, TRUE); pa_log_debug("Requesting rewind due to started move"); pa_sink_request_rewind(s, (size_t) -1); @@ -1789,10 +1918,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse size_t nbytes; /* Get the latency of the sink */ - if (!(s->flags & PA_SINK_LATENCY) || - PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - usec = 0; - + usec = pa_sink_get_latency_within_thread(s); nbytes = pa_usec_to_bytes(usec, &s->sample_spec); if (nbytes > 0) @@ -1880,6 +2006,9 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse pa_usec_t *usec = userdata; *usec = pa_sink_get_requested_latency_within_thread(s); + /* Yes, that's right, the IO thread will see -1 when no + * explicit requested latency is configured, the main + * thread will see max_latency */ if (*usec == (pa_usec_t) -1) *usec = s->thread_info.max_latency; @@ -1903,6 +2032,16 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse return 0; } + case PA_SINK_MESSAGE_GET_FIXED_LATENCY: + + *((pa_usec_t*) userdata) = s->thread_info.fixed_latency; + return 0; + + case PA_SINK_MESSAGE_SET_FIXED_LATENCY: + + pa_sink_set_fixed_latency_within_thread(s, (pa_usec_t) offset); + return 0; + case PA_SINK_MESSAGE_GET_MAX_REWIND: *((size_t*) userdata) = s->thread_info.max_rewind; @@ -1938,9 +2077,10 @@ int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) int ret = 0; pa_core_assert_ref(c); + pa_assert_ctl_context(); pa_assert(cause != 0); - for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx))) { + PA_IDXSET_FOREACH(sink, c->sinks, idx) { int r; if ((r = pa_sink_suspend(sink, suspend, cause)) < 0) @@ -1953,6 +2093,7 @@ int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) /* Called from main thread */ void pa_sink_detach(pa_sink *s) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_DETACH, NULL, 0, NULL) == 0); @@ -1961,6 +2102,7 @@ void pa_sink_detach(pa_sink *s) { /* Called from main thread */ void pa_sink_attach(pa_sink *s) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_ATTACH, NULL, 0, NULL) == 0); @@ -1972,9 +2114,10 @@ void pa_sink_detach_within_thread(pa_sink *s) { void *state = NULL; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) if (i->detach) i->detach(i); @@ -1988,9 +2131,10 @@ void pa_sink_attach_within_thread(pa_sink *s) { void *state = NULL; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) if (i->attach) i->attach(i); @@ -2001,6 +2145,7 @@ void pa_sink_attach_within_thread(pa_sink *s) { /* Called from IO thread */ void pa_sink_request_rewind(pa_sink*s, size_t nbytes) { pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); if (s->thread_info.state == PA_SINK_SUSPENDED) @@ -2030,15 +2175,15 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { pa_usec_t monitor_latency; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) - return PA_CLAMP(s->fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); if (s->thread_info.requested_latency_valid) return s->thread_info.requested_latency; - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) - + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) if (i->thread_info.requested_sink_latency != (pa_usec_t) -1 && (result == (pa_usec_t) -1 || result > i->thread_info.requested_sink_latency)) result = i->thread_info.requested_sink_latency; @@ -2066,6 +2211,7 @@ pa_usec_t pa_sink_get_requested_latency(pa_sink *s) { pa_usec_t usec = 0; pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SINK_IS_LINKED(s->state)); if (s->state == PA_SINK_SUSPENDED) @@ -2081,16 +2227,16 @@ void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) { void *state = NULL; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); if (max_rewind == s->thread_info.max_rewind) return; s->thread_info.max_rewind = max_rewind; - if (PA_SINK_IS_LINKED(s->thread_info.state)) { - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (PA_SINK_IS_LINKED(s->thread_info.state)) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); - } if (s->monitor_source) pa_source_set_max_rewind_within_thread(s->monitor_source, s->thread_info.max_rewind); @@ -2099,6 +2245,7 @@ void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) { /* Called from main thread */ void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (PA_SINK_IS_LINKED(s->state)) pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0); @@ -2111,6 +2258,7 @@ void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) { void *state = NULL; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); if (max_request == s->thread_info.max_request) return; @@ -2120,7 +2268,7 @@ void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) { if (PA_SINK_IS_LINKED(s->thread_info.state)) { pa_sink_input *i; - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) pa_sink_input_update_max_request(i, s->thread_info.max_request); } } @@ -2128,6 +2276,7 @@ void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) { /* Called from main thread */ void pa_sink_set_max_request(pa_sink *s, size_t max_request) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (PA_SINK_IS_LINKED(s->state)) pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REQUEST, NULL, max_request, NULL) == 0); @@ -2136,23 +2285,24 @@ void pa_sink_set_max_request(pa_sink *s, size_t max_request) { } /* Called from IO thread */ -void pa_sink_invalidate_requested_latency(pa_sink *s) { +void pa_sink_invalidate_requested_latency(pa_sink *s, pa_bool_t dynamic) { pa_sink_input *i; void *state = NULL; pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); - if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) + if ((s->flags & PA_SINK_DYNAMIC_LATENCY)) + s->thread_info.requested_latency_valid = FALSE; + else if (dynamic) return; - s->thread_info.requested_latency_valid = FALSE; - if (PA_SINK_IS_LINKED(s->thread_info.state)) { if (s->update_requested_latency) s->update_requested_latency(s); - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) if (i->update_sink_requested_latency) i->update_sink_requested_latency(i); } @@ -2161,6 +2311,7 @@ void pa_sink_invalidate_requested_latency(pa_sink *s) { /* Called from main thread */ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); /* min_latency == 0: no limit * min_latency anything else: specified limit @@ -2195,6 +2346,7 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_ /* Called from main thread */ void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); pa_assert(min_latency); pa_assert(max_latency); @@ -2213,9 +2365,8 @@ void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *ma /* Called from IO thread */ void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { - void *state = NULL; - pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); @@ -2226,27 +2377,36 @@ void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, max_latency == ABSOLUTE_MAX_LATENCY) || (s->flags & PA_SINK_DYNAMIC_LATENCY)); + if (s->thread_info.min_latency == min_latency && + s->thread_info.max_latency == max_latency) + return; + s->thread_info.min_latency = min_latency; s->thread_info.max_latency = max_latency; if (PA_SINK_IS_LINKED(s->thread_info.state)) { pa_sink_input *i; + void *state = NULL; - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) if (i->update_sink_latency_range) i->update_sink_latency_range(i); } - pa_sink_invalidate_requested_latency(s); + pa_sink_invalidate_requested_latency(s, FALSE); pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency); } -/* Called from main thread, before the sink is put */ +/* Called from main thread */ void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) { pa_sink_assert_ref(s); + pa_assert_ctl_context(); - pa_assert(pa_sink_get_state(s) == PA_SINK_INIT); + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + return; + } if (latency < ABSOLUTE_MIN_LATENCY) latency = ABSOLUTE_MIN_LATENCY; @@ -2254,14 +2414,69 @@ void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) { if (latency > ABSOLUTE_MAX_LATENCY) latency = ABSOLUTE_MAX_LATENCY; - s->fixed_latency = latency; + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0); + else + s->thread_info.fixed_latency = latency; + pa_source_set_fixed_latency(s->monitor_source, latency); } +/* Called from main thread */ +pa_usec_t pa_sink_get_fixed_latency(pa_sink *s) { + pa_usec_t latency; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) + return 0; + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0); + else + latency = s->thread_info.fixed_latency; + + return latency; +} + +/* Called from IO thread */ +void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency) { + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + return; + } + + pa_assert(latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(latency <= ABSOLUTE_MAX_LATENCY); + + if (s->thread_info.fixed_latency == latency) + return; + + s->thread_info.fixed_latency = latency; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + pa_sink_input *i; + void *state = NULL; + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + if (i->update_sink_fixed_latency) + i->update_sink_fixed_latency(i); + } + + pa_sink_invalidate_requested_latency(s, FALSE); + + pa_source_set_fixed_latency_within_thread(s->monitor_source, latency); +} + /* Called from main context */ size_t pa_sink_get_max_rewind(pa_sink *s) { size_t r; pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (!PA_SINK_IS_LINKED(s->state)) return s->thread_info.max_rewind; @@ -2275,6 +2490,7 @@ size_t pa_sink_get_max_rewind(pa_sink *s) { size_t pa_sink_get_max_request(pa_sink *s) { size_t r; pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (!PA_SINK_IS_LINKED(s->state)) return s->thread_info.max_request; @@ -2288,7 +2504,8 @@ size_t pa_sink_get_max_request(pa_sink *s) { int pa_sink_set_port(pa_sink *s, const char *name, pa_bool_t save) { pa_device_port *port; - pa_assert(s); + pa_sink_assert_ref(s); + pa_assert_ctl_context(); if (!s->set_port) { pa_log_debug("set_port() operation not implemented for sink %u \"%s\"", s->index, s->name); @@ -2319,7 +2536,6 @@ int pa_sink_set_port(pa_sink *s, const char *name, pa_bool_t save) { return 0; } -/* Called from main context */ pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) { const char *ff, *c, *t = NULL, *s = "", *profile, *bus; @@ -2434,3 +2650,48 @@ pa_bool_t pa_device_init_intended_roles(pa_proplist *p) { return FALSE; } + +unsigned pa_device_init_priority(pa_proplist *p) { + const char *s; + unsigned priority = 0; + + pa_assert(p); + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) { + + if (pa_streq(s, "sound")) + priority += 9000; + else if (!pa_streq(s, "modem")) + priority += 1000; + } + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) { + + if (pa_streq(s, "internal")) + priority += 900; + else if (pa_streq(s, "speaker")) + priority += 500; + else if (pa_streq(s, "headphone")) + priority += 400; + } + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_BUS))) { + + if (pa_streq(s, "pci")) + priority += 50; + else if (pa_streq(s, "usb")) + priority += 40; + else if (pa_streq(s, "bluetooth")) + priority += 30; + } + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) { + + if (pa_startswith(s, "analog-")) + priority += 9; + else if (pa_startswith(s, "iec958-")) + priority += 8; + } + + return priority; +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index d16fcc01..ba547fc3 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -42,6 +42,7 @@ typedef struct pa_device_port pa_device_port; #include <pulsecore/rtpoll.h> #include <pulsecore/card.h> #include <pulsecore/queue.h> +#include <pulsecore/thread-mq.h> #define PA_MAX_INPUTS_PER_SINK 32 @@ -89,9 +90,10 @@ struct pa_sink { unsigned n_volume_steps; /* shall be constant */ /* Also see http://pulseaudio.org/wiki/InternalVolumes */ - pa_cvolume virtual_volume; /* The volume clients are informed about */ - pa_cvolume reference_volume; /* The volume taken as refernce base for relative sink input volumes */ + pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative sink input volumes */ + pa_cvolume real_volume; /* The volume that the hardware is configured to */ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */ + pa_bool_t muted:1; pa_bool_t refresh_volume:1; @@ -101,15 +103,14 @@ struct pa_sink { pa_bool_t save_muted:1; pa_asyncmsgq *asyncmsgq; - pa_rtpoll *rtpoll; pa_memchunk silence; - pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */ - pa_hashmap *ports; pa_device_port *active_port; + unsigned priority; + /* Called when the main loop requests a state change. Called from * main loop context. If returns -1 the state change will be * inhibited */ @@ -155,9 +156,14 @@ struct pa_sink { pa_sink_state_t state; pa_hashmap *inputs; + pa_rtpoll *rtpoll; + pa_cvolume soft_volume; pa_bool_t soft_muted:1; + /* The requested latency is used for dynamic latency + * sinks. For fixed latency sinks it is always identical to + * the fixed_latency. See below. */ pa_bool_t requested_latency_valid:1; pa_usec_t requested_latency; @@ -173,14 +179,21 @@ struct pa_sink { size_t rewind_nbytes; pa_bool_t rewind_requested; + /* Both dynamic and fixed latencies will be clamped to this + * range. */ pa_usec_t min_latency; /* we won't go below this latency */ pa_usec_t max_latency; /* An upper limit for the latencies */ + + /* 'Fixed' simply means that the latency is exclusively + * decided on by the sink, and the clients have no influence + * in changing it */ + pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */ } thread_info; void *userdata; }; -PA_DECLARE_CLASS(pa_sink); +PA_DECLARE_PUBLIC_CLASS(pa_sink); #define PA_SINK(s) (pa_sink_cast(s)) typedef enum pa_sink_message { @@ -200,6 +213,8 @@ typedef enum pa_sink_message { PA_SINK_MESSAGE_DETACH, PA_SINK_MESSAGE_SET_LATENCY_RANGE, PA_SINK_MESSAGE_GET_LATENCY_RANGE, + PA_SINK_MESSAGE_SET_FIXED_LATENCY, + PA_SINK_MESSAGE_GET_FIXED_LATENCY, PA_SINK_MESSAGE_GET_MAX_REWIND, PA_SINK_MESSAGE_GET_MAX_REQUEST, PA_SINK_MESSAGE_SET_MAX_REWIND, @@ -267,12 +282,15 @@ void pa_sink_detach(pa_sink *s); void pa_sink_attach(pa_sink *s); void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume); -void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume, pa_bool_t save); -void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted, pa_bool_t save); +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume); +void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted); + +void pa_sink_update_flags(pa_sink *s, pa_sink_flags_t mask, pa_sink_flags_t value); pa_bool_t pa_device_init_description(pa_proplist *p); pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink); pa_bool_t pa_device_init_intended_roles(pa_proplist *p); +unsigned pa_device_init_priority(pa_proplist *p); /**** May be called by everyone, from main context */ @@ -280,6 +298,7 @@ pa_bool_t pa_device_init_intended_roles(pa_proplist *p); pa_usec_t pa_sink_get_latency(pa_sink *s); pa_usec_t pa_sink_get_requested_latency(pa_sink *s); void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency); +pa_usec_t pa_sink_get_fixed_latency(pa_sink *s); size_t pa_sink_get_max_rewind(pa_sink *s); size_t pa_sink_get_max_request(pa_sink *s); @@ -288,11 +307,8 @@ int pa_sink_update_status(pa_sink*s); int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause); int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); -void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume); -void pa_sink_propagate_flat_volume(pa_sink *s); - -void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg, pa_bool_t become_reference, pa_bool_t save); -const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh, pa_bool_t reference); +void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t sendmsg, pa_bool_t save); +const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh); void pa_sink_set_mute(pa_sink *sink, pa_bool_t mute, pa_bool_t save); pa_bool_t pa_sink_get_mute(pa_sink *sink, pa_bool_t force_refresh); @@ -331,16 +347,23 @@ void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind); void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request); void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency); /*** To be called exclusively by sink input drivers, from IO context */ void pa_sink_request_rewind(pa_sink*s, size_t nbytes); -void pa_sink_invalidate_requested_latency(pa_sink *s); +void pa_sink_invalidate_requested_latency(pa_sink *s, pa_bool_t dynamic); pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s); pa_device_port *pa_device_port_new(const char *name, const char *description, size_t extra); void pa_device_port_free(pa_device_port *p); +/* Verify that we called in IO context (aka 'thread context), or that + * the sink is not yet set up, i.e. the thread not set up yet. See + * pa_assert_io_context() in thread-mq.h for more information. */ +#define pa_sink_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SINK_IS_LINKED((s)->state)) + #endif diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c index 24535157..ef3c29ee 100644 --- a/src/pulsecore/socket-client.c +++ b/src/pulsecore/socket-client.c @@ -73,14 +73,18 @@ struct pa_socket_client { PA_REFCNT_DECLARE; - pa_mainloop_api *mainloop; int fd; + + pa_mainloop_api *mainloop; pa_io_event *io_event; pa_time_event *timeout_event; pa_defer_event *defer_event; + pa_socket_client_cb_t callback; void *userdata; + pa_bool_t local; + #ifdef HAVE_LIBASYNCNS asyncns_t *asyncns; asyncns_query_t * asyncns_query; @@ -92,22 +96,10 @@ static pa_socket_client* socket_client_new(pa_mainloop_api *m) { pa_socket_client *c; pa_assert(m); - c = pa_xnew(pa_socket_client, 1); + c = pa_xnew0(pa_socket_client, 1); PA_REFCNT_INIT(c); c->mainloop = m; c->fd = -1; - c->io_event = NULL; - c->timeout_event = NULL; - c->defer_event = NULL; - c->callback = NULL; - c->userdata = NULL; - c->local = FALSE; - -#ifdef HAVE_LIBASYNCNS - c->asyncns = NULL; - c->asyncns_io_event = NULL; - c->asyncns_query = NULL; -#endif return c; } @@ -163,7 +155,6 @@ static void do_call(pa_socket_client *c) { } io = pa_iochannel_new(c->mainloop, c->fd, c->fd); - pa_assert(io); finish: if (!io && c->fd >= 0) @@ -172,7 +163,6 @@ finish: free_events(c); - pa_assert(c->callback); c->callback(c, io, c->userdata); pa_socket_client_unref(c); @@ -202,8 +192,6 @@ static void connect_io_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event } static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t len) { - int r; - pa_assert(c); pa_assert(PA_REFCNT_VALUE(c) >= 1); pa_assert(sa); @@ -211,7 +199,7 @@ static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t pa_make_fd_nonblock(c->fd); - if ((r = connect(c->fd, sa, len)) < 0) { + if (connect(c->fd, sa, len) < 0) { #ifdef OS_IS_WIN32 if (WSAGetLastError() != EWOULDBLOCK) { pa_log_debug("connect(): %d", WSAGetLastError()); @@ -222,9 +210,9 @@ static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t return -1; } - pa_assert_se(c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c)); + c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c); } else - pa_assert_se(c->defer_event = c->mainloop->defer_new(c->mainloop, connect_defer_cb, c)); + c->defer_event = c->mainloop->defer_new(c->mainloop, connect_defer_cb, c); return 0; } @@ -235,7 +223,7 @@ pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address pa_assert(m); pa_assert(port > 0); - memset(&sa, 0, sizeof(sa)); + pa_zero(sa); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(address); @@ -243,28 +231,24 @@ pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); } -#ifdef HAVE_SYS_UN_H pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { +#ifdef HAVE_SYS_UN_H struct sockaddr_un sa; pa_assert(m); pa_assert(filename); - memset(&sa, 0, sizeof(sa)); + pa_zero(sa); sa.sun_family = AF_UNIX; pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); -} - #else /* HAVE_SYS_UN_H */ -pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { return NULL; -} - #endif /* HAVE_SYS_UN_H */ +} static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size_t salen) { pa_assert(c); @@ -273,13 +257,11 @@ static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size c->local = pa_socket_address_is_local(sa); - if ((c->fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) { + if ((c->fd = pa_socket_cloexec(sa->sa_family, SOCK_STREAM, 0)) < 0) { pa_log("socket(): %s", pa_cstrerror(errno)); return -1; } - pa_make_fd_cloexec(c->fd); - #ifdef HAVE_IPV6 if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) #else @@ -302,7 +284,7 @@ pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct pa_assert(sa); pa_assert(salen > 0); - pa_assert_se(c = socket_client_new(m)); + c = socket_client_new(m); if (sockaddr_prepare(c, sa, salen) < 0) goto fail; @@ -359,22 +341,25 @@ void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on c->userdata = userdata; } -#ifdef HAVE_IPV6 pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port) { +#ifdef HAVE_IPV6 struct sockaddr_in6 sa; pa_assert(m); pa_assert(address); pa_assert(port > 0); - memset(&sa, 0, sizeof(sa)); + pa_zero(sa); sa.sin6_family = AF_INET6; sa.sin6_port = htons(port); memcpy(&sa.sin6_addr, address, sizeof(sa.sin6_addr)); return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); -} + +#else + return NULL; #endif +} #ifdef HAVE_LIBASYNCNS @@ -468,14 +453,13 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, pa_bool_t use_ case PA_PARSED_ADDRESS_TCP4: /* Fallthrough */ case PA_PARSED_ADDRESS_TCP6: /* Fallthrough */ - case PA_PARSED_ADDRESS_TCP_AUTO:{ - + case PA_PARSED_ADDRESS_TCP_AUTO: { struct addrinfo hints; char port[12]; pa_snprintf(port, sizeof(port), "%u", (unsigned) a.port); - memset(&hints, 0, sizeof(hints)); + pa_zero(hints); if (a.type == PA_PARSED_ADDRESS_TCP4) hints.ai_family = PF_INET; #ifdef HAVE_IPV6 @@ -494,11 +478,10 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, pa_bool_t use_ if (!(asyncns = asyncns_new(1))) goto finish; - pa_assert_se(c = socket_client_new(m)); + c = socket_client_new(m); c->asyncns = asyncns; c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c); - c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints); - pa_assert(c->asyncns_query); + pa_assert_se(c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints)); start_timeout(c, use_rtclock); } #elif defined(HAVE_GETADDRINFO) @@ -541,6 +524,7 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, pa_bool_t use_ if (!host) goto finish; + pa_zero(sa); s.sin_family = AF_INET; memcpy(&s.sin_addr, host->h_addr, sizeof(struct in_addr)); s.sin_port = htons(a.port); diff --git a/src/pulsecore/socket-client.h b/src/pulsecore/socket-client.h index b896afa9..138f658c 100644 --- a/src/pulsecore/socket-client.h +++ b/src/pulsecore/socket-client.h @@ -35,9 +35,7 @@ typedef struct pa_socket_client pa_socket_client; typedef void (*pa_socket_client_cb_t)(pa_socket_client *c, pa_iochannel*io, void *userdata); pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port); -#ifdef HAVE_IPV6 pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port); -#endif pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename); pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen); pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, pa_bool_t use_rtclock, const char *a, uint16_t default_port); diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c index e660700c..5d55de3e 100644 --- a/src/pulsecore/socket-server.c +++ b/src/pulsecore/socket-server.c @@ -104,13 +104,11 @@ static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_ev pa_socket_server_ref(s); - if ((nfd = accept(fd, NULL, NULL)) < 0) { + if ((nfd = pa_accept_cloexec(fd, NULL, NULL)) < 0) { pa_log("accept(): %s", pa_cstrerror(errno)); goto finish; } - pa_make_fd_cloexec(nfd); - if (!s->on_connection) { pa_close(nfd); goto finish; @@ -186,13 +184,11 @@ pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *file pa_assert(m); pa_assert(filename); - if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) { pa_log("socket(): %s", pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(fd); - memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); @@ -246,13 +242,11 @@ pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address pa_assert(m); pa_assert(port); - if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(PF_INET, SOCK_STREAM, 0)) < 0) { pa_log("socket(PF_INET): %s", pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(fd); - #ifdef SO_REUSEADDR if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) pa_log("setsockopt(): %s", pa_cstrerror(errno)); @@ -299,13 +293,11 @@ pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t ad pa_assert(m); pa_assert(port > 0); - if ((fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(PF_INET6, SOCK_STREAM, 0)) < 0) { pa_log("socket(PF_INET6): %s", pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(fd); - #ifdef IPV6_V6ONLY on = 1; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) diff --git a/src/pulsecore/socket-util.c b/src/pulsecore/socket-util.c index 5fd5dd67..2cc9882a 100644 --- a/src/pulsecore/socket-util.c +++ b/src/pulsecore/socket-util.c @@ -85,12 +85,11 @@ void pa_socket_peer_to_string(int fd, char *c, size_t l) { #ifndef OS_IS_WIN32 pa_assert_se(fstat(fd, &st) == 0); -#endif -#ifndef OS_IS_WIN32 if (S_ISSOCK(st.st_mode)) { #endif union { + struct sockaddr_storage storage; struct sockaddr sa; struct sockaddr_in in; #ifdef HAVE_IPV6 @@ -152,7 +151,7 @@ void pa_make_socket_low_delay(int fd) { pa_assert(fd >= 0); priority = 6; - if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (void*)&priority, sizeof(priority)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) < 0) pa_log_warn("SO_PRIORITY failed: %s", pa_cstrerror(errno)); #endif } @@ -166,9 +165,9 @@ void pa_make_tcp_socket_low_delay(int fd) { { int on = 1; #if defined(SOL_TCP) - if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) < 0) + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) #else - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) < 0) + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) #endif pa_log_warn("TCP_NODELAY failed: %s", pa_cstrerror(errno)); } @@ -178,9 +177,9 @@ void pa_make_tcp_socket_low_delay(int fd) { { int tos = IPTOS_LOWDELAY; #ifdef SOL_IP - if (setsockopt(fd, SOL_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) + if (setsockopt(fd, SOL_IP, IP_TOS, &tos, sizeof(tos)) < 0) #else - if (setsockopt(fd, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) #endif pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); } @@ -196,9 +195,9 @@ void pa_make_udp_socket_low_delay(int fd) { { int tos = IPTOS_LOWDELAY; #ifdef SOL_IP - if (setsockopt(fd, SOL_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) + if (setsockopt(fd, SOL_IP, IP_TOS, &tos, sizeof(tos)) < 0) #else - if (setsockopt(fd, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) #endif pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); } @@ -206,11 +205,11 @@ void pa_make_udp_socket_low_delay(int fd) { } int pa_socket_set_rcvbuf(int fd, size_t l) { - int bufsz = (int)l; + int bufsz = (int) l; pa_assert(fd >= 0); - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&bufsz, sizeof(bufsz)) < 0) { + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsz, sizeof(bufsz)) < 0) { pa_log_warn("SO_RCVBUF: %s", pa_cstrerror(errno)); return -1; } @@ -219,12 +218,12 @@ int pa_socket_set_rcvbuf(int fd, size_t l) { } int pa_socket_set_sndbuf(int fd, size_t l) { - int bufsz = (int)l; + int bufsz = (int) l; pa_assert(fd >= 0); - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&bufsz, sizeof(bufsz)) < 0) { - pa_log("SO_SNDBUF: %s", pa_cstrerror(errno)); + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bufsz, sizeof(bufsz)) < 0) { + pa_log_warn("SO_SNDBUF: %s", pa_cstrerror(errno)); return -1; } @@ -239,7 +238,7 @@ int pa_unix_socket_is_stale(const char *fn) { pa_assert(fn); - if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) { pa_log("socket(): %s", pa_cstrerror(errno)); goto finish; } @@ -315,6 +314,7 @@ pa_bool_t pa_socket_address_is_local(const struct sockaddr *sa) { pa_bool_t pa_socket_is_local(int fd) { union { + struct sockaddr_storage storage; struct sockaddr sa; struct sockaddr_in in; #ifdef HAVE_IPV6 diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index 502e5c69..53674ba1 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -64,9 +64,8 @@ enum { FILE_STREAM_MESSAGE_UNLINK }; -PA_DECLARE_CLASS(file_stream); +PA_DEFINE_PRIVATE_CLASS(file_stream, pa_msgobject); #define FILE_STREAM(o) (file_stream_cast(o)) -static PA_DEFINE_CHECK_TYPE(file_stream, pa_msgobject); /* Called from main context */ static void file_stream_unlink(file_stream *u) { @@ -253,11 +252,7 @@ int pa_play_file( u->readf_function = NULL; u->memblockq = NULL; - if ((fd = open(fname, O_RDONLY -#ifdef O_NOCTTY - |O_NOCTTY -#endif - )) < 0) { + if ((fd = pa_open_cloexec(fname, O_RDONLY, 0)) < 0) { pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno)); goto fail; } @@ -312,7 +307,7 @@ int pa_play_file( pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname); pa_sndfile_init_proplist(u->sndfile, data.proplist); - pa_sink_input_new(&u->sink_input, sink->core, &data, 0); + pa_sink_input_new(&u->sink_input, sink->core, &data); pa_sink_input_new_data_done(&data); if (!u->sink_input) @@ -335,8 +330,7 @@ int pa_play_file( return 0; fail: - if (u) - file_stream_unref(u); + file_stream_unref(u); if (fd >= 0) pa_close(fd); diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c index 2d9b76ad..d8c10b1e 100644 --- a/src/pulsecore/sound-file.c +++ b/src/pulsecore/sound-file.c @@ -62,11 +62,7 @@ int pa_sound_file_load( pa_memchunk_reset(chunk); - if ((fd = open(fname, O_RDONLY -#ifdef O_NOCTTY - |O_NOCTTY -#endif - )) < 0) { + if ((fd = pa_open_cloexec(fname, O_RDONLY, 0)) < 0) { pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno)); goto finish; } diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 4ba25ae4..57ccc067 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -41,14 +41,14 @@ #define MEMBLOCKQ_MAXLENGTH (32*1024*1024) -static PA_DEFINE_CHECK_TYPE(pa_source_output, pa_msgobject); +PA_DEFINE_PUBLIC_CLASS(pa_source_output, pa_msgobject); static void source_output_free(pa_object* mo); pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) { pa_assert(data); - memset(data, 0, sizeof(*data)); + pa_zero(*data); data->resample_method = PA_RESAMPLER_INVALID; data->proplist = pa_proplist_new(); @@ -84,6 +84,7 @@ static void reset_callbacks(pa_source_output *o) { o->update_max_rewind = NULL; o->update_source_requested_latency = NULL; o->update_source_latency_range = NULL; + o->update_source_fixed_latency = NULL; o->attach = NULL; o->detach = NULL; o->suspend = NULL; @@ -100,17 +101,18 @@ static void reset_callbacks(pa_source_output *o) { int pa_source_output_new( pa_source_output**_o, pa_core *core, - pa_source_output_new_data *data, - pa_source_output_flags_t flags) { + pa_source_output_new_data *data) { pa_source_output *o; pa_resampler *resampler = NULL; char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; int r; + char *pt; pa_assert(_o); pa_assert(core); pa_assert(data); + pa_assert_ctl_context(); if (data->client) pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist); @@ -144,13 +146,13 @@ int pa_source_output_new( pa_return_val_if_fail(pa_channel_map_valid(&data->channel_map), -PA_ERR_INVALID); pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID); - if (flags & PA_SOURCE_OUTPUT_FIX_FORMAT) + if (data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT) data->sample_spec.format = data->source->sample_spec.format; - if (flags & PA_SOURCE_OUTPUT_FIX_RATE) + if (data->flags & PA_SOURCE_OUTPUT_FIX_RATE) data->sample_spec.rate = data->source->sample_spec.rate; - if (flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) { + if (data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) { data->sample_spec.channels = data->source->sample_spec.channels; data->channel_map = data->source->channel_map; } @@ -166,7 +168,7 @@ int pa_source_output_new( if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0) return r; - if ((flags & PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND) && + if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) && pa_source_get_state(data->source) == PA_SOURCE_SUSPENDED) { pa_log("Failed to create source output: source is suspended."); return -PA_ERR_BADSTATE; @@ -177,7 +179,7 @@ int pa_source_output_new( return -PA_ERR_TOOLARGE; } - if ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || + if ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) || !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) { @@ -186,9 +188,9 @@ int pa_source_output_new( &data->source->sample_spec, &data->source->channel_map, &data->sample_spec, &data->channel_map, data->resample_method, - ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | - ((flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | - (core->disable_remixing || (flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { pa_log_warn("Unsupported resampling operation."); return -PA_ERR_NOTSUPPORTED; @@ -201,7 +203,7 @@ int pa_source_output_new( o->core = core; o->state = PA_SOURCE_OUTPUT_INIT; - o->flags = flags; + o->flags = data->flags; o->proplist = pa_proplist_copy(data->proplist); o->driver = pa_xstrdup(pa_path_get_filename(data->driver)); o->module = data->module; @@ -246,12 +248,15 @@ int pa_source_output_new( if (o->direct_on_input) pa_assert_se(pa_idxset_put(o->direct_on_input->direct_outputs, o, NULL) == 0); - pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s", + pt = pa_proplist_to_string_sep(o->proplist, "\n "); + pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s\n %s", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)), o->source->name, pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map)); + pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map), + pt); + pa_xfree(pt); /* Don't forget to call pa_source_output_put! */ @@ -262,6 +267,7 @@ int pa_source_output_new( /* Called from main context */ static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) { pa_assert(o); + pa_assert_ctl_context(); if (!o->source) return; @@ -275,6 +281,7 @@ static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) /* Called from main context */ static void source_output_set_state(pa_source_output *o, pa_source_output_state_t state) { pa_assert(o); + pa_assert_ctl_context(); if (o->state == state) return; @@ -294,6 +301,7 @@ static void source_output_set_state(pa_source_output *o, pa_source_output_state_ void pa_source_output_unlink(pa_source_output*o) { pa_bool_t linked; pa_assert(o); + pa_assert_ctl_context(); /* See pa_sink_unlink() for a couple of comments how this function * works */ @@ -346,6 +354,7 @@ static void source_output_free(pa_object* mo) { pa_source_output *o = PA_SOURCE_OUTPUT(mo); pa_assert(o); + pa_assert_ctl_context(); pa_assert(pa_source_output_refcnt(o) == 0); if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) @@ -353,8 +362,6 @@ static void source_output_free(pa_object* mo) { pa_log_info("Freeing output %u \"%s\"", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME))); - pa_assert(!o->thread_info.attached); - if (o->thread_info.delay_memblockq) pa_memblockq_free(o->thread_info.delay_memblockq); @@ -371,7 +378,9 @@ static void source_output_free(pa_object* mo) { /* Called from main context */ void pa_source_output_put(pa_source_output *o) { pa_source_output_state_t state; + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(o->state == PA_SOURCE_OUTPUT_INIT); @@ -395,6 +404,7 @@ void pa_source_output_put(pa_source_output *o) { /* Called from main context */ void pa_source_output_kill(pa_source_output*o) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); o->kill(o); @@ -405,6 +415,7 @@ pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_la pa_usec_t r[2] = { 0, 0 }; pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); @@ -424,6 +435,7 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { size_t limit, mbs = 0; pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); pa_assert(chunk); pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec)); @@ -499,8 +511,9 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { /* Called from thread context */ void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in source sample spec */) { - pa_source_output_assert_ref(o); + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec)); @@ -526,8 +539,17 @@ void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in so } /* Called from thread context */ +size_t pa_source_output_get_max_rewind(pa_source_output *o) { + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + + return o->thread_info.resampler ? pa_resampler_request(o->thread_info.resampler, o->source->thread_info.max_rewind) : o->source->thread_info.max_rewind; +} + +/* Called from thread context */ void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* in the source's sample spec */) { pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec)); @@ -538,15 +560,16 @@ void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* i /* Called from thread context */ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) { pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) - usec = o->source->fixed_latency; + usec = o->source->thread_info.fixed_latency; if (usec != (pa_usec_t) -1) usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency); o->thread_info.requested_source_latency = usec; - pa_source_invalidate_requested_latency(o->source); + pa_source_invalidate_requested_latency(o->source, TRUE); return usec; } @@ -554,6 +577,7 @@ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output /* Called from main context */ pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); @@ -565,7 +589,7 @@ pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t if (o->source) { if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) - usec = o->source->fixed_latency; + usec = pa_source_get_fixed_latency(o->source); if (usec != (pa_usec_t) -1) { pa_usec_t min_latency, max_latency; @@ -582,6 +606,7 @@ pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t /* Called from main context */ pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { pa_usec_t usec = 0; @@ -598,6 +623,7 @@ pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) { /* Called from main context */ void pa_source_output_cork(pa_source_output *o, pa_bool_t b) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING); @@ -606,6 +632,7 @@ void pa_source_output_cork(pa_source_output *o, pa_bool_t b) { /* Called from main context */ int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); pa_return_val_if_fail(o->thread_info.resampler, -PA_ERR_BADSTATE); @@ -623,6 +650,7 @@ 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)) @@ -647,11 +675,12 @@ void pa_source_output_set_name(pa_source_output *o, const char *name) { /* Called from main thread */ void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); if (p) pa_proplist_update(o->proplist, mode, p); - if (PA_SINK_IS_LINKED(o->state)) { + 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); } @@ -660,6 +689,7 @@ void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode /* 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(); return o->actual_resample_method; } @@ -667,6 +697,7 @@ pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) { /* Called from main context */ pa_bool_t pa_source_output_may_move(pa_source_output *o) { pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE) @@ -708,6 +739,7 @@ int pa_source_output_start_move(pa_source_output *o) { int r; pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); pa_assert(o->source); @@ -739,6 +771,7 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t pa_resampler *new_resampler; pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); pa_assert(!o->source); pa_source_assert_ref(dest); @@ -816,10 +849,29 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t } /* Called from main context */ +void pa_source_output_fail_move(pa_source_output *o) { + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(!o->source); + + /* Check if someone wants this source output? */ + if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP) + return; + + if (o->moving) + o->moving(o, NULL); + + pa_source_output_kill(o); +} + +/* Called from main context */ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t save) { int r; pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); pa_assert(o->source); pa_source_assert_ref(dest); @@ -838,6 +890,7 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t sav } if ((r = pa_source_output_finish_move(o, dest, save)) < 0) { + pa_source_output_fail_move(o); pa_source_output_unref(o); return r; } @@ -850,6 +903,7 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t sav /* Called from IO thread context */ void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) { pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); if (state == o->thread_info.state) return; @@ -906,11 +960,13 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int return -PA_ERR_NOTIMPLEMENTED; } +/* Called from main context */ void pa_source_output_send_event(pa_source_output *o, const char *event, pa_proplist *data) { pa_proplist *pl = NULL; pa_source_output_send_event_hook_data hook_data; pa_source_output_assert_ref(o); + pa_assert_ctl_context(); pa_assert(event); if (!o->send_event) diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index 9824e160..273b78fc 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -55,7 +55,8 @@ typedef enum pa_source_output_flags { PA_SOURCE_OUTPUT_FIX_RATE = 64, PA_SOURCE_OUTPUT_FIX_CHANNELS = 128, PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND = 256, - PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND = 512 + PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND = 512, + PA_SOURCE_OUTPUT_KILL_ON_SUSPEND = 1024 } pa_source_output_flags_t; struct pa_source_output { @@ -108,6 +109,10 @@ struct pa_source_output { * from IO context. */ void (*update_source_latency_range) (pa_source_output *o); /* may be NULL */ + /* Called whenver the fixed latency of the source changes, if there + * is one. Called from IO context. */ + void (*update_source_fixed_latency) (pa_source_output *i); /* may be NULL */ + /* If non-NULL this function is called when the output is first * connected to a source. Called from IO thread context */ void (*attach) (pa_source_output *o); /* may be NULL */ @@ -127,7 +132,9 @@ struct pa_source_output { /* If non-NULL called whenever the source output is moved to a new * source. Called from main context after the stream was detached * from the old source and before it is attached to the new - * source. */ + * source. If dest is NULL the move was executed in two + * phases and the second one failed; the stream will be destroyed + * after this call. */ void (*moving) (pa_source_output *o, pa_source *dest); /* may be NULL */ /* Supposed to unlink and destroy this stream. Called from main @@ -175,7 +182,7 @@ struct pa_source_output { void *userdata; }; -PA_DECLARE_CLASS(pa_source_output); +PA_DECLARE_PUBLIC_CLASS(pa_source_output); #define PA_SOURCE_OUTPUT(o) pa_source_output_cast(o) enum { @@ -194,6 +201,8 @@ typedef struct pa_source_output_send_event_hook_data { } pa_source_output_send_event_hook_data; typedef struct pa_source_output_new_data { + pa_source_output_flags_t flags; + pa_proplist *proplist; pa_sink_input *direct_on_input; @@ -224,8 +233,7 @@ void pa_source_output_new_data_done(pa_source_output_new_data *data); int pa_source_output_new( pa_source_output**o, pa_core *core, - pa_source_output_new_data *data, - pa_source_output_flags_t flags); + pa_source_output_new_data *data); void pa_source_output_put(pa_source_output *o); void pa_source_output_unlink(pa_source_output*o); @@ -238,6 +246,8 @@ void pa_source_output_cork(pa_source_output *o, pa_bool_t b); int pa_source_output_set_rate(pa_source_output *o, uint32_t rate); +size_t pa_source_output_get_max_rewind(pa_source_output *o); + /* Callable by everyone */ /* External code may request disconnection with this funcion */ @@ -260,6 +270,7 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t sav * new source */ int pa_source_output_start_move(pa_source_output *o); int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t save); +void pa_source_output_fail_move(pa_source_output *o); #define pa_source_output_get_state(o) ((o)->state) @@ -277,4 +288,7 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec); +#define pa_source_output_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SOURCE_OUTPUT_IS_LINKED((s)->state)) + #endif diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 74f38bc5..415c54bc 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -33,6 +33,7 @@ #include <pulse/timeval.h> #include <pulse/util.h> +#include <pulsecore/core-util.h> #include <pulsecore/source-output.h> #include <pulsecore/namereg.h> #include <pulsecore/core-subscribe.h> @@ -45,14 +46,14 @@ #define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) #define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) -static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject); +PA_DEFINE_PUBLIC_CLASS(pa_source, pa_msgobject); static void source_free(pa_object *o); pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) { pa_assert(data); - memset(data, 0, sizeof(*data)); + pa_zero(*data); data->proplist = pa_proplist_new(); return data; @@ -145,6 +146,7 @@ pa_source* pa_source_new( pa_assert(core); pa_assert(data); pa_assert(data->name); + pa_assert_ctl_context(); s = pa_msgobject_new(pa_source); @@ -179,7 +181,7 @@ pa_source* pa_source_new( pa_cvolume_reset(&data->volume, data->sample_spec.channels); pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); - pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels); + pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec)); if (!data->muted_is_set) data->muted = FALSE; @@ -203,6 +205,7 @@ pa_source* pa_source_new( s->core = core; s->state = PA_SOURCE_INIT; s->flags = flags; + s->priority = 0; s->suspend_cause = 0; s->name = pa_xstrdup(name); s->proplist = pa_proplist_copy(data->proplist); @@ -210,6 +213,8 @@ pa_source* pa_source_new( s->module = data->module; s->card = data->card; + s->priority = pa_device_init_priority(s->proplist); + s->sample_spec = data->sample_spec; s->channel_map = data->channel_map; @@ -217,20 +222,17 @@ pa_source* pa_source_new( s->n_corked = 0; s->monitor_of = NULL; - s->virtual_volume = data->volume; + s->volume = data->volume; pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); s->base_volume = PA_VOLUME_NORM; s->n_volume_steps = PA_VOLUME_NORM+1; s->muted = data->muted; s->refresh_volume = s->refresh_muted = FALSE; - s->fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; - reset_callbacks(s); s->userdata = NULL; s->asyncmsgq = NULL; - s->rtpoll = NULL; /* As a minor optimization we just steal the list instead of * copying it here */ @@ -263,6 +265,7 @@ pa_source* pa_source_new( &s->sample_spec, 0); + s->thread_info.rtpoll = NULL; s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); s->thread_info.soft_volume = s->soft_volume; s->thread_info.soft_muted = s->muted; @@ -272,6 +275,7 @@ pa_source* pa_source_new( s->thread_info.requested_latency = 0; s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY; + s->thread_info.fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0); @@ -297,6 +301,7 @@ static int source_set_state(pa_source *s, pa_source_state_t state) { pa_source_state_t original_state; pa_assert(s); + pa_assert_ctl_context(); if (s->state == state) return 0; @@ -333,27 +338,26 @@ static int source_set_state(pa_source *s, pa_source_state_t state) { /* We're suspending or resuming, tell everyone about it */ - for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx))) + PA_IDXSET_FOREACH(o, s->outputs, idx) if (s->state == PA_SOURCE_SUSPENDED && - (o->flags & PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND)) + (o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND)) pa_source_output_kill(o); else if (o->suspend) o->suspend(o, state == PA_SOURCE_SUSPENDED); } - return 0; } /* Called from main context */ void pa_source_put(pa_source *s) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(s->state == PA_SOURCE_INIT); /* The following fields must be initialized properly when calling _put() */ pa_assert(s->asyncmsgq); - pa_assert(s->rtpoll); pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); /* Generally, flags should be initialized via pa_source_new(). As @@ -368,7 +372,7 @@ void pa_source_put(pa_source *s) { pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SOURCE_DECIBEL_VOLUME)); pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1); - pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == (s->fixed_latency != 0)); + pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0)); pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0); @@ -382,6 +386,7 @@ void pa_source_unlink(pa_source *s) { pa_source_output *o, *j = NULL; pa_assert(s); + pa_assert_ctl_context(); /* See pa_sink_unlink() for a couple of comments how this function * works. */ @@ -423,6 +428,7 @@ static void source_free(pa_object *o) { pa_source *s = PA_SOURCE(o); pa_assert(s); + pa_assert_ctl_context(); pa_assert(pa_source_refcnt(s) == 0); if (PA_SOURCE_IS_LINKED(s->state)) @@ -458,23 +464,40 @@ static void source_free(pa_object *o) { pa_xfree(s); } -/* Called from main context */ +/* Called from main context, and not while the IO thread is active, please */ void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) { pa_source_assert_ref(s); + pa_assert_ctl_context(); s->asyncmsgq = q; } -/* Called from main context */ +/* Called from main context, and not while the IO thread is active, please */ +void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (mask == 0) + return; + + /* For now, allow only a minimal set of flags to be changed. */ + pa_assert((mask & ~(PA_SOURCE_DYNAMIC_LATENCY|PA_SOURCE_LATENCY)) == 0); + + s->flags = (s->flags & ~mask) | (value & mask); +} + +/* Called from IO context, or before _put() from main context */ void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) { pa_source_assert_ref(s); + pa_source_assert_io_context(s); - s->rtpoll = p; + s->thread_info.rtpoll = p; } /* Called from main context */ int pa_source_update_status(pa_source*s) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); if (s->state == PA_SOURCE_SUSPENDED) @@ -486,6 +509,7 @@ int pa_source_update_status(pa_source*s) { /* Called from main context */ int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); pa_assert(cause != 0); @@ -513,6 +537,7 @@ int pa_source_sync_suspend(pa_source *s) { pa_sink_state_t state; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); pa_assert(s->monitor_of); @@ -532,6 +557,7 @@ pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q) { uint32_t idx; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); if (!q) @@ -556,12 +582,13 @@ void pa_source_move_all_finish(pa_source *s, pa_queue *q, pa_bool_t save) { pa_source_output *o; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); pa_assert(q); while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) { if (pa_source_output_finish_move(o, s, save) < 0) - pa_source_output_kill(o); + pa_source_output_fail_move(o); pa_source_output_unref(o); } @@ -572,13 +599,13 @@ void pa_source_move_all_finish(pa_source *s, pa_queue *q, pa_bool_t save) { /* Called from main context */ void pa_source_move_all_fail(pa_queue *q) { pa_source_output *o; + + pa_assert_ctl_context(); pa_assert(q); while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) { - if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_OK) { - pa_source_output_kill(o); - pa_source_output_unref(o); - } + pa_source_output_fail_move(o); + pa_source_output_unref(o); } pa_queue_free(q, NULL, NULL); @@ -590,17 +617,18 @@ void pa_source_process_rewind(pa_source *s, size_t nbytes) { void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); - if (s->thread_info.state == PA_SOURCE_SUSPENDED) + if (nbytes <= 0) return; - if (nbytes <= 0) + if (s->thread_info.state == PA_SOURCE_SUSPENDED) return; pa_log_debug("Processing rewind..."); - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) { pa_source_output_assert_ref(o); pa_source_output_process_rewind(o, nbytes); } @@ -612,6 +640,7 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) { void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); pa_assert(chunk); @@ -651,6 +680,7 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) { /* Called from IO thread context */ void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk) { pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); pa_source_output_assert_ref(o); pa_assert(o->thread_info.direct_on_input); @@ -682,6 +712,7 @@ pa_usec_t pa_source_get_latency(pa_source *s) { pa_usec_t usec; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); if (s->state == PA_SOURCE_SUSPENDED) @@ -701,6 +732,7 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) { pa_msgobject *o; pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); /* The returned value is supposed to be in the time domain of the sound card! */ @@ -722,77 +754,96 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) { } /* Called from main thread */ -void pa_source_set_volume(pa_source *s, const pa_cvolume *volume, pa_bool_t save) { - pa_cvolume old_virtual_volume; - pa_bool_t virtual_volume_changed; +void pa_source_set_volume( + pa_source *s, + const pa_cvolume *volume, + pa_bool_t save) { + + pa_bool_t real_changed; + pa_cvolume old_volume; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); - pa_assert(volume); pa_assert(pa_cvolume_valid(volume)); - pa_assert(pa_cvolume_compatible(volume, &s->sample_spec)); + pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec)); - old_virtual_volume = s->virtual_volume; - s->virtual_volume = *volume; - virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume); - s->save_volume = (!virtual_volume_changed && s->save_volume) || save; + old_volume = s->volume; + + if (pa_cvolume_compatible(volume, &s->sample_spec)) + s->volume = *volume; + else + pa_cvolume_scale(&s->volume, pa_cvolume_max(volume)); + + real_changed = !pa_cvolume_equal(&old_volume, &s->volume); + s->save_volume = (!real_changed && s->save_volume) || save; if (s->set_volume) { pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); s->set_volume(s); } else - s->soft_volume = s->virtual_volume; + s->soft_volume = s->volume; pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0); - if (virtual_volume_changed) + if (real_changed) pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } /* Called from main thread. Only to be called by source implementor */ void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) { pa_source_assert_ref(s); - pa_assert(volume); + pa_assert_ctl_context(); + + if (!volume) + pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); + else + s->soft_volume = *volume; if (PA_SOURCE_IS_LINKED(s->state)) pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0); else - s->thread_info.soft_volume = *volume; + s->thread_info.soft_volume = s->soft_volume; } /* Called from main thread */ const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); if (s->refresh_volume || force_refresh) { - pa_cvolume old_virtual_volume = s->virtual_volume; + pa_cvolume old_volume; + + old_volume = s->volume; if (s->get_volume) s->get_volume(s); pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0); - if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) + if (!pa_cvolume_equal(&old_volume, &s->volume)) { + s->save_volume = TRUE; pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } } - return &s->virtual_volume; + return &s->volume; } /* Called from main thread */ -void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume, pa_bool_t save) { +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume) { pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); /* The source implementor may call this if the volume changed to make sure everyone is notified */ - if (pa_cvolume_equal(&s->virtual_volume, new_volume)) { - s->save_volume = s->save_volume || save; + if (pa_cvolume_equal(&s->volume, new_volume)) return; - } - s->virtual_volume = *new_volume; - s->save_volume = save; + s->volume = *new_volume; + s->save_volume = TRUE; pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } @@ -802,6 +853,7 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute, pa_bool_t save) { pa_bool_t old_muted; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); old_muted = s->muted; @@ -820,6 +872,7 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute, pa_bool_t save) { /* Called from main thread */ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); if (s->refresh_muted || force_refresh) { @@ -831,6 +884,8 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0); if (old_muted != s->muted) { + s->save_muted = TRUE; + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); /* Make sure the soft mute status stays in sync */ @@ -842,18 +897,18 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { } /* Called from main thread */ -void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted, pa_bool_t save) { +void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted) { pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); /* The source implementor may call this if the mute state changed to make sure everyone is notified */ - if (s->muted == new_muted) { - s->save_muted = s->save_muted || save; + if (s->muted == new_muted) return; - } s->muted = new_muted; - s->save_muted = save; + s->save_muted = TRUE; pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } @@ -861,6 +916,7 @@ void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted, pa_bool_t save) { /* Called from main thread */ pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) { pa_source_assert_ref(s); + pa_assert_ctl_context(); if (p) pa_proplist_update(s->proplist, mode, p); @@ -874,16 +930,18 @@ pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_prop } /* Called from main thread */ +/* FIXME -- this should be dropped and be merged into pa_source_update_proplist() */ void pa_source_set_description(pa_source *s, const char *description) { const char *old; pa_source_assert_ref(s); + pa_assert_ctl_context(); if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) return; old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); - if (old && description && !strcmp(old, description)) + if (old && description && pa_streq(old, description)) return; if (description) @@ -901,6 +959,7 @@ void pa_source_set_description(pa_source *s, const char *description) { unsigned pa_source_linked_by(pa_source *s) { pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert_ctl_context(); return pa_idxset_size(s->outputs); } @@ -911,6 +970,7 @@ unsigned pa_source_used_by(pa_source *s) { pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert_ctl_context(); ret = pa_idxset_size(s->outputs); pa_assert(ret >= s->n_corked); @@ -925,6 +985,7 @@ unsigned pa_source_check_suspend(pa_source *s) { uint32_t idx; pa_source_assert_ref(s); + pa_assert_ctl_context(); if (!PA_SOURCE_IS_LINKED(s->state)) return 0; @@ -1006,7 +1067,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index))) pa_source_output_unref(o); - pa_source_invalidate_requested_latency(s); + pa_source_invalidate_requested_latency(s, TRUE); return 0; } @@ -1086,6 +1147,16 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ return 0; } + case PA_SOURCE_MESSAGE_GET_FIXED_LATENCY: + + *((pa_usec_t*) userdata) = s->thread_info.fixed_latency; + return 0; + + case PA_SOURCE_MESSAGE_SET_FIXED_LATENCY: + + pa_source_set_fixed_latency_within_thread(s, (pa_usec_t) offset); + return 0; + case PA_SOURCE_MESSAGE_GET_MAX_REWIND: *((size_t*) userdata) = s->thread_info.max_rewind; @@ -1120,6 +1191,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t caus int ret = 0; pa_core_assert_ref(c); + pa_assert_ctl_context(); pa_assert(cause != 0); for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) { @@ -1138,6 +1210,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t caus /* Called from main thread */ void pa_source_detach(pa_source *s) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL) == 0); @@ -1146,6 +1219,7 @@ void pa_source_detach(pa_source *s) { /* Called from main thread */ void pa_source_attach(pa_source *s) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL) == 0); @@ -1157,9 +1231,10 @@ void pa_source_detach_within_thread(pa_source *s) { void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) if (o->detach) o->detach(o); } @@ -1170,9 +1245,10 @@ void pa_source_attach_within_thread(pa_source *s) { void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) if (o->attach) o->attach(o); } @@ -1184,15 +1260,15 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) - return PA_CLAMP(s->fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); if (s->thread_info.requested_latency_valid) return s->thread_info.requested_latency; - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) - + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) if (o->thread_info.requested_source_latency != (pa_usec_t) -1 && (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) result = o->thread_info.requested_source_latency; @@ -1214,6 +1290,7 @@ pa_usec_t pa_source_get_requested_latency(pa_source *s) { pa_usec_t usec = 0; pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(PA_SOURCE_IS_LINKED(s->state)); if (s->state == PA_SOURCE_SUSPENDED) @@ -1230,21 +1307,22 @@ void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind) { void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); if (max_rewind == s->thread_info.max_rewind) return; s->thread_info.max_rewind = max_rewind; - if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) pa_source_output_update_max_rewind(o, s->thread_info.max_rewind); - } } /* Called from main thread */ void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { pa_source_assert_ref(s); + pa_assert_ctl_context(); if (PA_SOURCE_IS_LINKED(s->state)) pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0); @@ -1253,17 +1331,18 @@ void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { } /* Called from IO thread */ -void pa_source_invalidate_requested_latency(pa_source *s) { +void pa_source_invalidate_requested_latency(pa_source *s, pa_bool_t dynamic) { pa_source_output *o; void *state = NULL; pa_source_assert_ref(s); + pa_source_assert_io_context(s); - if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + if ((s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + s->thread_info.requested_latency_valid = FALSE; + else if (dynamic) return; - s->thread_info.requested_latency_valid = FALSE; - if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { if (s->update_requested_latency) @@ -1275,12 +1354,13 @@ void pa_source_invalidate_requested_latency(pa_source *s) { } if (s->monitor_of) - pa_sink_invalidate_requested_latency(s->monitor_of); + pa_sink_invalidate_requested_latency(s->monitor_of, dynamic); } /* Called from main thread */ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { pa_source_assert_ref(s); + pa_assert_ctl_context(); /* min_latency == 0: no limit * min_latency anything else: specified limit @@ -1315,6 +1395,7 @@ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t /* Called from main thread */ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { pa_source_assert_ref(s); + pa_assert_ctl_context(); pa_assert(min_latency); pa_assert(max_latency); @@ -1333,9 +1414,8 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t /* Called from IO thread, and from main thread before pa_source_put() is called */ void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { - void *state = NULL; - pa_source_assert_ref(s); + pa_source_assert_io_context(s); pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); @@ -1347,25 +1427,34 @@ void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_laten (s->flags & PA_SOURCE_DYNAMIC_LATENCY) || s->monitor_of); + if (s->thread_info.min_latency == min_latency && + s->thread_info.max_latency == max_latency) + return; + s->thread_info.min_latency = min_latency; s->thread_info.max_latency = max_latency; if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { pa_source_output *o; + void *state = NULL; - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) if (o->update_source_latency_range) o->update_source_latency_range(o); } - pa_source_invalidate_requested_latency(s); + pa_source_invalidate_requested_latency(s, FALSE); } /* Called from main thread, before the source is put */ void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency) { pa_source_assert_ref(s); + pa_assert_ctl_context(); - pa_assert(pa_source_get_state(s) == PA_SOURCE_INIT); + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + return; + } if (latency < ABSOLUTE_MIN_LATENCY) latency = ABSOLUTE_MIN_LATENCY; @@ -1373,12 +1462,64 @@ void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency) { if (latency > ABSOLUTE_MAX_LATENCY) latency = ABSOLUTE_MAX_LATENCY; - s->fixed_latency = latency; + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0); + else + s->thread_info.fixed_latency = latency; +} + +/* Called from main thread */ +pa_usec_t pa_source_get_fixed_latency(pa_source *s) { + pa_usec_t latency; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) + return 0; + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0); + else + latency = s->thread_info.fixed_latency; + + return latency; +} + +/* Called from IO thread */ +void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency) { + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + return; + } + + pa_assert(latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(latency <= ABSOLUTE_MAX_LATENCY); + + if (s->thread_info.fixed_latency == latency) + return; + + s->thread_info.fixed_latency = latency; + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + pa_source_output *o; + void *state = NULL; + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + if (o->update_source_fixed_latency) + o->update_source_fixed_latency(o); + } + + pa_source_invalidate_requested_latency(s, FALSE); } /* Called from main thread */ size_t pa_source_get_max_rewind(pa_source *s) { size_t r; + pa_assert_ctl_context(); pa_source_assert_ref(s); if (!PA_SOURCE_IS_LINKED(s->state)) @@ -1394,9 +1535,10 @@ int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) { pa_device_port *port; pa_assert(s); + pa_assert_ctl_context(); if (!s->set_port) { - pa_log_debug("set_port() operation not implemented for sink %u \"%s\"", s->index, s->name); + pa_log_debug("set_port() operation not implemented for source %u \"%s\"", s->index, s->name); return -PA_ERR_NOTIMPLEMENTED; } diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 7e9fd8b7..e3e56bc4 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -43,6 +43,7 @@ typedef struct pa_source pa_source; #include <pulsecore/source-output.h> #include <pulsecore/card.h> #include <pulsecore/queue.h> +#include <pulsecore/thread-mq.h> #define PA_MAX_OUTPUTS_PER_SOURCE 32 @@ -78,7 +79,7 @@ struct pa_source { pa_volume_t base_volume; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */ - pa_cvolume virtual_volume, soft_volume; + pa_cvolume volume, soft_volume; pa_bool_t muted:1; pa_bool_t refresh_volume:1; @@ -89,15 +90,14 @@ struct pa_source { pa_bool_t save_muted:1; pa_asyncmsgq *asyncmsgq; - pa_rtpoll *rtpoll; pa_memchunk silence; - pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */ - pa_hashmap *ports; pa_device_port *active_port; + unsigned priority; + /* Called when the main loop requests a state change. Called from * main loop context. If returns -1 the state change will be * inhibited */ @@ -139,6 +139,8 @@ struct pa_source { pa_source_state_t state; pa_hashmap *outputs; + pa_rtpoll *rtpoll; + pa_cvolume soft_volume; pa_bool_t soft_muted:1; @@ -151,12 +153,14 @@ struct pa_source { pa_usec_t min_latency; /* we won't go below this latency */ pa_usec_t max_latency; /* An upper limit for the latencies */ - } thread_info; + + pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */ + } thread_info; void *userdata; }; -PA_DECLARE_CLASS(pa_source); +PA_DECLARE_PUBLIC_CLASS(pa_source); #define PA_SOURCE(s) pa_source_cast(s) typedef enum pa_source_message { @@ -173,6 +177,8 @@ typedef enum pa_source_message { PA_SOURCE_MESSAGE_DETACH, PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, + PA_SOURCE_MESSAGE_SET_FIXED_LATENCY, + PA_SOURCE_MESSAGE_GET_FIXED_LATENCY, PA_SOURCE_MESSAGE_GET_MAX_REWIND, PA_SOURCE_MESSAGE_SET_MAX_REWIND, PA_SOURCE_MESSAGE_MAX @@ -237,17 +243,20 @@ void pa_source_detach(pa_source *s); void pa_source_attach(pa_source *s); void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume); -void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume, pa_bool_t save); -void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted, pa_bool_t save); +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume); +void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted); int pa_source_sync_suspend(pa_source *s); +void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value); + /*** May be called by everyone, from main context */ /* The returned value is supposed to be in the time domain of the sound card! */ pa_usec_t pa_source_get_latency(pa_source *s); pa_usec_t pa_source_get_requested_latency(pa_source *s); void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency); +pa_usec_t pa_source_get_fixed_latency(pa_source *s); size_t pa_source_get_max_rewind(pa_source *s); @@ -257,6 +266,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t caus void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, pa_bool_t save); const pa_cvolume *pa_source_get_volume(pa_source *source, pa_bool_t force_refresh); + void pa_source_set_mute(pa_source *source, pa_bool_t mute, pa_bool_t save); pa_bool_t pa_source_get_mute(pa_source *source, pa_bool_t force_refresh); @@ -288,11 +298,16 @@ void pa_source_detach_within_thread(pa_source *s); pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s); void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind); + void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency); /*** To be called exclusively by source output drivers, from IO context */ -void pa_source_invalidate_requested_latency(pa_source *s); +void pa_source_invalidate_requested_latency(pa_source *s, pa_bool_t dynamic); pa_usec_t pa_source_get_latency_within_thread(pa_source *s); +#define pa_source_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SOURCE_IS_LINKED((s)->state)) + #endif diff --git a/src/pulsecore/start-child.c b/src/pulsecore/start-child.c index 7774bde6..4a70aea1 100644 --- a/src/pulsecore/start-child.c +++ b/src/pulsecore/start-child.c @@ -68,23 +68,29 @@ int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid) { } else { /* child */ - pa_reset_priority(); + pa_reset_personality(); pa_assert_se(pa_close(pipe_fds[0]) == 0); - pa_assert_se(dup2(pipe_fds[1], 1) == 1); + pa_assert_se(dup2(pipe_fds[1], STDOUT_FILENO) == STDOUT_FILENO); - if (pipe_fds[1] != 1) + if (pipe_fds[1] != STDOUT_FILENO) pa_assert_se(pa_close(pipe_fds[1]) == 0); - pa_close(0); - pa_assert_se(open("/dev/null", O_RDONLY) == 0); + pa_close(STDIN_FILENO); + pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO); - pa_close(2); - pa_assert_se(open("/dev/null", O_WRONLY) == 2); + pa_close(STDERR_FILENO); + pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO); pa_close_all(-1); pa_reset_sigs(-1); pa_unblock_sigs(-1); + pa_reset_priority(); + pa_unset_env_recorded(); + + /* Make sure our children are not influenced by the + * LD_BIND_NOW we set for ourselves. */ + unsetenv("LD_BIND_NOW"); #ifdef PR_SET_PDEATHSIG /* On Linux we can use PR_SET_PDEATHSIG to have the helper diff --git a/src/pulsecore/svolume_arm.c b/src/pulsecore/svolume_arm.c new file mode 100644 index 00000000..5bd1448f --- /dev/null +++ b/src/pulsecore/svolume_arm.c @@ -0,0 +1,195 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/timeval.h> +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/core-util.h> + +#include "cpu-arm.h" + +#include "sample-util.h" +#include "endianmacros.h" + +#if defined (__arm__) + +#define MOD_INC() \ + " subs r0, r6, %2 \n\t" \ + " addcs r0, %1 \n\t" \ + " movcs r6, r0 \n\t" + +static void +pa_volume_s16ne_arm (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + int32_t *ve; + + channels = PA_MAX (4U, channels); + ve = volumes + channels; + + __asm__ __volatile__ ( + " mov r6, %1 \n\t" + " mov %3, %3, LSR #1 \n\t" /* length /= sizeof (int16_t) */ + " tst %3, #1 \n\t" /* check for odd samples */ + " beq 2f \n\t" + + "1: \n\t" + " ldr r0, [r6], #4 \n\t" /* odd samples volumes */ + " ldrh r2, [%0] \n\t" + + " smulwb r0, r0, r2 \n\t" + " ssat r0, #16, r0 \n\t" + + " strh r0, [%0], #2 \n\t" + + MOD_INC() + + "2: \n\t" + " mov %3, %3, LSR #1 \n\t" + " tst %3, #1 \n\t" /* check for odd samples */ + " beq 4f \n\t" + + "3: \n\t" + " ldrd r2, [r6], #8 \n\t" /* 2 samples at a time */ + " ldr r0, [%0] \n\t" + + " smulwt r2, r2, r0 \n\t" + " smulwb r3, r3, r0 \n\t" + + " ssat r2, #16, r2 \n\t" + " ssat r3, #16, r3 \n\t" + + " pkhbt r0, r3, r2, LSL #16 \n\t" + " str r0, [%0], #4 \n\t" + + MOD_INC() + + "4: \n\t" + " movs %3, %3, LSR #1 \n\t" + " beq 6f \n\t" + + "5: \n\t" + " ldrd r2, [r6], #8 \n\t" /* 4 samples at a time */ + " ldrd r4, [r6], #8 \n\t" + " ldrd r0, [%0] \n\t" + + " smulwt r2, r2, r0 \n\t" + " smulwb r3, r3, r0 \n\t" + " smulwt r4, r4, r1 \n\t" + " smulwb r5, r5, r1 \n\t" + + " ssat r2, #16, r2 \n\t" + " ssat r3, #16, r3 \n\t" + " ssat r4, #16, r4 \n\t" + " ssat r5, #16, r5 \n\t" + + " pkhbt r0, r3, r2, LSL #16 \n\t" + " pkhbt r1, r5, r4, LSL #16 \n\t" + " strd r0, [%0], #8 \n\t" + + MOD_INC() + + " subs %3, %3, #1 \n\t" + " bne 5b \n\t" + "6: \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (ve), "+r" (length) + : + : "r6", "r5", "r4", "r3", "r2", "r1", "r0", "cc" + ); +} + +#undef RUN_TEST + +#ifdef RUN_TEST +#define CHANNELS 2 +#define SAMPLES 1023 +#define TIMES 1000 +#define PADDING 16 + +static void run_test (void) { + int16_t samples[SAMPLES]; + int16_t samples_ref[SAMPLES]; + int16_t samples_orig[SAMPLES]; + int32_t volumes[CHANNELS + PADDING]; + int i, j, padding; + pa_do_volume_func_t func; + pa_usec_t start, stop; + + func = pa_get_volume_func (PA_SAMPLE_S16NE); + + printf ("checking ARM %zd\n", sizeof (samples)); + + pa_random (samples, sizeof (samples)); + memcpy (samples_ref, samples, sizeof (samples)); + memcpy (samples_orig, samples, sizeof (samples)); + + for (i = 0; i < CHANNELS; i++) + volumes[i] = rand() >> 1; + for (padding = 0; padding < PADDING; padding++, i++) + volumes[i] = volumes[padding]; + + func (samples_ref, volumes, CHANNELS, sizeof (samples)); + pa_volume_s16ne_arm (samples, volumes, CHANNELS, sizeof (samples)); + for (i = 0; i < SAMPLES; i++) { + if (samples[i] != samples_ref[i]) { + printf ("%d: %04x != %04x (%04x * %04x)\n", i, samples[i], samples_ref[i], + samples_orig[i], volumes[i % CHANNELS]); + } + } + + start = pa_rtclock_now(); + for (j = 0; j < TIMES; j++) { + memcpy (samples, samples_orig, sizeof (samples)); + pa_volume_s16ne_arm (samples, volumes, CHANNELS, sizeof (samples)); + } + stop = pa_rtclock_now(); + pa_log_info("ARM: %llu usec.", (long long unsigned int) (stop - start)); + + start = pa_rtclock_now(); + for (j = 0; j < TIMES; j++) { + memcpy (samples_ref, samples_orig, sizeof (samples)); + func (samples_ref, volumes, CHANNELS, sizeof (samples)); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int) (stop - start)); +} +#endif + +#endif /* defined (__arm__) */ + + +void pa_volume_func_init_arm (pa_cpu_arm_flag_t flags) { +#if defined (__arm__) + pa_log_info("Initialising ARM optimized functions."); + +#ifdef RUN_TEST + run_test (); +#endif + + pa_set_volume_func (PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_arm); +#endif /* defined (__arm__) */ +} diff --git a/src/pulsecore/svolume_c.c b/src/pulsecore/svolume_c.c new file mode 100644 index 00000000..5fc052b8 --- /dev/null +++ b/src/pulsecore/svolume_c.c @@ -0,0 +1,335 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/core-util.h> + +#include "sample-util.h" +#include "endianmacros.h" + +static void +pa_volume_u8_c (uint8_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + for (channel = 0; length; length--) { + int32_t t, hi, lo; + + hi = volumes[channel] >> 16; + lo = volumes[channel] & 0xFFFF; + + t = (int32_t) *samples - 0x80; + t = ((t * lo) >> 16) + (t * hi); + t = PA_CLAMP_UNLIKELY(t, -0x80, 0x7F); + *samples++ = (uint8_t) (t + 0x80); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_alaw_c (uint8_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + for (channel = 0; length; length--) { + int32_t t, hi, lo; + + hi = volumes[channel] >> 16; + lo = volumes[channel] & 0xFFFF; + + t = (int32_t) st_alaw2linear16(*samples); + t = ((t * lo) >> 16) + (t * hi); + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = (uint8_t) st_13linear2alaw((int16_t) t >> 3); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_ulaw_c (uint8_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + for (channel = 0; length; length--) { + int32_t t, hi, lo; + + hi = volumes[channel] >> 16; + lo = volumes[channel] & 0xFFFF; + + t = (int32_t) st_ulaw2linear16(*samples); + t = ((t * lo) >> 16) + (t * hi); + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = (uint8_t) st_14linear2ulaw((int16_t) t >> 2); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s16ne_c (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (int16_t); + + for (channel = 0; length; length--) { + int32_t t, hi, lo; + + /* Multiplying the 32bit volume factor with the 16bit + * sample might result in an 48bit value. We want to + * do without 64 bit integers and hence do the + * multiplication independantly for the HI and LO part + * of the volume. */ + + hi = volumes[channel] >> 16; + lo = volumes[channel] & 0xFFFF; + + t = (int32_t)(*samples); + t = ((t * lo) >> 16) + (t * hi); + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = (int16_t) t; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s16re_c (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (int16_t); + + for (channel = 0; length; length--) { + int32_t t, hi, lo; + + hi = volumes[channel] >> 16; + lo = volumes[channel] & 0xFFFF; + + t = (int32_t) PA_INT16_SWAP(*samples); + t = ((t * lo) >> 16) + (t * hi); + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = PA_INT16_SWAP((int16_t) t); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_float32ne_c (float *samples, float *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (float); + + for (channel = 0; length; length--) { + *samples++ *= volumes[channel]; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_float32re_c (float *samples, float *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (float); + + for (channel = 0; length; length--) { + float t; + + t = PA_FLOAT32_SWAP(*samples); + t *= volumes[channel]; + *samples++ = PA_FLOAT32_SWAP(t); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s32ne_c (int32_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (int32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t)(*samples); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = (int32_t) t; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s32re_c (int32_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (int32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t) PA_INT32_SWAP(*samples); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = PA_INT32_SWAP((int32_t) t); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s24ne_c (uint8_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + uint8_t *e; + + e = samples + length; + + for (channel = 0; samples < e; samples += 3) { + int64_t t; + + t = (int64_t)((int32_t) (PA_READ24NE(samples) << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + PA_WRITE24NE(samples, ((uint32_t) (int32_t) t) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s24re_c (uint8_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + uint8_t *e; + + e = samples + length; + + for (channel = 0; samples < e; samples += 3) { + int64_t t; + + t = (int64_t)((int32_t) (PA_READ24RE(samples) << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + PA_WRITE24RE(samples, ((uint32_t) (int32_t) t) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s24_32ne_c (uint32_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (uint32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t) ((int32_t) (*samples << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = ((uint32_t) ((int32_t) t)) >> 8; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void +pa_volume_s24_32re_c (uint32_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + unsigned channel; + + length /= sizeof (uint32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t) ((int32_t) (PA_UINT32_SWAP(*samples) << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = PA_UINT32_SWAP(((uint32_t) ((int32_t) t)) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static pa_do_volume_func_t do_volume_table[] = +{ + [PA_SAMPLE_U8] = (pa_do_volume_func_t) pa_volume_u8_c, + [PA_SAMPLE_ALAW] = (pa_do_volume_func_t) pa_volume_alaw_c, + [PA_SAMPLE_ULAW] = (pa_do_volume_func_t) pa_volume_ulaw_c, + [PA_SAMPLE_S16NE] = (pa_do_volume_func_t) pa_volume_s16ne_c, + [PA_SAMPLE_S16RE] = (pa_do_volume_func_t) pa_volume_s16re_c, + [PA_SAMPLE_FLOAT32NE] = (pa_do_volume_func_t) pa_volume_float32ne_c, + [PA_SAMPLE_FLOAT32RE] = (pa_do_volume_func_t) pa_volume_float32re_c, + [PA_SAMPLE_S32NE] = (pa_do_volume_func_t) pa_volume_s32ne_c, + [PA_SAMPLE_S32RE] = (pa_do_volume_func_t) pa_volume_s32re_c, + [PA_SAMPLE_S24NE] = (pa_do_volume_func_t) pa_volume_s24ne_c, + [PA_SAMPLE_S24RE] = (pa_do_volume_func_t) pa_volume_s24re_c, + [PA_SAMPLE_S24_32NE] = (pa_do_volume_func_t) pa_volume_s24_32ne_c, + [PA_SAMPLE_S24_32RE] = (pa_do_volume_func_t) pa_volume_s24_32re_c +}; + +pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f) { + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return do_volume_table[f]; +} + +void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func) { + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + do_volume_table[f] = func; +} diff --git a/src/pulsecore/svolume_mmx.c b/src/pulsecore/svolume_mmx.c new file mode 100644 index 00000000..a011789c --- /dev/null +++ b/src/pulsecore/svolume_mmx.c @@ -0,0 +1,319 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/timeval.h> +#include <pulse/rtclock.h> + +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/core-util.h> + +#include "cpu-x86.h" + +#include "sample-util.h" +#include "endianmacros.h" + +#if defined (__i386__) || defined (__amd64__) +/* in s: 2 int16_t samples + * in v: 2 int32_t volumes, fixed point 16:16 + * out s: contains scaled and clamped int16_t samples. + * + * We calculate the high 32 bits of a 32x16 multiply which we then + * clamp to 16 bits. The calulcation is: + * + * vl = (v & 0xffff) + * vh = (v >> 16) + * s = ((s * vl) >> 16) + (s * vh); + * + * For the first multiply we have to do a sign correction as we need to + * multiply a signed int with an unsigned int. Hacker's delight 8-3 gives a + * simple formula to correct the sign of the high word after the signed + * multiply. + */ +#define VOLUME_32x16(s,v) /* .. | vh | vl | */ \ + " pxor %%mm4, %%mm4 \n\t" /* .. | 0 | 0 | */ \ + " punpcklwd %%mm4, "#s" \n\t" /* .. | 0 | p0 | */ \ + " pcmpgtw "#v", %%mm4 \n\t" /* .. | 0 | s(vl) | */ \ + " pand "#s", %%mm4 \n\t" /* .. | 0 | (p0) | (vl >> 15) & p */ \ + " movq "#s", %%mm5 \n\t" \ + " pmulhw "#v", "#s" \n\t" /* .. | 0 | vl*p0 | */ \ + " paddw %%mm4, "#s" \n\t" /* .. | 0 | vl*p0 | + sign correct */ \ + " pslld $16, "#s" \n\t" /* .. | vl*p0 | 0 | */ \ + " psrld $16, "#v" \n\t" /* .. | 0 | vh | */ \ + " psrad $16, "#s" \n\t" /* .. | vl*p0 | sign extend */ \ + " pmaddwd %%mm5, "#v" \n\t" /* .. | p0 * vh | */ \ + " paddd "#s", "#v" \n\t" /* .. | p0 * v0 | */ \ + " packssdw "#v", "#v" \n\t" /* .. | p1*v1 | p0*v0 | */ + +/* approximately advances %3 = (%3 + a) % b. This function requires that + * a <= b. */ +#define MOD_ADD(a,b) \ + " add "#a", %3 \n\t" \ + " mov %3, %4 \n\t" \ + " sub "#b", %4 \n\t" \ + " cmovae %4, %3 \n\t" + +/* swap 16 bits */ +#define SWAP_16(s) \ + " movq "#s", %%mm4 \n\t" /* .. | h l | */ \ + " psrlw $8, %%mm4 \n\t" /* .. | 0 h | */ \ + " psllw $8, "#s" \n\t" /* .. | l 0 | */ \ + " por %%mm4, "#s" \n\t" /* .. | l h | */ + +/* swap 2 registers 16 bits for better pairing */ +#define SWAP_16_2(s1,s2) \ + " movq "#s1", %%mm4 \n\t" /* .. | h l | */ \ + " movq "#s2", %%mm5 \n\t" \ + " psrlw $8, %%mm4 \n\t" /* .. | 0 h | */ \ + " psrlw $8, %%mm5 \n\t" \ + " psllw $8, "#s1" \n\t" /* .. | l 0 | */ \ + " psllw $8, "#s2" \n\t" \ + " por %%mm4, "#s1" \n\t" /* .. | l h | */ \ + " por %%mm5, "#s2" \n\t" + +static void +pa_volume_s16ne_mmx (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + pa_reg_x86 channel, temp; + + /* the max number of samples we process at a time, this is also the max amount + * we overread the volume array, which should have enough padding. */ + channels = PA_MAX (4U, channels); + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%1, %3, 4), %%mm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " movd %4, %%mm1 \n\t" + VOLUME_32x16 (%%mm1, %%mm0) + " movd %%mm0, %4 \n\t" /* .. | p0*v0 | */ + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" /* check for odd samples */ + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + VOLUME_32x16 (%%mm1, %%mm0) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " cmp $0, %2 \n\t" + " je 6f \n\t" + + "5: \n\t" /* do samples in groups of 4 */ + " movq (%1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movq 8(%1, %3, 4), %%mm2 \n\t" /* | v3h | v3l | v2h | v2l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + " movd 4(%0), %%mm3 \n\t" /* .. | p3 | p2 | */ + VOLUME_32x16 (%%mm1, %%mm0) + VOLUME_32x16 (%%mm3, %%mm2) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " movd %%mm2, 4(%0) \n\t" /* .. | p3*v3 | p2*v2 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + " dec %2 \n\t" + " jne 5b \n\t" + + "6: \n\t" + " emms \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=D" ((pa_reg_x86)channel), "=&r" (temp) + : "rm" ((pa_reg_x86)channels) + : "cc" + ); +} + +static void +pa_volume_s16re_mmx (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + pa_reg_x86 channel, temp; + + /* the max number of samples we process at a time, this is also the max amount + * we overread the volume array, which should have enough padding. */ + channels = PA_MAX (4U, channels); + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + " pcmpeqw %%mm6, %%mm6 \n\t" /* .. | ffff | ffff | */ + " pcmpeqw %%mm7, %%mm7 \n\t" /* .. | ffff | ffff | */ + " pslld $16, %%mm6 \n\t" /* .. | ffff | 0 | */ + " psrld $31, %%mm7 \n\t" /* .. | 0 | 1 | */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%1, %3, 4), %%mm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " rorw $8, %w4 \n\t" + " movd %4, %%mm1 \n\t" + VOLUME_32x16 (%%mm1, %%mm0) + " movd %%mm0, %4 \n\t" /* .. | p0*v0 | */ + " rorw $8, %w4 \n\t" + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" /* check for odd samples */ + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + SWAP_16 (%%mm1) + VOLUME_32x16 (%%mm1, %%mm0) + SWAP_16 (%%mm0) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " cmp $0, %2 \n\t" + " je 6f \n\t" + + "5: \n\t" /* do samples in groups of 4 */ + " movq (%1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movq 8(%1, %3, 4), %%mm2 \n\t" /* | v3h | v3l | v2h | v2l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + " movd 4(%0), %%mm3 \n\t" /* .. | p3 | p2 | */ + SWAP_16_2 (%%mm1, %%mm3) + VOLUME_32x16 (%%mm1, %%mm0) + VOLUME_32x16 (%%mm3, %%mm2) + SWAP_16_2 (%%mm0, %%mm2) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " movd %%mm2, 4(%0) \n\t" /* .. | p3*v3 | p2*v2 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + " dec %2 \n\t" + " jne 5b \n\t" + + "6: \n\t" + " emms \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=D" ((pa_reg_x86)channel), "=&r" (temp) + : "rm" ((pa_reg_x86)channels) + : "cc" + ); +} + +#undef RUN_TEST + +#ifdef RUN_TEST +#define CHANNELS 2 +#define SAMPLES 1021 +#define TIMES 1000 +#define PADDING 16 + +static void run_test (void) { + int16_t samples[SAMPLES]; + int16_t samples_ref[SAMPLES]; + int16_t samples_orig[SAMPLES]; + int32_t volumes[CHANNELS + PADDING]; + int i, j, padding; + pa_do_volume_func_t func; + pa_usec_t start, stop; + + func = pa_get_volume_func (PA_SAMPLE_S16NE); + + printf ("checking MMX %zd\n", sizeof (samples)); + + pa_random (samples, sizeof (samples)); + /* for (i = 0; i < SAMPLES; i++) + samples[i] = -1; */ + memcpy (samples_ref, samples, sizeof (samples)); + memcpy (samples_orig, samples, sizeof (samples)); + + for (i = 0; i < CHANNELS; i++) + volumes[i] = rand() >> 1; + /* volumes[i] = 0x0000ffff; */ + for (padding = 0; padding < PADDING; padding++, i++) + volumes[i] = volumes[padding]; + + func (samples_ref, volumes, CHANNELS, sizeof (samples)); + pa_volume_s16ne_mmx (samples, volumes, CHANNELS, sizeof (samples)); + for (i = 0; i < SAMPLES; i++) { + if (samples[i] != samples_ref[i]) { + printf ("%d: %04x != %04x (%04x * %08x)\n", i, samples[i], samples_ref[i], + samples_orig[i], volumes[i % CHANNELS]); + } + } + + start = pa_rtclock_now(); + for (j = 0; j < TIMES; j++) { + memcpy (samples, samples_orig, sizeof (samples)); + pa_volume_s16ne_mmx (samples, volumes, CHANNELS, sizeof (samples)); + } + stop = pa_rtclock_now(); + pa_log_info("MMX: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (j = 0; j < TIMES; j++) { + memcpy (samples_ref, samples_orig, sizeof (samples)); + func (samples_ref, volumes, CHANNELS, sizeof (samples)); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); + + pa_assert_se(memcmp(samples_ref, samples, sizeof(samples)) == 0); +} +#endif + +#endif /* defined (__i386__) || defined (__amd64__) */ + + +void pa_volume_func_init_mmx (pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + +#ifdef RUN_TEST + run_test (); +#endif + + if (flags & PA_CPU_X86_MMX) { + pa_log_info("Initialising MMX optimized functions."); + + pa_set_volume_func (PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_mmx); + pa_set_volume_func (PA_SAMPLE_S16RE, (pa_do_volume_func_t) pa_volume_s16re_mmx); + } +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/svolume_sse.c b/src/pulsecore/svolume_sse.c new file mode 100644 index 00000000..620524fa --- /dev/null +++ b/src/pulsecore/svolume_sse.c @@ -0,0 +1,321 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/timeval.h> +#include <pulse/rtclock.h> + +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/core-util.h> + +#include "cpu-x86.h" + +#include "sample-util.h" +#include "endianmacros.h" + +#if defined (__i386__) || defined (__amd64__) + +#define VOLUME_32x16(s,v) /* .. | vh | vl | */ \ + " pxor %%xmm4, %%xmm4 \n\t" /* .. | 0 | 0 | */ \ + " punpcklwd %%xmm4, "#s" \n\t" /* .. | 0 | p0 | */ \ + " pcmpgtw "#s", %%xmm4 \n\t" /* .. | 0 | s(p0) | */ \ + " pand "#v", %%xmm4 \n\t" /* .. | 0 | (vl) | */ \ + " movdqa "#s", %%xmm5 \n\t" \ + " pmulhuw "#v", "#s" \n\t" /* .. | 0 | vl*p0 | */ \ + " psubd %%xmm4, "#s" \n\t" /* .. | 0 | vl*p0 | + sign correct */ \ + " psrld $16, "#v" \n\t" /* .. | 0 | vh | */ \ + " pmaddwd %%xmm5, "#v" \n\t" /* .. | p0 * vh | */ \ + " paddd "#s", "#v" \n\t" /* .. | p0 * v0 | */ \ + " packssdw "#v", "#v" \n\t" /* .. | p1*v1 | p0*v0 | */ + +#define MOD_ADD(a,b) \ + " add "#a", %3 \n\t" /* channel += inc */ \ + " mov %3, %4 \n\t" \ + " sub "#b", %4 \n\t" /* tmp = channel - channels */ \ + " cmovae %4, %3 \n\t" /* if (tmp >= 0) channel = tmp */ + +/* swap 16 bits */ +#define SWAP_16(s) \ + " movdqa "#s", %%xmm4 \n\t" /* .. | h l | */ \ + " psrlw $8, %%xmm4 \n\t" /* .. | 0 h | */ \ + " psllw $8, "#s" \n\t" /* .. | l 0 | */ \ + " por %%xmm4, "#s" \n\t" /* .. | l h | */ + +/* swap 2 registers 16 bits for better pairing */ +#define SWAP_16_2(s1,s2) \ + " movdqa "#s1", %%xmm4 \n\t" /* .. | h l | */ \ + " movdqa "#s2", %%xmm5 \n\t" \ + " psrlw $8, %%xmm4 \n\t" /* .. | 0 h | */ \ + " psrlw $8, %%xmm5 \n\t" \ + " psllw $8, "#s1" \n\t" /* .. | l 0 | */ \ + " psllw $8, "#s2" \n\t" \ + " por %%xmm4, "#s1" \n\t" /* .. | l h | */ \ + " por %%xmm5, "#s2" \n\t" + +static void +pa_volume_s16ne_sse2 (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + pa_reg_x86 channel, temp; + + /* the max number of samples we process at a time, this is also the max amount + * we overread the volume array, which should have enough padding. */ + channels = PA_MAX (8U, channels); + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%1, %3, 4), %%xmm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " movd %4, %%xmm1 \n\t" + VOLUME_32x16 (%%xmm1, %%xmm0) + " movd %%xmm0, %4 \n\t" /* .. | p0*v0 | */ + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%1, %3, 4), %%xmm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%xmm1 \n\t" /* .. | p1 | p0 | */ + VOLUME_32x16 (%%xmm1, %%xmm0) + " movd %%xmm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " test $1, %2 \n\t" + " je 6f \n\t" + + /* FIXME, we can do aligned access of the volume values if we can guarantee + * that the array is 16 bytes aligned, we probably have to do the odd values + * after this then. */ + "5: \n\t" /* do samples in groups of 4 */ + " movdqu (%1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + VOLUME_32x16 (%%xmm1, %%xmm0) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + + "6: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 8 samples at a time */ + " cmp $0, %2 \n\t" + " je 8f \n\t" + + "7: \n\t" /* do samples in groups of 8 */ + " movdqu (%1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movdqu 16(%1, %3, 4), %%xmm2 \n\t" /* | v7h | v7l .. v4h | v4l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + " movq 8(%0), %%xmm3 \n\t" /* .. | p7 .. p4 | */ + VOLUME_32x16 (%%xmm1, %%xmm0) + VOLUME_32x16 (%%xmm3, %%xmm2) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " movq %%xmm2, 8(%0) \n\t" /* .. | p7*v7 .. p4*v4 | */ + " add $16, %0 \n\t" + MOD_ADD ($8, %5) + " dec %2 \n\t" + " jne 7b \n\t" + "8: \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=D" (channel), "=&r" (temp) + : "rm" ((pa_reg_x86)channels) + : "cc" + ); +} + +static void +pa_volume_s16re_sse2 (int16_t *samples, int32_t *volumes, unsigned channels, unsigned length) +{ + pa_reg_x86 channel, temp; + + /* the max number of samples we process at a time, this is also the max amount + * we overread the volume array, which should have enough padding. */ + channels = PA_MAX (8U, channels); + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%1, %3, 4), %%xmm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " rorw $8, %w4 \n\t" + " movd %4, %%xmm1 \n\t" + VOLUME_32x16 (%%xmm1, %%xmm0) + " movd %%xmm0, %4 \n\t" /* .. | p0*v0 | */ + " rorw $8, %w4 \n\t" + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%1, %3, 4), %%xmm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%xmm1 \n\t" /* .. | p1 | p0 | */ + SWAP_16 (%%xmm1) + VOLUME_32x16 (%%xmm1, %%xmm0) + SWAP_16 (%%xmm0) + " movd %%xmm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " test $1, %2 \n\t" + " je 6f \n\t" + + /* FIXME, we can do aligned access of the volume values if we can guarantee + * that the array is 16 bytes aligned, we probably have to do the odd values + * after this then. */ + "5: \n\t" /* do samples in groups of 4 */ + " movdqu (%1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + SWAP_16 (%%xmm1) + VOLUME_32x16 (%%xmm1, %%xmm0) + SWAP_16 (%%xmm0) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + + "6: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 8 samples at a time */ + " cmp $0, %2 \n\t" + " je 8f \n\t" + + "7: \n\t" /* do samples in groups of 8 */ + " movdqu (%1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movdqu 16(%1, %3, 4), %%xmm2 \n\t" /* | v7h | v7l .. v4h | v4l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + " movq 8(%0), %%xmm3 \n\t" /* .. | p7 .. p4 | */ + SWAP_16_2 (%%xmm1, %%xmm3) + VOLUME_32x16 (%%xmm1, %%xmm0) + VOLUME_32x16 (%%xmm3, %%xmm2) + SWAP_16_2 (%%xmm0, %%xmm2) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " movq %%xmm2, 8(%0) \n\t" /* .. | p7*v7 .. p4*v4 | */ + " add $16, %0 \n\t" + MOD_ADD ($8, %5) + " dec %2 \n\t" + " jne 7b \n\t" + "8: \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=D" (channel), "=&r" (temp) + : "rm" ((pa_reg_x86)channels) + : "cc" + ); +} + +#undef RUN_TEST + +#ifdef RUN_TEST +#define CHANNELS 2 +#define SAMPLES 1021 +#define TIMES 1000 +#define PADDING 16 + +static void run_test (void) { + int16_t samples[SAMPLES]; + int16_t samples_ref[SAMPLES]; + int16_t samples_orig[SAMPLES]; + int32_t volumes[CHANNELS + PADDING]; + int i, j, padding; + pa_do_volume_func_t func; + pa_usec_t start, stop; + + func = pa_get_volume_func (PA_SAMPLE_S16NE); + + printf ("checking SSE2 %zd\n", sizeof (samples)); + + pa_random (samples, sizeof (samples)); + memcpy (samples_ref, samples, sizeof (samples)); + memcpy (samples_orig, samples, sizeof (samples)); + + for (i = 0; i < CHANNELS; i++) + volumes[i] = rand() >> 1; + for (padding = 0; padding < PADDING; padding++, i++) + volumes[i] = volumes[padding]; + + func (samples_ref, volumes, CHANNELS, sizeof (samples)); + pa_volume_s16ne_sse2 (samples, volumes, CHANNELS, sizeof (samples)); + for (i = 0; i < SAMPLES; i++) { + if (samples[i] != samples_ref[i]) { + printf ("%d: %04x != %04x (%04x * %04x)\n", i, samples[i], samples_ref[i], + samples_orig[i], volumes[i % CHANNELS]); + } + } + + start = pa_rtclock_now(); + for (j = 0; j < TIMES; j++) { + memcpy (samples, samples_orig, sizeof (samples)); + pa_volume_s16ne_sse2 (samples, volumes, CHANNELS, sizeof (samples)); + } + stop = pa_rtclock_now(); + pa_log_info("SSE: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (j = 0; j < TIMES; j++) { + memcpy (samples_ref, samples_orig, sizeof (samples)); + func (samples_ref, volumes, CHANNELS, sizeof (samples)); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); + + pa_assert_se(memcmp(samples_ref, samples, sizeof(samples)) == 0); +} +#endif +#endif /* defined (__i386__) || defined (__amd64__) */ + +void pa_volume_func_init_sse (pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + +#ifdef RUN_TEST + run_test (); +#endif + + if (flags & PA_CPU_X86_SSE2) { + pa_log_info("Initialising SSE2 optimized functions."); + + pa_set_volume_func (PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_sse2); + pa_set_volume_func (PA_SAMPLE_S16RE, (pa_do_volume_func_t) pa_volume_s16re_sse2); + } +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c index 34f92a7e..73997a74 100644 --- a/src/pulsecore/thread-mq.c +++ b/src/pulsecore/thread-mq.c @@ -59,7 +59,7 @@ static void asyncmsgq_read_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io pa_memchunk chunk; /* Check whether there is a message for us to process */ - while (pa_asyncmsgq_get(aq, &object, &code, &data, &offset, &chunk, 0) == 0) { + while (pa_asyncmsgq_get(aq, &object, &code, &data, &offset, &chunk, 0) >= 0) { int ret; ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); @@ -104,6 +104,15 @@ void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rt void pa_thread_mq_done(pa_thread_mq *q) { pa_assert(q); + /* Since we are called from main context we can be sure that the + * inq is empty. However, the outq might still contain messages + * for the main loop, which we need to dispatch (e.g. release + * msgs, other stuff). Hence do so if we aren't currently + * dispatching anyway. */ + + if (!pa_asyncmsgq_dispatching(q->outq)) + pa_asyncmsgq_flush(q->outq, TRUE); + q->mainloop->io_free(q->read_event); q->mainloop->io_free(q->write_event); q->read_event = q->write_event = NULL; diff --git a/src/pulsecore/thread-mq.h b/src/pulsecore/thread-mq.h index 3b5e0e78..96839d25 100644 --- a/src/pulsecore/thread-mq.h +++ b/src/pulsecore/thread-mq.h @@ -45,4 +45,12 @@ void pa_thread_mq_install(pa_thread_mq *q); /* Return the pa_thread_mq object that is set for the current thread */ pa_thread_mq *pa_thread_mq_get(void); +/* Verify that we are in control context (aka 'main context'). */ +#define pa_assert_ctl_context(s) \ + pa_assert(!pa_thread_mq_get()) + +/* Verify that we are in IO context (aka 'thread context'). */ +#define pa_assert_io_context(s) \ + pa_assert(pa_thread_mq_get()) + #endif diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c index d6c37878..1371ad56 100644 --- a/src/pulsecore/time-smoother.c +++ b/src/pulsecore/time-smoother.c @@ -196,6 +196,13 @@ static double avg_gradient(pa_smoother *s, pa_usec_t x) { int64_t ax = 0, ay = 0, k, t; double r; + /* FIXME: Optimization: Jason Newton suggested that instead of + * going through the history on each iteration we could calculated + * avg_gradient() as we go. + * + * Second idea: it might make sense to weight history entries: + * more recent entries should matter more than old ones. */ + /* Too few measurements, assume gradient of 1 */ if (s->n_history < s->min_history) return 1; diff --git a/src/pulsecore/usergroup.c b/src/pulsecore/usergroup.c new file mode 100644 index 00000000..71b13bca --- /dev/null +++ b/src/pulsecore/usergroup.c @@ -0,0 +1,372 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Ted Percival + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <errno.h> + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "usergroup.h" + +#ifdef HAVE_GRP_H + +/* Returns a suitable starting size for a getgrnam_r() or getgrgid_r() buffer, + plus the size of a struct group. + */ +static size_t starting_getgr_buflen(void) { + size_t full_size; + long n; +#ifdef _SC_GETGR_R_SIZE_MAX + n = sysconf(_SC_GETGR_R_SIZE_MAX); +#else + n = -1; +#endif + if (n <= 0) + n = 512; + + full_size = (size_t) n + sizeof(struct group); + + if (full_size < (size_t) n) /* check for integer overflow */ + return (size_t) n; + + return full_size; +} + +/* Returns a suitable starting size for a getpwnam_r() or getpwuid_r() buffer, + plus the size of a struct passwd. + */ +static size_t starting_getpw_buflen(void) { + long n; + size_t full_size; + +#ifdef _SC_GETPW_R_SIZE_MAX + n = sysconf(_SC_GETPW_R_SIZE_MAX); +#else + n = -1; +#endif + if (n <= 0) + n = 512; + + full_size = (size_t) n + sizeof(struct passwd); + + if (full_size < (size_t) n) /* check for integer overflow */ + return (size_t) n; + + return full_size; +} + +/* Given a memory allocation (*bufptr) and its length (*buflenptr), + double the size of the allocation, updating the given buffer and length + arguments. This function should be used in conjunction with the pa_*alloc + and pa_xfree functions. + + Unlike realloc(), this function does *not* retain the original buffer's + contents. + + Returns 0 on success, nonzero on error. The error cause is indicated by + errno. + */ +static int expand_buffer_trashcontents(void **bufptr, size_t *buflenptr) { + size_t newlen; + + if (!bufptr || !*bufptr || !buflenptr) { + errno = EINVAL; + return -1; + } + + newlen = *buflenptr * 2; + + if (newlen < *buflenptr) { + errno = EOVERFLOW; + return -1; + } + + /* Don't bother retaining memory contents; free & alloc anew */ + pa_xfree(*bufptr); + + *bufptr = pa_xmalloc(newlen); + *buflenptr = newlen; + + return 0; +} + +#ifdef HAVE_GETGRGID_R +/* Thread-safe getgrgid() replacement. + Returned value should be freed using pa_getgrgid_free() when the caller is + finished with the returned group data. + + API is the same as getgrgid(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct group *pa_getgrgid_malloc(gid_t gid) { + size_t buflen, getgr_buflen; + int err; + void *buf; + void *getgr_buf; + struct group *result = NULL; + + buflen = starting_getgr_buflen(); + buf = pa_xmalloc(buflen); + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + + while ((err = getgrgid_r(gid, (struct group *)buf, getgr_buf, + getgr_buflen, &result)) == ERANGE) + { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getgrgid_free(struct group *grp) { + pa_xfree(grp); +} + +#else /* !HAVE_GETGRGID_R */ + +struct group *pa_getgrgid_malloc(gid_t gid) { + return getgrgid(gid); +} + +void pa_getgrgid_free(struct group *grp) { + /* nothing */ + return; +} + +#endif /* !HAVE_GETGRGID_R */ + +#ifdef HAVE_GETGRNAM_R +/* Thread-safe getgrnam() function. + Returned value should be freed using pa_getgrnam_free() when the caller is + finished with the returned group data. + + API is the same as getgrnam(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct group *pa_getgrnam_malloc(const char *name) { + size_t buflen, getgr_buflen; + int err; + void *buf; + void *getgr_buf; + struct group *result = NULL; + + buflen = starting_getgr_buflen(); + buf = pa_xmalloc(buflen); + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + + while ((err = getgrnam_r(name, (struct group *)buf, getgr_buf, + getgr_buflen, &result)) == ERANGE) + { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getgrnam_free(struct group *group) { + pa_xfree(group); +} + +#else /* !HAVE_GETGRNAM_R */ + +struct group *pa_getgrnam_malloc(const char *name) { + return getgrnam(name); +} + +void pa_getgrnam_free(struct group *group) { + /* nothing */ + return; +} + +#endif /* HAVE_GETGRNAM_R */ + +#endif /* HAVE_GRP_H */ + +#ifdef HAVE_PWD_H + +#ifdef HAVE_GETPWNAM_R +/* Thread-safe getpwnam() function. + Returned value should be freed using pa_getpwnam_free() when the caller is + finished with the returned passwd data. + + API is the same as getpwnam(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct passwd *pa_getpwnam_malloc(const char *name) { + size_t buflen, getpw_buflen; + int err; + void *buf; + void *getpw_buf; + struct passwd *result = NULL; + + buflen = starting_getpw_buflen(); + buf = pa_xmalloc(buflen); + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + + while ((err = getpwnam_r(name, (struct passwd *)buf, getpw_buf, + getpw_buflen, &result)) == ERANGE) + { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getpwnam_free(struct passwd *passwd) { + pa_xfree(passwd); +} + +#else /* !HAVE_GETPWNAM_R */ + +struct passwd *pa_getpwnam_malloc(const char *name) { + return getpwnam(name); +} + +void pa_getpwnam_free(struct passwd *passwd) { + /* nothing */ + return; +} + +#endif /* !HAVE_GETPWNAM_R */ + +#ifdef HAVE_GETPWUID_R +/* Thread-safe getpwuid() function. + Returned value should be freed using pa_getpwuid_free() when the caller is + finished with the returned group data. + + API is the same as getpwuid(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct passwd *pa_getpwuid_malloc(uid_t uid) { + size_t buflen, getpw_buflen; + int err; + void *buf; + void *getpw_buf; + struct passwd *result = NULL; + + buflen = starting_getpw_buflen(); + buf = pa_xmalloc(buflen); + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + + while ((err = getpwuid_r(uid, (struct passwd *)buf, getpw_buf, + getpw_buflen, &result)) == ERANGE) + { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getpwuid_free(struct passwd *passwd) { + pa_xfree(passwd); +} + +#else /* !HAVE_GETPWUID_R */ + +struct passwd *pa_getpwuid_malloc(uid_t uid) { + return getpwuid(uid); +} + +void pa_getpwuid_free(struct passwd *passwd) { + /* nothing */ + return; +} + +#endif /* !HAVE_GETPWUID_R */ + +#endif /* HAVE_PWD_H */ diff --git a/src/pulsecore/usergroup.h b/src/pulsecore/usergroup.h new file mode 100644 index 00000000..1c091638 --- /dev/null +++ b/src/pulsecore/usergroup.h @@ -0,0 +1,51 @@ +#ifndef foousergrouphfoo +#define foousergrouphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Ted Percival + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sys/types.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_GRP_H + +struct group *pa_getgrgid_malloc(gid_t gid); +void pa_getgrgid_free(struct group *grp); + +struct group *pa_getgrnam_malloc(const char *name); +void pa_getgrnam_free(struct group *group); + +#endif /* HAVE_GRP_H */ + +#ifdef HAVE_PWD_H + +struct passwd *pa_getpwuid_malloc(uid_t uid); +void pa_getpwuid_free(struct passwd *passwd); + +struct passwd *pa_getpwnam_malloc(const char *name); +void pa_getpwnam_free(struct passwd *passwd); + +#endif /* HAVE_PWD_H */ + +#endif /* foousergrouphfoo */ diff --git a/src/pulsecore/vector.h b/src/pulsecore/vector.h index 924e3cb8..9de3b8cd 100644 --- a/src/pulsecore/vector.h +++ b/src/pulsecore/vector.h @@ -23,7 +23,8 @@ #include <inttypes.h> /* First, define HAVE_VECTOR if we have the gcc vector extensions at all */ -#if defined(__SSE2__) || defined(__ALTIVEC__) +#if defined(__SSE2__) + /* || defined(__ALTIVEC__)*/ #define HAVE_VECTOR diff --git a/src/pulsecore/x11prop.c b/src/pulsecore/x11prop.c index 873a76e7..dc8ec294 100644 --- a/src/pulsecore/x11prop.c +++ b/src/pulsecore/x11prop.c @@ -32,12 +32,12 @@ void pa_x11_set_prop(Display *d, const char *name, const char *data) { Atom a = XInternAtom(d, name, False); - XChangeProperty(d, RootWindow(d, 0), a, XA_STRING, 8, PropModeReplace, (const unsigned char*) data, (int) (strlen(data)+1)); + XChangeProperty(d, DefaultRootWindow(d), a, XA_STRING, 8, PropModeReplace, (const unsigned char*) data, (int) (strlen(data)+1)); } void pa_x11_del_prop(Display *d, const char *name) { Atom a = XInternAtom(d, name, False); - XDeleteProperty(d, RootWindow(d, 0), a); + XDeleteProperty(d, DefaultRootWindow(d), a); } char* pa_x11_get_prop(Display *d, const char *name, char *p, size_t l) { @@ -47,13 +47,21 @@ char* pa_x11_get_prop(Display *d, const char *name, char *p, size_t l) { unsigned long nbytes_after; unsigned char *prop = NULL; char *ret = NULL; + int window_ret; Atom a = XInternAtom(d, name, False); - if (XGetWindowProperty(d, RootWindow(d, 0), a, 0, (long) ((l+2)/4), False, XA_STRING, &actual_type, &actual_format, &nitems, &nbytes_after, &prop) != Success) - goto finish; - if (actual_type != XA_STRING) - goto finish; + window_ret = XGetWindowProperty(d, DefaultRootWindow(d), a, 0, (long) ((l+2)/4), False, XA_STRING, &actual_type, &actual_format, &nitems, &nbytes_after, &prop); + + if (window_ret != Success || actual_type != XA_STRING) { + if (DefaultScreen(d) != 0) { + window_ret = XGetWindowProperty(d, RootWindow(d, 0), a, 0, (long) ((l+2)/4), False, XA_STRING, &actual_type, &actual_format, &nitems, &nbytes_after, &prop); + + if (window_ret != Success || actual_type != XA_STRING) + goto finish; + } else + goto finish; + } memcpy(p, prop, nitems); p[nitems] = 0; diff --git a/src/tests/envelope-test.c b/src/tests/envelope-test.c index 3af3044e..9382040b 100644 --- a/src/tests/envelope-test.c +++ b/src/tests/envelope-test.c @@ -34,8 +34,6 @@ #include <pulsecore/memblock.h> #include <pulsecore/sample-util.h> -#include <liboil/liboil.h> - const pa_envelope_def ramp_down = { .n_points = 2, .points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC }, @@ -202,7 +200,6 @@ int main(int argc, char *argv[]) { .values = { PA_VOLUME_NORM, PA_VOLUME_NORM/2 } }; - oil_init(); pa_log_set_level(PA_LOG_DEBUG); pa_assert_se(pool = pa_mempool_new(FALSE, 0)); diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c index 3f65cbac..457c4acd 100644 --- a/src/tests/mix-test.c +++ b/src/tests/mix-test.c @@ -32,8 +32,6 @@ #include <pulsecore/memblock.h> #include <pulsecore/sample-util.h> -#include <liboil/liboil.h> - static float swap_float(float a) { uint32_t *b = (uint32_t*) &a; *b = PA_UINT32_SWAP(*b); @@ -69,6 +67,8 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { break; } + case PA_SAMPLE_S24_32NE: + case PA_SAMPLE_S24_32RE: case PA_SAMPLE_S32NE: case PA_SAMPLE_S32RE: { uint32_t *u = d; @@ -84,7 +84,7 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { uint8_t *u = d; for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { - printf("0x%02x%02x%02xx ", *u, *(u+1), *(u+2)); + printf("0x%02x%02x%02xx ", *u, *(u+1), *(u+2)); u += 3; } @@ -125,66 +125,72 @@ static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) { case PA_SAMPLE_U8: case PA_SAMPLE_ULAW: case PA_SAMPLE_ALAW: { - static const uint8_t u8_samples[] = - { 0x00, 0xFF, 0x7F, 0x80, 0x9f, - 0x3f, 0x01, 0xF0, 0x20, 0x21 }; + static const uint8_t u8_samples[] = { + 0x00, 0xFF, 0x7F, 0x80, 0x9f, + 0x3f, 0x01, 0xF0, 0x20, 0x21 + }; - memcpy(d, &u8_samples[0], sizeof(u8_samples)); + memcpy(d, u8_samples, sizeof(u8_samples)); break; } case PA_SAMPLE_S16NE: case PA_SAMPLE_S16RE: { - static const uint16_t u16_samples[] = - { 0x0000, 0xFFFF, 0x7FFF, 0x8000, 0x9fff, - 0x3fff, 0x0001, 0xF000, 0x0020, 0x0021 }; + static const uint16_t u16_samples[] = { + 0x0000, 0xFFFF, 0x7FFF, 0x8000, 0x9fff, + 0x3fff, 0x0001, 0xF000, 0x0020, 0x0021 + }; - memcpy(d, &u16_samples[0], sizeof(u16_samples)); + memcpy(d, u16_samples, sizeof(u16_samples)); break; } + case PA_SAMPLE_S24_32NE: + case PA_SAMPLE_S24_32RE: case PA_SAMPLE_S32NE: case PA_SAMPLE_S32RE: { - static const uint32_t u32_samples[] = - { 0x00000001, 0xFFFF0002, 0x7FFF0003, 0x80000004, 0x9fff0005, - 0x3fff0006, 0x00010007, 0xF0000008, 0x00200009, 0x0021000A }; + static const uint32_t u32_samples[] = { + 0x00000001, 0xFFFF0002, 0x7FFF0003, 0x80000004, 0x9fff0005, + 0x3fff0006, 0x00010007, 0xF0000008, 0x00200009, 0x0021000A + }; - memcpy(d, &u32_samples[0], sizeof(u32_samples)); + memcpy(d, u32_samples, sizeof(u32_samples)); break; } case PA_SAMPLE_S24NE: case PA_SAMPLE_S24RE: { - /* Need to be on a byte array because they are not aligned */ - static const uint8_t u24_samples[] = - { 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0x02, - 0x7F, 0xFF, 0x03, - 0x80, 0x00, 0x04, - 0x9f, 0xff, 0x05, - 0x3f, 0xff, 0x06, - 0x01, 0x00, 0x07, - 0xF0, 0x00, 0x08, - 0x20, 0x00, 0x09, - 0x21, 0x00, 0x0A }; - - memcpy(d, &u24_samples[0], sizeof(u24_samples)); + /* Need to be on a byte array because they are not aligned */ + static const uint8_t u24_samples[] = { + 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0x02, + 0x7F, 0xFF, 0x03, + 0x80, 0x00, 0x04, + 0x9f, 0xff, 0x05, + 0x3f, 0xff, 0x06, + 0x01, 0x00, 0x07, + 0xF0, 0x00, 0x08, + 0x20, 0x00, 0x09, + 0x21, 0x00, 0x0A + }; + + memcpy(d, u24_samples, sizeof(u24_samples)); break; } case PA_SAMPLE_FLOAT32NE: case PA_SAMPLE_FLOAT32RE: { float *u = d; - static const float float_samples[] = - { 0.0f, -1.0f, 1.0f, 4711.0f, 0.222f, - 0.33f, -.3f, 99.0f, -0.555f, -.123f }; + static const float float_samples[] = { + 0.0f, -1.0f, 1.0f, 4711.0f, 0.222f, + 0.33f, -.3f, 99.0f, -0.555f, -.123f + }; if (ss->format == PA_SAMPLE_FLOAT32RE) { for (i = 0; i < 10; i++) u[i] = swap_float(float_samples[i]); - } else { - memcpy(d, &float_samples[0], sizeof(float_samples)); - } + } else + memcpy(d, float_samples, sizeof(float_samples)); break; } @@ -203,7 +209,6 @@ int main(int argc, char *argv[]) { pa_sample_spec a; pa_cvolume v; - oil_init(); pa_log_set_level(PA_LOG_DEBUG); pa_assert_se(pool = pa_mempool_new(FALSE, 0)); diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c index 9d110d6b..4990bf93 100644 --- a/src/tests/remix-test.c +++ b/src/tests/remix-test.c @@ -32,8 +32,6 @@ #include <pulsecore/memblock.h> #include <pulsecore/sample-util.h> -#include <liboil/liboil.h> - int main(int argc, char *argv[]) { static const pa_channel_map maps[] = { @@ -55,7 +53,6 @@ int main(int argc, char *argv[]) { unsigned i, j; pa_mempool *pool; - oil_init(); pa_log_set_level(PA_LOG_DEBUG); pa_assert_se(pool = pa_mempool_new(FALSE, 0)); diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c index 7236265a..82198b5e 100644 --- a/src/tests/resampler-test.c +++ b/src/tests/resampler-test.c @@ -32,8 +32,6 @@ #include <pulsecore/memblock.h> #include <pulsecore/sample-util.h> -#include <liboil/liboil.h> - static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { void *d; unsigned i; @@ -248,7 +246,6 @@ int main(int argc, char *argv[]) { pa_sample_spec a, b; pa_cvolume v; - oil_init(); pa_log_set_level(PA_LOG_DEBUG); pa_assert_se(pool = pa_mempool_new(FALSE, 0)); diff --git a/src/tests/stripnul.c b/src/tests/stripnul.c index 1d8c4938..d677ad20 100644 --- a/src/tests/stripnul.c +++ b/src/tests/stripnul.c @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) { FILE *i, *o; size_t granularity; - pa_bool_t found; + pa_bool_t found = FALSE; uint8_t *zero; pa_assert_se(argc >= 2); diff --git a/src/tests/usergroup-test.c b/src/tests/usergroup-test.c new file mode 100644 index 00000000..a48b016d --- /dev/null +++ b/src/tests/usergroup-test.c @@ -0,0 +1,161 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Ted Percival + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> +#include <errno.h> + +#include <pulsecore/usergroup.h> + +static int load_reference_structs(struct group **gr, struct passwd **pw) { + setpwent(); + *pw = getpwent(); + endpwent(); + + setgrent(); + *gr = getgrent(); + endgrent(); + + return (*gr && *pw) ? 0 : 1; +} + +static int compare_group(const struct group *a, const struct group *b) { + char **amem, **bmem; + + if (strcmp(a->gr_name, b->gr_name)) { + fprintf(stderr, "Group name mismatch: [%s] [%s]\n", + a->gr_name, b->gr_name); + return 1; + } + + if (strcmp(a->gr_passwd, b->gr_passwd)) { + fprintf(stderr, "Group password mismatch: [%s] [%s]\n", + a->gr_passwd, b->gr_passwd); + return 1; + } + + if (a->gr_gid != b->gr_gid) { + fprintf(stderr, "Gid mismatch: [%lu] [%lu]\n", + (unsigned long) a->gr_gid, (unsigned long) b->gr_gid); + return 1; + } + + /* XXX: Assuming the group ordering is identical. */ + for (amem = a->gr_mem, bmem = b->gr_mem; *amem && *bmem; ++amem, ++bmem) { + if (strcmp(*amem, *bmem)) { + fprintf(stderr, "Group member mismatch: [%s] [%s]\n", + *amem, *bmem); + return 1; + } + } + + if (*amem || *bmem) { + fprintf(stderr, "Mismatched group count\n"); + return 1; + } + + return 0; +} + +static int compare_passwd(const struct passwd *a, const struct passwd *b) { + if (strcmp(a->pw_name, b->pw_name)) { + fprintf(stderr, "pw_name mismatch: [%s] [%s]\n", a->pw_name, b->pw_name); + return 1; + } + + if (strcmp(a->pw_passwd, b->pw_passwd)) { + fprintf(stderr, "pw_passwd mismatch: [%s] [%s]\n", a->pw_passwd, b->pw_passwd); + return 1; + } + + if (a->pw_uid != b->pw_uid) { + fprintf(stderr, "pw_uid mismatch: [%lu] [%lu]\n", + (unsigned long) a->pw_uid, (unsigned long) b->pw_uid); + return 1; + } + + if (a->pw_gid != b->pw_gid) { + fprintf(stderr, "pw_gid mismatch: [%lu] [%lu]\n", + (unsigned long) a->pw_gid, (unsigned long) b->pw_gid); + return 1; + } + + if (strcmp(a->pw_gecos, b->pw_gecos)) { + fprintf(stderr, "pw_gecos mismatch: [%s] [%s]\n", a->pw_gecos, b->pw_gecos); + return 1; + } + + if (strcmp(a->pw_dir, b->pw_dir)) { + fprintf(stderr, "pw_dir mismatch: [%s] [%s]\n", a->pw_dir, b->pw_dir); + return 1; + } + + if (strcmp(a->pw_shell, b->pw_shell)) { + fprintf(stderr, "pw_shell mismatch: [%s] [%s]\n", a->pw_shell, b->pw_shell); + return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) { + struct group *gr; + struct passwd *pw; + int err; + struct group *reference_group = NULL; + struct passwd *reference_passwd = NULL; + + err = load_reference_structs(&reference_group, &reference_passwd); + if (err) + return 77; + + errno = 0; + gr = pa_getgrgid_malloc(reference_group->gr_gid); + if (compare_group(reference_group, gr)) + return 1; + pa_getgrgid_free(gr); + + errno = 0; + gr = pa_getgrnam_malloc(reference_group->gr_name); + if (compare_group(reference_group, gr)) + return 1; + pa_getgrnam_free(gr); + + errno = 0; + pw = pa_getpwuid_malloc(reference_passwd->pw_uid); + if (compare_passwd(reference_passwd, pw)) + return 1; + pa_getpwuid_free(pw); + + errno = 0; + pw = pa_getpwnam_malloc(reference_passwd->pw_name); + if (compare_passwd(reference_passwd, pw)) + return 1; + pa_getpwnam_free(pw); + + return 0; +} diff --git a/src/tests/voltest.c b/src/tests/voltest.c index 2dcfa53c..551f7ecd 100644 --- a/src/tests/voltest.c +++ b/src/tests/voltest.c @@ -1,13 +1,40 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <stdio.h> #include <pulse/volume.h> #include <pulse/gccmacro.h> +#include <pulsecore/macro.h> + int main(int argc, char *argv[]) { pa_volume_t v; pa_cvolume cv; float b; pa_channel_map map; + pa_volume_t md = 0; + unsigned mdn = 0; printf("Attenuation of sample 1 against 32767: %g dB\n", 20.0*log10(1.0/32767.0)); printf("Smallest possible attenutation > 0 applied to 32767: %li\n", lrint(32767.0*pa_sw_volume_to_linear(1))); @@ -60,5 +87,48 @@ int main(int argc, char *argv[]) { printf("After: volume: [%s]; balance: %2.1f (intended: %2.1f) %s\n", pa_cvolume_snprint(s, sizeof(s), &r), k, b, k < b-.05 || k > b+.5 ? "MISMATCH" : ""); } + for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 51) { + + double l = pa_sw_volume_to_linear(v); + pa_volume_t k = pa_sw_volume_from_linear(l); + double db = pa_sw_volume_to_dB(v); + pa_volume_t r = pa_sw_volume_from_dB(db); + pa_volume_t w; + + pa_assert(k == v); + pa_assert(r == v); + + for (w = PA_VOLUME_MUTED; w < PA_VOLUME_NORM*2; w += 37) { + + double t = pa_sw_volume_to_linear(w); + double db2 = pa_sw_volume_to_dB(w); + pa_volume_t p, p1, p2; + double q, qq; + + p = pa_sw_volume_multiply(v, w); + qq = db + db2; + p2 = pa_sw_volume_from_dB(qq); + q = l*t; + p1 = pa_sw_volume_from_linear(q); + + if (p2 > p && p2 - p > md) + md = p2 - p; + if (p2 < p && p - p2 > md) + md = p - p2; + if (p1 > p && p1 - p > md) + md = p1 - p; + if (p1 < p && p - p1 > md) + md = p - p1; + + if (p1 != p || p2 != p) + mdn++; + } + } + + printf("max deviation: %lu n=%lu\n", (unsigned long) md, (unsigned long) mdn); + + pa_assert(md <= 1); + pa_assert(mdn <= 251); + return 0; } diff --git a/src/utils/pacat.c b/src/utils/pacat.c index 9264a062..d348c16a 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -406,7 +406,6 @@ static void context_state_callback(pa_context *c, void *userdata) { break; case PA_CONTEXT_READY: { - int r; pa_buffer_attr buffer_attr; pa_assert(c); @@ -431,25 +430,29 @@ static void context_state_callback(pa_context *c, void *userdata) { pa_stream_set_event_callback(stream, stream_event_callback, NULL); pa_stream_set_buffer_attr_callback(stream, stream_buffer_attr_callback, NULL); + pa_zero(buffer_attr); + buffer_attr.maxlength = (uint32_t) -1; + buffer_attr.prebuf = (uint32_t) -1; + if (latency > 0) { - memset(&buffer_attr, 0, sizeof(buffer_attr)); - buffer_attr.tlength = (uint32_t) latency; + buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) latency; buffer_attr.minreq = (uint32_t) process_time; - buffer_attr.maxlength = (uint32_t) -1; - buffer_attr.prebuf = (uint32_t) -1; - buffer_attr.fragsize = (uint32_t) latency; flags |= PA_STREAM_ADJUST_LATENCY; + } else { + buffer_attr.tlength = (uint32_t) -1; + buffer_attr.minreq = (uint32_t) -1; + buffer_attr.fragsize = (uint32_t) -1; } if (mode == PLAYBACK) { pa_cvolume cv; - if ((r = pa_stream_connect_playback(stream, device, latency > 0 ? &buffer_attr : NULL, flags, volume_is_set ? pa_cvolume_set(&cv, sample_spec.channels, volume) : NULL, NULL)) < 0) { + if (pa_stream_connect_playback(stream, device, latency > 0 ? &buffer_attr : NULL, flags, volume_is_set ? pa_cvolume_set(&cv, sample_spec.channels, volume) : NULL, NULL) < 0) { pa_log(_("pa_stream_connect_playback() failed: %s"), pa_strerror(pa_context_errno(c))); goto fail; } } else { - if ((r = pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : NULL, flags)) < 0) { + if (pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : NULL, flags) < 0) { pa_log(_("pa_stream_connect_record() failed: %s"), pa_strerror(pa_context_errno(c))); goto fail; } @@ -574,9 +577,10 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd return; } - pa_log(_("Time: %0.3f sec; Latency: %0.0f usec. \r"), + fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec."), (float) usec / 1000000, (float) l * (negative?-1.0f:1.0f)); + fprintf(stderr, " \r"); } /* Someone requested that the latency is shown */ @@ -768,7 +772,6 @@ int main(int argc, char *argv[]) { case ARG_STREAM_NAME: { char *t; - t = pa_locale_to_utf8(optarg); if (!(t = pa_locale_to_utf8(optarg)) || pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t) < 0) { @@ -903,7 +906,7 @@ int main(int argc, char *argv[]) { filename = argv[optind]; - if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { + if ((fd = pa_open_cloexec(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { pa_log(_("open(): %s"), strerror(errno)); goto quit; } diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index ac60a0bc..ef58e9ce 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -25,7 +25,7 @@ #include <assert.h> #include <signal.h> -#include <sys/select.h> +#include <sys/poll.h> #include <sys/socket.h> #include <unistd.h> #include <errno.h> @@ -45,6 +45,13 @@ int main(int argc, char*argv[]) { + enum { + WATCH_STDIN, + WATCH_STDOUT, + WATCH_SOCKET, + N_WATCH + }; + pid_t pid ; int fd = -1; int ret = 1, i; @@ -53,6 +60,7 @@ int main(int argc, char*argv[]) { size_t ibuf_index, ibuf_length, obuf_index, obuf_length; char *cli; pa_bool_t ibuf_eof, obuf_eof, ibuf_closed, obuf_closed; + struct pollfd pollfd[N_WATCH]; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); @@ -62,7 +70,7 @@ int main(int argc, char*argv[]) { goto fail; } - if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) { pa_log(_("socket(PF_UNIX, SOCK_STREAM, 0): %s"), strerror(errno)); goto fail; } @@ -108,7 +116,7 @@ int main(int argc, char*argv[]) { size_t k; k = PA_MIN(sizeof(ibuf) - ibuf_length, strlen(argv[i])); - memcpy(ibuf + ibuf_length, argv[1], k); + memcpy(ibuf + ibuf_length, argv[i], k); ibuf_length += k; if (ibuf_length < sizeof(ibuf)) { @@ -120,38 +128,45 @@ int main(int argc, char*argv[]) { ibuf_eof = TRUE; } - for (;;) { - fd_set ifds, ofds; + pa_zero(pollfd); + pollfd[WATCH_STDIN].fd = STDIN_FILENO; + pollfd[WATCH_STDOUT].fd = STDOUT_FILENO; + pollfd[WATCH_SOCKET].fd = fd; + + for (;;) { if (ibuf_eof && obuf_eof && ibuf_length <= 0 && obuf_length <= 0) break; - FD_ZERO(&ifds); - FD_ZERO(&ofds); + pollfd[WATCH_STDIN].events = pollfd[WATCH_STDOUT].events = pollfd[WATCH_SOCKET].events = 0; if (obuf_length > 0) - FD_SET(1, &ofds); + pollfd[WATCH_STDOUT].events |= POLLOUT; else if (!obuf_eof) - FD_SET(fd, &ifds); + pollfd[WATCH_SOCKET].events |= POLLIN; if (ibuf_length > 0) - FD_SET(fd, &ofds); + pollfd[WATCH_SOCKET].events |= POLLOUT; else if (!ibuf_eof) - FD_SET(0, &ifds); + pollfd[WATCH_STDIN].events |= POLLIN; - if (select(FD_SETSIZE, &ifds, &ofds, NULL, NULL) < 0) { - pa_log(_("select(): %s"), strerror(errno)); + if (poll(pollfd, N_WATCH, -1) < 0) { + + if (errno == EINTR) + continue; + + pa_log(_("poll(): %s"), strerror(errno)); goto fail; } - if (FD_ISSET(0, &ifds)) { + if (pollfd[WATCH_STDIN].revents & POLLIN) { ssize_t r; pa_assert(!ibuf_length); - if ((r = pa_read(0, ibuf, sizeof(ibuf), NULL)) <= 0) { + if ((r = pa_read(STDIN_FILENO, ibuf, sizeof(ibuf), NULL)) <= 0) { if (r < 0) { pa_log(_("read(): %s"), strerror(errno)); goto fail; @@ -164,7 +179,7 @@ int main(int argc, char*argv[]) { } } - if (FD_ISSET(fd, &ifds)) { + if (pollfd[WATCH_SOCKET].revents & POLLIN) { ssize_t r; pa_assert(!obuf_length); @@ -181,21 +196,26 @@ int main(int argc, char*argv[]) { } } - if (FD_ISSET(1, &ofds)) { + if (pollfd[WATCH_STDOUT].revents & POLLHUP) { + obuf_eof = TRUE; + obuf_length = 0; + } else if (pollfd[WATCH_STDOUT].revents & POLLOUT) { ssize_t r; pa_assert(obuf_length); - if ((r = pa_write(1, obuf + obuf_index, obuf_length, NULL)) < 0) { + if ((r = pa_write(STDOUT_FILENO, obuf + obuf_index, obuf_length, NULL)) < 0) { pa_log(_("write(): %s"), strerror(errno)); goto fail; } obuf_length -= (size_t) r; obuf_index += obuf_index; - } - if (FD_ISSET(fd, &ofds)) { + if (pollfd[WATCH_SOCKET].revents & POLLHUP) { + ibuf_eof = TRUE; + ibuf_length = 0; + } if (pollfd[WATCH_SOCKET].revents & POLLOUT) { ssize_t r; pa_assert(ibuf_length); @@ -209,14 +229,14 @@ int main(int argc, char*argv[]) { } if (ibuf_length <= 0 && ibuf_eof && !ibuf_closed) { - pa_close(0); + pa_close(STDIN_FILENO); shutdown(fd, SHUT_WR); ibuf_closed = TRUE; } if (obuf_length <= 0 && obuf_eof && !obuf_closed) { shutdown(fd, SHUT_RD); - pa_close(1); + pa_close(STDOUT_FILENO); obuf_closed = TRUE; } } diff --git a/src/utils/pactl.c b/src/utils/pactl.c index c8c3a437..ee67c425 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -44,13 +44,10 @@ #include <pulsecore/log.h> #include <pulsecore/sndfile-util.h> -#define BUFSIZE (16*1024) - static pa_context *context = NULL; static pa_mainloop_api *mainloop_api = NULL; static char - *device = NULL, *sample_name = NULL, *sink_name = NULL, *source_name = NULL, @@ -66,6 +63,8 @@ static uint32_t static uint32_t module_index; static pa_bool_t suspend; +static pa_bool_t mute; +static pa_volume_t volume; static pa_proplist *proplist = NULL; @@ -74,7 +73,6 @@ static pa_stream *sample_stream = NULL; static pa_sample_spec sample_spec; static pa_channel_map channel_map; static size_t sample_length = 0; - static int actions = 1; static pa_bool_t nl = FALSE; @@ -95,7 +93,13 @@ static enum { SUSPEND_SOURCE, SET_CARD_PROFILE, SET_SINK_PORT, - SET_SOURCE_PORT + SET_SOURCE_PORT, + SET_SINK_VOLUME, + SET_SOURCE_VOLUME, + SET_SINK_INPUT_VOLUME, + SET_SINK_MUTE, + SET_SOURCE_MUTE, + SET_SINK_INPUT_MUTE } action = NONE; static void quit(int ret) { @@ -109,6 +113,7 @@ static void context_drain_complete(pa_context *c, void *userdata) { static void drain(void) { pa_operation *o; + if (!(o = pa_context_drain(context, context_drain_complete, NULL))) pa_context_disconnect(context); else @@ -123,9 +128,9 @@ static void complete_action(void) { } static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) { - char s[128]; + char s[PA_BYTES_SNPRINT_MAX]; if (!i) { - pa_log(_("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get statistics: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -146,15 +151,28 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; if (!i) { - pa_log(_("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get server information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } + printf(_("Server String: %s\n" + "Library Protocol Version: %u\n" + "Server Protocol Version: %u\n" + "Is Local: %s\n" + "Client Index: %u\n" + "Tile Size: %zu\n"), + pa_context_get_server(c), + pa_context_get_protocol_version(c), + pa_context_get_server_protocol_version(c), + pa_yes_no(pa_context_is_local(c)), + pa_context_get_index(c), + pa_context_get_tile_size(c, NULL)); + pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec); pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); - printf(_("User name: %s\n" + printf(_("User Name: %s\n" "Host Name: %s\n" "Server Name: %s\n" "Server Version: %s\n" @@ -162,7 +180,7 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi "Default Channel Map: %s\n" "Default Sink: %s\n" "Default Source: %s\n" - "Cookie: %08x\n"), + "Cookie: %04x:%04x\n"), i->user_name, i->host_name, i->server_name, @@ -171,7 +189,8 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi cm, i->default_sink_name, i->default_source_name, - i->cookie); + i->cookie >> 16, + i->cookie & 0xFFFFU); complete_action(); } @@ -195,7 +214,7 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ char *pl; if (is_last < 0) { - pa_log(_("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sink information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -287,7 +306,7 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int char *pl; if (is_last < 0) { - pa_log(_("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get source information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -365,7 +384,7 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int char *pl; if (is_last < 0) { - pa_log(_("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get module information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -402,7 +421,7 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int char *pl; if (is_last < 0) { - pa_log(_("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get client information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -437,7 +456,7 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ char *pl; if (is_last < 0) { - pa_log(_("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get card information: %s"), pa_strerror(pa_context_errno(c))); complete_action(); return; } @@ -486,7 +505,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info char *pl; if (is_last < 0) { - pa_log(_("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sink input information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -544,7 +563,7 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu char *pl; if (is_last < 0) { - pa_log(_("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get source output information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -591,11 +610,11 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu } static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int is_last, void *userdata) { - char t[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + char t[PA_BYTES_SNPRINT_MAX], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; char *pl; if (is_last < 0) { - pa_log(_("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sample information: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -643,7 +662,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int static void simple_callback(pa_context *c, int success, void *userdata) { if (!success) { - pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failure: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -653,7 +672,7 @@ static void simple_callback(pa_context *c, int success, void *userdata) { static void index_callback(pa_context *c, uint32_t idx, void *userdata) { if (idx == PA_INVALID_INDEX) { - pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failure: %s"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -677,7 +696,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { case PA_STREAM_FAILED: default: - pa_log(_("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("Failed to upload sample: %s"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); quit(1); } } @@ -694,7 +713,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { if ((sf_readf_float(sndfile, d, l)) != l) { pa_xfree(d); - pa_log(_("Premature end of file\n")); + pa_log(_("Premature end of file")); quit(1); return; } @@ -726,7 +745,7 @@ static void context_state_callback(pa_context *c, void *userdata) { break; case PLAY_SAMPLE: - pa_operation_unref(pa_context_play_sample(c, sample_name, device, PA_VOLUME_NORM, simple_callback, NULL)); + pa_operation_unref(pa_context_play_sample(c, sample_name, sink_name, PA_VOLUME_NORM, simple_callback, NULL)); break; case REMOVE_SAMPLE: @@ -800,6 +819,42 @@ static void context_state_callback(pa_context *c, void *userdata) { pa_operation_unref(pa_context_set_source_port_by_name(c, source_name, port_name, simple_callback, NULL)); break; + case SET_SINK_MUTE: + pa_operation_unref(pa_context_set_sink_mute_by_name(c, sink_name, mute, simple_callback, NULL)); + break; + + case SET_SOURCE_MUTE: + pa_operation_unref(pa_context_set_source_mute_by_name(c, source_name, mute, simple_callback, NULL)); + break; + + case SET_SINK_INPUT_MUTE: + pa_operation_unref(pa_context_set_sink_input_mute(c, sink_input_idx, mute, simple_callback, NULL)); + break; + + case SET_SINK_VOLUME: { + pa_cvolume v; + + pa_cvolume_set(&v, 1, volume); + pa_operation_unref(pa_context_set_sink_volume_by_name(c, sink_name, &v, simple_callback, NULL)); + break; + } + + case SET_SOURCE_VOLUME: { + pa_cvolume v; + + pa_cvolume_set(&v, 1, volume); + pa_operation_unref(pa_context_set_source_volume_by_name(c, source_name, &v, simple_callback, NULL)); + break; + } + + case SET_SINK_INPUT_VOLUME: { + pa_cvolume v; + + pa_cvolume_set(&v, 1, volume); + pa_operation_unref(pa_context_set_sink_input_volume(c, sink_input_idx, &v, simple_callback, NULL)); + break; + } + default: pa_assert_not_reached(); } @@ -811,13 +866,13 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: - pa_log(_("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Connection failure: %s"), pa_strerror(pa_context_errno(c))); quit(1); } } static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { - pa_log(_("Got SIGINT, exiting.\n")); + pa_log(_("Got SIGINT, exiting.")); quit(0); } @@ -829,20 +884,30 @@ static void help(const char *argv0) { "%s [options] upload-sample FILENAME [NAME]\n" "%s [options] play-sample NAME [SINK]\n" "%s [options] remove-sample NAME\n" - "%s [options] move-sink-input ID SINK\n" - "%s [options] move-source-output ID SOURCE\n" + "%s [options] move-sink-input SINKINPUT SINK\n" + "%s [options] move-source-output SOURCEOUTPUT SOURCE\n" "%s [options] load-module NAME [ARGS ...]\n" - "%s [options] unload-module ID\n" - "%s [options] suspend-sink [SINK] 1|0\n" - "%s [options] suspend-source [SOURCE] 1|0\n" - "%s [options] set-card-profile [CARD] [PROFILE] \n" - "%s [options] set-sink-port [SINK] [PORT] \n" - "%s [options] set-source-port [SOURCE] [PORT] \n\n" + "%s [options] unload-module MODULE\n" + "%s [options] suspend-sink SINK 1|0\n" + "%s [options] suspend-source SOURCE 1|0\n" + "%s [options] set-card-profile CARD PROFILE\n" + "%s [options] set-sink-port SINK PORT\n" + "%s [options] set-source-port SOURCE PORT\n" + "%s [options] set-sink-volume SINK VOLUME\n" + "%s [options] set-source-volume SOURCE VOLUME\n" + "%s [options] set-sink-input-volume SINKINPUT VOLUME\n" + "%s [options] set-sink-mute SINK 1|0\n" + "%s [options] set-source-mute SOURCE 1|0\n" + "%s [options] set-sink-input-mute SINKINPUT 1|0\n\n" " -h, --help Show this help\n" " --version Show version\n\n" " -s, --server=SERVER The name of the server to connect to\n" " -n, --client-name=NAME How to call this client on the server\n"), - argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0); + argv0, argv0, argv0, argv0, argv0, + argv0, argv0, argv0, argv0, argv0, + argv0, argv0, argv0, argv0, argv0, + argv0, argv0, argv0, argv0, argv0, + argv0); } enum { @@ -897,7 +962,7 @@ int main(int argc, char *argv[]) { if (!(t = pa_locale_to_utf8(optarg)) || pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t) < 0) { - pa_log(_("Invalid client name '%s'\n"), t ? t : optarg); + pa_log(_("Invalid client name '%s'"), t ? t : optarg); pa_xfree(t); goto quit; } @@ -923,7 +988,7 @@ int main(int argc, char *argv[]) { action = UPLOAD_SAMPLE; if (optind+1 >= argc) { - pa_log(_("Please specify a sample file to load\n")); + pa_log(_("Please specify a sample file to load")); goto quit; } @@ -936,19 +1001,19 @@ int main(int argc, char *argv[]) { pa_zero(sfi); if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfi))) { - pa_log(_("Failed to open sound file.\n")); + pa_log(_("Failed to open sound file.")); goto quit; } if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { - pa_log(_("Failed to determine sample specification from file.\n")); + pa_log(_("Failed to determine sample specification from file.")); goto quit; } sample_spec.format = PA_SAMPLE_FLOAT32; if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { if (sample_spec.channels > 2) - pa_log(_("Warning: Failed to determine sample specification from file.\n")); + pa_log(_("Warning: Failed to determine sample specification from file.")); pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); } @@ -958,19 +1023,19 @@ int main(int argc, char *argv[]) { } else if (pa_streq(argv[optind], "play-sample")) { action = PLAY_SAMPLE; if (argc != optind+2 && argc != optind+3) { - pa_log(_("You have to specify a sample name to play\n")); + pa_log(_("You have to specify a sample name to play")); goto quit; } sample_name = pa_xstrdup(argv[optind+1]); if (optind+2 < argc) - device = pa_xstrdup(argv[optind+2]); + sink_name = pa_xstrdup(argv[optind+2]); } else if (pa_streq(argv[optind], "remove-sample")) { action = REMOVE_SAMPLE; if (argc != optind+2) { - pa_log(_("You have to specify a sample name to remove\n")); + pa_log(_("You have to specify a sample name to remove")); goto quit; } @@ -979,7 +1044,7 @@ int main(int argc, char *argv[]) { } else if (pa_streq(argv[optind], "move-sink-input")) { action = MOVE_SINK_INPUT; if (argc != optind+3) { - pa_log(_("You have to specify a sink input index and a sink\n")); + pa_log(_("You have to specify a sink input index and a sink")); goto quit; } @@ -989,7 +1054,7 @@ int main(int argc, char *argv[]) { } else if (pa_streq(argv[optind], "move-source-output")) { action = MOVE_SOURCE_OUTPUT; if (argc != optind+3) { - pa_log(_("You have to specify a source output index and a source\n")); + pa_log(_("You have to specify a source output index and a source")); goto quit; } @@ -1004,7 +1069,7 @@ int main(int argc, char *argv[]) { action = LOAD_MODULE; if (argc <= optind+1) { - pa_log(_("You have to specify a module name and arguments.\n")); + pa_log(_("You have to specify a module name and arguments.")); goto quit; } @@ -1024,7 +1089,7 @@ int main(int argc, char *argv[]) { action = UNLOAD_MODULE; if (argc != optind+2) { - pa_log(_("You have to specify a module index\n")); + pa_log(_("You have to specify a module index")); goto quit; } @@ -1034,7 +1099,7 @@ int main(int argc, char *argv[]) { action = SUSPEND_SINK; if (argc > optind+3 || optind+1 >= argc) { - pa_log(_("You may not specify more than one sink. You have to specify a boolean value.\n")); + pa_log(_("You may not specify more than one sink. You have to specify a boolean value.")); goto quit; } @@ -1047,7 +1112,7 @@ int main(int argc, char *argv[]) { action = SUSPEND_SOURCE; if (argc > optind+3 || optind+1 >= argc) { - pa_log(_("You may not specify more than one source. You have to specify a boolean value.\n")); + pa_log(_("You may not specify more than one source. You have to specify a boolean value.")); goto quit; } @@ -1059,7 +1124,7 @@ int main(int argc, char *argv[]) { action = SET_CARD_PROFILE; if (argc != optind+3) { - pa_log(_("You have to specify a card name/index and a profile name\n")); + pa_log(_("You have to specify a card name/index and a profile name")); goto quit; } @@ -1070,7 +1135,7 @@ int main(int argc, char *argv[]) { action = SET_SINK_PORT; if (argc != optind+3) { - pa_log(_("You have to specify a sink name/index and a port name\n")); + pa_log(_("You have to specify a sink name/index and a port name")); goto quit; } @@ -1081,13 +1146,123 @@ int main(int argc, char *argv[]) { action = SET_SOURCE_PORT; if (argc != optind+3) { - pa_log(_("You have to specify a source name/index and a port name\n")); + pa_log(_("You have to specify a source name/index and a port name")); goto quit; } source_name = pa_xstrdup(argv[optind+1]); port_name = pa_xstrdup(argv[optind+2]); + } else if (pa_streq(argv[optind], "set-sink-volume")) { + uint32_t v; + action = SET_SINK_VOLUME; + + if (argc != optind+3) { + pa_log(_("You have to specify a sink name/index and a volume")); + goto quit; + } + + if (pa_atou(argv[optind+2], &v) < 0) { + pa_log(_("Invalid volume specification")); + goto quit; + } + + sink_name = pa_xstrdup(argv[optind+1]); + volume = (pa_volume_t) v; + + } else if (pa_streq(argv[optind], "set-source-volume")) { + uint32_t v; + action = SET_SOURCE_VOLUME; + + if (argc != optind+3) { + pa_log(_("You have to specify a source name/index and a volume")); + goto quit; + } + + if (pa_atou(argv[optind+2], &v) < 0) { + pa_log(_("Invalid volume specification")); + goto quit; + } + + source_name = pa_xstrdup(argv[optind+1]); + volume = (pa_volume_t) v; + + } else if (pa_streq(argv[optind], "set-sink-input-volume")) { + uint32_t v; + action = SET_SINK_INPUT_VOLUME; + + if (argc != optind+3) { + pa_log(_("You have to specify a sink input index and a volume")); + goto quit; + } + + if (pa_atou(argv[optind+1], &sink_input_idx) < 0) { + pa_log(_("Invalid sink input index")); + goto quit; + } + + if (pa_atou(argv[optind+2], &v) < 0) { + pa_log(_("Invalid volume specification")); + goto quit; + } + + volume = (pa_volume_t) v; + + } else if (pa_streq(argv[optind], "set-sink-mute")) { + int b; + action = SET_SINK_MUTE; + + if (argc != optind+3) { + pa_log(_("You have to specify a sink name/index and a mute boolean")); + goto quit; + } + + if ((b = pa_parse_boolean(argv[optind+2])) < 0) { + pa_log(_("Invalid volume specification")); + goto quit; + } + + sink_name = pa_xstrdup(argv[optind+1]); + mute = b; + + } else if (pa_streq(argv[optind], "set-source-mute")) { + int b; + action = SET_SOURCE_MUTE; + + if (argc != optind+3) { + pa_log(_("You have to specify a source name/index and a mute boolean")); + goto quit; + } + + if ((b = pa_parse_boolean(argv[optind+2])) < 0) { + pa_log(_("Invalid volume specification")); + goto quit; + } + + source_name = pa_xstrdup(argv[optind+1]); + mute = b; + + } else if (pa_streq(argv[optind], "set-sink-input-mute")) { + int b; + action = SET_SINK_INPUT_MUTE; + + if (argc != optind+3) { + pa_log(_("You have to specify a sink input index and a mute boolean")); + goto quit; + } + + if (pa_atou(argv[optind+1], &sink_input_idx) < 0) { + pa_log(_("Invalid sink input index specification")); + goto quit; + } + + if ((b = pa_parse_boolean(argv[optind+2])) < 0) { + pa_log(_("Invalid volume specification")); + goto quit; + } + + mute = b; + } else if (pa_streq(argv[optind], "help")) { help(bn); ret = 0; @@ -1096,12 +1271,12 @@ int main(int argc, char *argv[]) { } if (action == NONE) { - pa_log(_("No valid command specified.\n")); + pa_log(_("No valid command specified.")); goto quit; } if (!(m = pa_mainloop_new())) { - pa_log(_("pa_mainloop_new() failed.\n")); + pa_log(_("pa_mainloop_new() failed.")); goto quit; } @@ -1113,7 +1288,7 @@ int main(int argc, char *argv[]) { pa_disable_sigpipe(); if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { - pa_log(_("pa_context_new() failed.\n")); + pa_log(_("pa_context_new() failed.")); goto quit; } @@ -1124,7 +1299,7 @@ int main(int argc, char *argv[]) { } if (pa_mainloop_run(m, &ret) < 0) { - pa_log(_("pa_mainloop_run() failed.\n")); + pa_log(_("pa_mainloop_run() failed.")); goto quit; } @@ -1141,7 +1316,6 @@ quit: } pa_xfree(server); - pa_xfree(device); pa_xfree(sample_name); pa_xfree(sink_name); pa_xfree(source_name); diff --git a/src/utils/padsp.c b/src/utils/padsp.c index 882522c4..2ed0a039 100644 --- a/src/utils/padsp.c +++ b/src/utils/padsp.c @@ -1394,11 +1394,13 @@ static int sndstat_open(int flags, int *_errno) { "Mixers:\n" "0: PulseAudio Virtual OSS\n"; - char fn[] = "/tmp/padsp-sndstat-XXXXXX"; + char *fn; mode_t u; int fd = -1; int e; + fn = pa_sprintf_malloc("%s" PA_PATH_SEP "padsp-sndstat-XXXXXX", pa_get_temp_dir()); + debug(DEBUG_LEVEL_NORMAL, __FILE__": sndstat_open()\n"); if (flags != O_RDONLY @@ -1423,6 +1425,7 @@ static int sndstat_open(int flags, int *_errno) { } unlink(fn); + pa_xfree(fn); if (write(fd, sndstat, sizeof(sndstat) -1) != sizeof(sndstat)-1) { *_errno = errno; @@ -1439,6 +1442,7 @@ static int sndstat_open(int flags, int *_errno) { return fd; fail: + pa_xfree(fn); if (fd >= 0) close(fd); return -1; @@ -1821,7 +1825,7 @@ fail: pa_threaded_mainloop_unlock(i->mainloop); - return 0; + return r; } static int dsp_trigger(fd_info *i) { @@ -1864,7 +1868,7 @@ fail: pa_threaded_mainloop_unlock(i->mainloop); - return 0; + return r; } static int dsp_cork(fd_info *i, pa_stream *s, int b) { @@ -1902,7 +1906,7 @@ fail: pa_threaded_mainloop_unlock(i->mainloop); - return 0; + return r; } static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) { diff --git a/src/utils/pasuspender.c b/src/utils/pasuspender.c index c327ee41..534b77b4 100644 --- a/src/utils/pasuspender.c +++ b/src/utils/pasuspender.c @@ -45,8 +45,6 @@ #include <pulse/pulseaudio.h> #include <pulsecore/macro.h> -#define BUFSIZE 1024 - static pa_context *context = NULL; static pa_mainloop_api *mainloop_api = NULL; static char **child_argv = NULL; |