diff options
Diffstat (limited to 'src')
284 files changed, 29676 insertions, 6112 deletions
diff --git a/src/.gitignore b/src/.gitignore index 85370444..82331524 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,4 @@ +sigbus-test  TAGS  alsa-time-test  gtk-test diff --git a/src/Makefile.am b/src/Makefile.am index a99e2756..7ebf1f8a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,6 +29,9 @@ pulsecoreincludedir=$(includedir)/pulsecore  pulseconfdir=$(sysconfdir)/pulse  pulselibexecdir=$(libexecdir)/pulse  xdgautostartdir=$(sysconfdir)/xdg/autostart +alsaprofilesetsdir=$(datadir)/pulseaudio/alsa-mixer/profile-sets +alsapathsdir=$(datadir)/pulseaudio/alsa-mixer/paths +udevrulesdir=/lib/udev/rules.d  ###################################  #            Defines              # @@ -56,9 +59,16 @@ AM_CFLAGS = \  	-I$(top_builddir)/src/modules/gconf \  	-I$(top_srcdir)/src/modules/bluetooth \  	-I$(top_builddir)/src/modules/bluetooth \ +	-I$(top_srcdir)/src/modules/oss \ +	-I$(top_builddir)/src/modules/oss \  	-I$(top_srcdir)/src/modules/alsa \  	-I$(top_builddir)/src/modules/alsa \  	-I$(top_srcdir)/src/modules/raop \ +	-I$(top_builddir)/src/modules/raop \ +	-I$(top_srcdir)/src/modules/x11 \ +	-I$(top_builddir)/src/modules/x11 \ +	-I$(top_srcdir)/src/modules/jack \ +	-I$(top_builddir)/src/modules/jack \  	$(PTHREAD_CFLAGS) -D_POSIX_PTHREAD_SEMANTICS \  	$(LIBSAMPLERATE_CFLAGS) \  	$(LIBSNDFILE_CFLAGS) \ @@ -71,7 +81,9 @@ AM_CFLAGS = \  	-DPA_SYSTEM_STATE_PATH=\"$(PA_SYSTEM_STATE_PATH)\" \  	-DAO_REQUIRE_CAS \  	-DPULSE_LOCALEDIR=\"$(pulselocaledir)\" \ -	-DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" +	-DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" \ +	-DPA_ALSA_PATHS_DIR=\"$(alsapathsdir)\" \ +	-DPA_ALSA_PROFILE_SETS_DIR=\"$(alsaprofilesetsdir)\"  AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS)  AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) @@ -87,7 +99,7 @@ WINSOCK_LIBS=-lwsock32 -lws2_32 -lwininet  endif  FOREIGN_CFLAGS = -w -MODULE_LDFLAGS = -module -disable-static -avoid-version +MODULE_LDFLAGS = -module -disable-static -avoid-version $(LDFLAGS_NOUNDEFINED)  ###################################  #          Extra files            # @@ -107,7 +119,25 @@ EXTRA_DIST = \  		modules/module-defs.h.m4 \  		daemon/pulseaudio.desktop.in \  		map-file \ -		daemon/org.pulseaudio.policy.in +		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 \ +		modules/alsa/mixer/profile-sets/90-pulseaudio.rules \ +		modules/alsa/mixer/paths/analog-input-aux.conf \ +		modules/alsa/mixer/paths/analog-input.conf \ +		modules/alsa/mixer/paths/analog-input.conf.common \ +		modules/alsa/mixer/paths/analog-input-fm.conf \ +		modules/alsa/mixer/paths/analog-input-linein.conf \ +		modules/alsa/mixer/paths/analog-input-mic.conf \ +		modules/alsa/mixer/paths/analog-input-mic.conf.common \ +		modules/alsa/mixer/paths/analog-input-mic-line.conf \ +		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.conf.common \ +		modules/alsa/mixer/paths/analog-output-headphones.conf \ +		modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ +		modules/alsa/mixer/paths/analog-output-mono.conf  pulseconf_DATA = \  		default.pa \ @@ -158,16 +188,6 @@ else  pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f))  endif -if HAVE_POLKIT -policy_in_files = daemon/org.pulseaudio.policy.in -policy_DATA = $(policy_in_files:.policy.in=.policy) -@INTLTOOL_POLICY_RULE@ - -pulseaudio_SOURCES += daemon/polkit.c daemon/polkit.h -pulseaudio_CFLAGS += $(POLKIT_CFLAGS) -pulseaudio_LDADD += $(POLKIT_LIBS) -endif -  ###################################  #       Utility programs          #  ################################### @@ -175,7 +195,6 @@ endif  bin_PROGRAMS += \  		pacat \  		pactl \ -		paplay \  		pasuspender  if HAVE_AF_UNIX @@ -193,23 +212,18 @@ endif  bin_SCRIPTS = esdcompat start-pulseaudio-x11  pacat_SOURCES = utils/pacat.c -pacat_LDADD = $(AM_LDADD) libpulse.la -pacat_CFLAGS = $(AM_CFLAGS) +pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) +pacat_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)  pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -paplay_SOURCES = utils/paplay.c -paplay_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) -paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) -paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -  pactl_SOURCES = utils/pactl.c  pactl_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS)  pactl_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)  pactl_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)  pasuspender_SOURCES = utils/pasuspender.c -pasuspender_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) -pasuspender_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) +pasuspender_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la +pasuspender_CFLAGS = $(AM_CFLAGS)  pasuspender_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)  pacmd_SOURCES = utils/pacmd.c @@ -223,7 +237,7 @@ pax11publish_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@  pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)  pabrowse_SOURCES = utils/pabrowse.c -pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la +pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la libpulsecommon-@PA_MAJORMINORMICRO@.la  pabrowse_CFLAGS = $(AM_CFLAGS)  pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) @@ -259,7 +273,8 @@ TESTS = \  		envelope-test \  		proplist-test \  		lock-autospawn-test \ -		prioq-test +		prioq-test \ +		sigbus-test  TESTS_BINARIES = \  		mainloop-test \ @@ -296,7 +311,8 @@ TESTS_BINARIES = \  		rtstutter \  		stripnul \  		lock-autospawn-test \ -		prioq-test +		prioq-test \ +		sigbus-test  if HAVE_SIGXCPU  #TESTS += \ @@ -520,6 +536,11 @@ prioq_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecomm  prioq_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)  prioq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +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) +  gtk_test_SOURCES = tests/gtk-test.c  gtk_test_LDADD = $(AM_LDADD) libpulse.la libpulse-mainloop-glib.la  gtk_test_CFLAGS = $(AM_CFLAGS) $(GTK20_CFLAGS) @@ -545,6 +566,7 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES = \  		pulsecore/authkey.c pulsecore/authkey.h \  		pulsecore/conf-parser.c pulsecore/conf-parser.h \  		pulsecore/core-error.c pulsecore/core-error.h \ +		pulsecore/core-rtclock.c pulsecore/core-rtclock.h \  		pulsecore/core-util.c pulsecore/core-util.h \  		pulsecore/creds.h \  		pulsecore/dynarray.c pulsecore/dynarray.h \ @@ -575,13 +597,14 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES = \  		pulsecore/pipe.c pulsecore/pipe.h \  		pulsecore/poll.c pulsecore/poll.h \  		pulsecore/prioq.c pulsecore/prioq.h \ +		pulsecore/memtrap.c pulsecore/memtrap.h \ +		pulsecore/aupdate.c pulsecore/aupdate.h \  		pulsecore/proplist-util.c pulsecore/proplist-util.h \  		pulsecore/pstream-util.c pulsecore/pstream-util.h \  		pulsecore/pstream.c pulsecore/pstream.h \  		pulsecore/queue.c pulsecore/queue.h \  		pulsecore/random.c pulsecore/random.h \  		pulsecore/refcnt.h \ -		pulsecore/rtclock.c pulsecore/rtclock.h \  		pulsecore/shm.c pulsecore/shm.h \  		pulsecore/bitset.c pulsecore/bitset.h \  		pulsecore/socket-client.c pulsecore/socket-client.h \ @@ -592,11 +615,12 @@ 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/sndfile-util.c pulsecore/sndfile-util.h \  		pulsecore/winsock.h  libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS)  libpulsecommon_@PA_MAJORMINORMICRO@_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version -libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) +libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)  # proplist-util.h uses these header files, but not the library itself!  libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS += $(GLIB20_CFLAGS) $(GTK20_CFLAGS) @@ -633,6 +657,14 @@ if OS_IS_WIN32  libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/dllmain.c  endif +if HAVE_DBUS +libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES += \ +		pulsecore/dbus-util.c pulsecore/dbus-util.h \ +		pulsecore/rtkit.c pulsecore/rtkit.h +libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS += $(DBUS_CFLAGS) +libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD += $(DBUS_LIBS) +endif +  ###################################  #         Client library          #  ################################### @@ -652,6 +684,7 @@ pulseinclude_HEADERS = \  		pulse/operation.h \  		pulse/proplist.h \  		pulse/pulseaudio.h \ +		pulse/rtclock.h \  		pulse/sample.h \  		pulse/scache.h \  		pulse/simple.h \ @@ -702,6 +735,7 @@ libpulse_la_SOURCES = \  		pulse/operation.c pulse/operation.h \  		pulse/proplist.c pulse/proplist.h \  		pulse/pulseaudio.h \ +		pulse/rtclock.c pulse/rtclock.h \  		pulse/sample.c pulse/sample.h \  		pulse/scache.c pulse/scache.h \  		pulse/stream.c pulse/stream.h \ @@ -713,7 +747,7 @@ libpulse_la_SOURCES = \  		pulse/volume.c pulse/volume.h \  		pulse/xmalloc.c pulse/xmalloc.h -libpulse_la_CFLAGS = $(AM_CFLAGS) +libpulse_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)  libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la  libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO) @@ -785,7 +819,6 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \  		pulsecore/play-memchunk.c pulsecore/play-memchunk.h \  		pulsecore/resampler.c pulsecore/resampler.h \  		pulsecore/rtpoll.c pulsecore/rtpoll.h \ -		pulsecore/rtsig.c pulsecore/rtsig.h \  		pulsecore/sample-util.c pulsecore/sample-util.h \  		pulsecore/sconv-s16be.c pulsecore/sconv-s16be.h \  		pulsecore/sconv-s16le.c pulsecore/sconv-s16le.h \ @@ -801,11 +834,12 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \  		pulsecore/source.c pulsecore/source.h \  		pulsecore/start-child.c pulsecore/start-child.h \  		pulsecore/thread-mq.c pulsecore/thread-mq.h \ -		pulsecore/time-smoother.c pulsecore/time-smoother.h +		pulsecore/time-smoother.c pulsecore/time-smoother.h \ +		pulsecore/database.h -libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS) $(LIBOIL_CFLAGS)  libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS = -avoid-version -libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_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) $(LIBOIL_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 @@ -813,6 +847,25 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(X11_CFLAGS)  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_CFLAGS += $(DBUS_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(DBUS_LIBS) +endif + +if HAVE_GDBM +libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-gdbm.c +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(GDBM_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(GDBM_LIBS) +endif + +if HAVE_TDB +libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-tdb.c +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(TDB_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(TDB_LIBS) +endif + +  # We split the foreign code off to not be annoyed by warnings we don't care about  noinst_LTLIBRARIES = libpulsecore-foreign.la @@ -861,7 +914,7 @@ libprotocol_cli_la_SOURCES = pulsecore/protocol-cli.c pulsecore/protocol-cli.h  libprotocol_cli_la_LDFLAGS = -avoid-version  libprotocol_cli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libcli.la -libprotocol_http_la_SOURCES = pulsecore/protocol-http.c pulsecore/protocol-http.h +libprotocol_http_la_SOURCES = pulsecore/protocol-http.c pulsecore/protocol-http.h pulsecore/mime-type.c pulsecore/mime-type.h  libprotocol_http_la_LDFLAGS = -avoid-version  libprotocol_http_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -902,7 +955,6 @@ libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore-@PA_MAJORMIN  if HAVE_DBUS  # Serveral module (e.g. libalsa-util.la)  modlibexec_LTLIBRARIES += \ -		libdbus-util.la \  		module-console-kit.la  endif @@ -920,6 +972,7 @@ modlibexec_LTLIBRARIES += \  		module-default-device-restore.la \  		module-always-sink.la \  		module-rescue-streams.la \ +		module-intended-roles.la \  		module-suspend-on-idle.la \  		module-http-protocol-tcp.la \  		module-sine.la \ @@ -992,6 +1045,34 @@ modlibexec_LTLIBRARIES += \  		module-alsa-sink.la \  		module-alsa-source.la \  		module-alsa-card.la + +alsaprofilesets_DATA = \ +		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 + +if HAVE_UDEV +udevrules_DATA = \ +		modules/alsa/mixer/profile-sets/90-pulseaudio.rules +endif + +alsapaths_DATA = \ +		modules/alsa/mixer/paths/analog-input-aux.conf \ +		modules/alsa/mixer/paths/analog-input.conf \ +		modules/alsa/mixer/paths/analog-input.conf.common \ +		modules/alsa/mixer/paths/analog-input-fm.conf \ +		modules/alsa/mixer/paths/analog-input-linein.conf \ +		modules/alsa/mixer/paths/analog-input-mic.conf \ +		modules/alsa/mixer/paths/analog-input-mic.conf.common \ +		modules/alsa/mixer/paths/analog-input-mic-line.conf \ +		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.conf.common \ +		modules/alsa/mixer/paths/analog-output-headphones.conf \ +		modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ +		modules/alsa/mixer/paths/analog-output-mono.conf +  endif  if HAVE_SOLARIS @@ -1039,6 +1120,16 @@ modlibexec_LTLIBRARIES += \  		module-hal-detect.la  endif +if HAVE_UDEV +modlibexec_LTLIBRARIES += \ +		module-udev-detect.la +endif + +if HAVE_DBUS +modlibexec_LTLIBRARIES += \ +		module-rygel-media-server.la +endif +  if HAVE_BLUEZ  modlibexec_LTLIBRARIES += \  		libbluetooth-util.la \ @@ -1094,10 +1185,11 @@ SYMDEF_FILES = \  		modules/module-mmkbd-evdev-symdef.h \  		modules/module-http-protocol-tcp-symdef.h \  		modules/module-http-protocol-unix-symdef.h \ -		modules/module-x11-bell-symdef.h \ -		modules/module-x11-publish-symdef.h \ -		modules/module-x11-xsmp-symdef.h \ -		modules/module-x11-cork-request-symdef.h \ +		modules/module-rygel-media-server-symdef.h \ +		modules/x11/module-x11-bell-symdef.h \ +		modules/x11/module-x11-publish-symdef.h \ +		modules/x11/module-x11-xsmp-symdef.h \ +		modules/x11/module-x11-cork-request-symdef.h \  		modules/oss/module-oss-symdef.h \  		modules/alsa/module-alsa-sink-symdef.h \  		modules/alsa/module-alsa-source-symdef.h \ @@ -1107,8 +1199,8 @@ SYMDEF_FILES = \  		modules/module-detect-symdef.h \  		modules/rtp/module-rtp-send-symdef.h \  		modules/rtp/module-rtp-recv-symdef.h \ -		modules/module-jack-sink-symdef.h \ -		modules/module-jack-source-symdef.h \ +		modules/jack/module-jack-sink-symdef.h \ +		modules/jack/module-jack-source-symdef.h \  		modules/module-volume-restore-symdef.h \  		modules/module-device-restore-symdef.h \  		modules/module-stream-restore-symdef.h \ @@ -1116,13 +1208,15 @@ SYMDEF_FILES = \  		modules/module-default-device-restore-symdef.h \  		modules/module-always-sink-symdef.h \  		modules/module-rescue-streams-symdef.h \ +		modules/module-intended-roles-symdef.h \  		modules/module-suspend-on-idle-symdef.h \  		modules/module-hal-detect-symdef.h \ +		modules/module-udev-detect-symdef.h \  		modules/bluetooth/module-bluetooth-proximity-symdef.h \  		modules/bluetooth/module-bluetooth-discover-symdef.h \  		modules/bluetooth/module-bluetooth-device-symdef.h \ -		modules/module-raop-sink-symdef.h \ -		modules/module-raop-discover-symdef.h \ +		modules/raop/module-raop-sink-symdef.h \ +		modules/raop/module-raop-discover-symdef.h \  		modules/gconf/module-gconf-symdef.h \  		modules/module-position-event-sounds-symdef.h \  		modules/module-augment-properties-symdef.h \ @@ -1152,7 +1246,7 @@ module_simple_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR  module_cli_la_SOURCES = modules/module-cli.c  module_cli_la_LDFLAGS = $(MODULE_LDFLAGS) -module_cli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libcli.la +module_cli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libcli.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_cli_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c  module_cli_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS) @@ -1271,22 +1365,22 @@ module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.  # X11 -module_x11_bell_la_SOURCES = modules/module-x11-bell.c +module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c  module_x11_bell_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS)  module_x11_bell_la_LDFLAGS = $(MODULE_LDFLAGS)  module_x11_bell_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_x11_publish_la_SOURCES = modules/module-x11-publish.c +module_x11_publish_la_SOURCES = modules/x11/module-x11-publish.c  module_x11_publish_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS)  module_x11_publish_la_LDFLAGS = $(MODULE_LDFLAGS)  module_x11_publish_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_x11_xsmp_la_SOURCES = modules/module-x11-xsmp.c +module_x11_xsmp_la_SOURCES = modules/x11/module-x11-xsmp.c  module_x11_xsmp_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS)  module_x11_xsmp_la_LDFLAGS = $(MODULE_LDFLAGS)  module_x11_xsmp_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_x11_cork_request_la_SOURCES = modules/module-x11-cork-request.c +module_x11_cork_request_la_SOURCES = modules/x11/module-x11-cork-request.c  module_x11_cork_request_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS)  module_x11_cork_request_la_LDFLAGS = $(MODULE_LDFLAGS)  module_x11_cork_request_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -1303,7 +1397,7 @@ module_oss_la_LIBADD = $(AM_LIBADD) liboss-util.la libpulsecore-@PA_MAJORMINORMI  # ALSA -libalsa_util_la_SOURCES = modules/alsa/alsa-util.c modules/alsa/alsa-util.h modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h modules/alsa/alsa-source.c modules/alsa/alsa-source.h +libalsa_util_la_SOURCES = modules/alsa/alsa-util.c modules/alsa/alsa-util.h modules/alsa/alsa-mixer.c modules/alsa/alsa-mixer.h modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h modules/alsa/alsa-source.c modules/alsa/alsa-source.h modules/reserve-wrap.c modules/reserve-wrap.h  libalsa_util_la_LDFLAGS = -avoid-version  libalsa_util_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) @@ -1321,8 +1415,8 @@ libalsa_util_la_CFLAGS += $(UDEV_CFLAGS)  endif  if HAVE_DBUS -libalsa_util_la_SOURCES += modules/reserve.h modules/reserve.c modules/reserve-wrap.c modules/reserve-wrap.h -libalsa_util_la_LIBADD += $(DBUS_LIBS) libdbus-util.la +libalsa_util_la_SOURCES += modules/reserve.h modules/reserve.c modules/reserve-monitor.h modules/reserve-monitor.c +libalsa_util_la_LIBADD += $(DBUS_LIBS)  libalsa_util_la_CFLAGS += $(DBUS_CFLAGS)  endif @@ -1351,7 +1445,7 @@ module_solaris_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la lib  module_zeroconf_publish_la_SOURCES = modules/module-zeroconf-publish.c  module_zeroconf_publish_la_LDFLAGS = $(MODULE_LDFLAGS) -module_zeroconf_publish_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_zeroconf_publish_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_zeroconf_publish_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS)  module_zeroconf_discover_la_SOURCES = modules/module-zeroconf-discover.c @@ -1414,19 +1508,19 @@ module_cork_music_on_phone_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) -module_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la -lgdbm libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_device_restore_la_CFLAGS = $(AM_CFLAGS)  # Stream volume/muted/device restore module  module_stream_restore_la_SOURCES = modules/module-stream-restore.c  module_stream_restore_la_LDFLAGS = $(MODULE_LDFLAGS) -module_stream_restore_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la -lgdbm libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +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)  # Card profile restore module  module_card_restore_la_SOURCES = modules/module-card-restore.c  module_card_restore_la_LDFLAGS = $(MODULE_LDFLAGS) -module_card_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la -lgdbm libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_card_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_card_restore_la_CFLAGS = $(AM_CFLAGS)  # Default sink/source restore module @@ -1447,6 +1541,12 @@ module_rescue_streams_la_LDFLAGS = $(MODULE_LDFLAGS)  module_rescue_streams_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_rescue_streams_la_CFLAGS = $(AM_CFLAGS) +# Automatically move streams to devices that are intended for their roles +module_intended_roles_la_SOURCES = modules/module-intended-roles.c +module_intended_roles_la_LDFLAGS = $(MODULE_LDFLAGS) +module_intended_roles_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_intended_roles_la_CFLAGS = $(AM_CFLAGS) +  # Suspend-on-idle module  module_suspend_on_idle_la_SOURCES = modules/module-suspend-on-idle.c  module_suspend_on_idle_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -1466,30 +1566,29 @@ module_rtp_recv_la_CFLAGS = $(AM_CFLAGS)  # JACK -module_jack_sink_la_SOURCES = modules/module-jack-sink.c +module_jack_sink_la_SOURCES = modules/jack/module-jack-sink.c  module_jack_sink_la_LDFLAGS = $(MODULE_LDFLAGS)  module_jack_sink_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la $(JACK_LIBS) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_jack_sink_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) -module_jack_source_la_SOURCES = modules/module-jack-source.c +module_jack_source_la_SOURCES = modules/jack/module-jack-source.c  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) -# HAL/D-Bus -libdbus_util_la_SOURCES = modules/dbus-util.c modules/dbus-util.h -libdbus_util_la_LDFLAGS = -avoid-version -libdbus_util_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -libdbus_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -  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 libdbus-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +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) +module_udev_detect_la_SOURCES = modules/module-udev-detect.c +module_udev_detect_la_LDFLAGS = $(MODULE_LDFLAGS) +module_udev_detect_la_LIBADD = $(AM_LIBADD) $(UDEV_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_udev_detect_la_CFLAGS = $(AM_CFLAGS) $(UDEV_CFLAGS) +  module_console_kit_la_SOURCES = modules/module-console-kit.c  module_console_kit_la_LDFLAGS = $(MODULE_LDFLAGS) -module_console_kit_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_console_kit_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_console_kit_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)  # GConf support @@ -1506,7 +1605,7 @@ gconf_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)  # Bluetooth proximity  module_bluetooth_proximity_la_SOURCES = modules/bluetooth/module-bluetooth-proximity.c  module_bluetooth_proximity_la_LDFLAGS = $(MODULE_LDFLAGS) -module_bluetooth_proximity_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_bluetooth_proximity_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_bluetooth_proximity_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_BT_PROXIMITY_HELPER=\"$(pulselibexecdir)/proximity-helper\"  proximity_helper_SOURCES = modules/bluetooth/proximity-helper.c @@ -1517,7 +1616,7 @@ proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)  # Bluetooth sink / source  module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c  module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS) -module_bluetooth_discover_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libbluetooth-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_bluetooth_discover_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libbluetooth-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)  libbluetooth_sbc_la_SOURCES = modules/bluetooth/sbc.c modules/bluetooth/sbc.h modules/bluetooth/sbc_tables.h modules/bluetooth/sbc_math.h modules/bluetooth/sbc_primitives.h modules/bluetooth/sbc_primitives.c modules/bluetooth/sbc_primitives_mmx.h modules/bluetooth/sbc_primitives_neon.h modules/bluetooth/sbc_primitives_mmx.c modules/bluetooth/sbc_primitives_neon.c @@ -1534,34 +1633,35 @@ BLUETOOTH_IPC_FILES = $(subst modules/bluetooth/,,$(libbluetooth_ipc_la_SOURCES)  libbluetooth_util_la_SOURCES = modules/bluetooth/bluetooth-util.c modules/bluetooth/bluetooth-util.h  libbluetooth_util_la_LDFLAGS = -avoid-version -libbluetooth_util_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libdbus-util.la +libbluetooth_util_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  libbluetooth_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)  module_bluetooth_device_la_SOURCES = modules/bluetooth/module-bluetooth-device.c modules/bluetooth/rtp.h  module_bluetooth_device_la_LDFLAGS = $(MODULE_LDFLAGS) -module_bluetooth_device_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libbluetooth-util.la libbluetooth-ipc.la libbluetooth-sbc.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_bluetooth_device_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libbluetooth-util.la libbluetooth-ipc.la libbluetooth-sbc.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)  # Apple Airtunes/RAOP -module_raop_sink_la_SOURCES = modules/module-raop-sink.c +module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c  module_raop_sink_la_LDFLAGS = $(MODULE_LDFLAGS)  module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la librtp.la libraop.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_raop_discover_la_SOURCES = modules/module-raop-discover.c +module_raop_discover_la_SOURCES = modules/raop/module-raop-discover.c  module_raop_discover_la_LDFLAGS = $(MODULE_LDFLAGS)  module_raop_discover_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la  module_raop_discover_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) +# Rygel +module_rygel_media_server_la_SOURCES = modules/module-rygel-media-server.c +module_rygel_media_server_la_LDFLAGS = $(MODULE_LDFLAGS) +module_rygel_media_server_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libprotocol-http.la +module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)  ###################################  #        Some minor stuff         #  ################################### -suid: pulseaudio .libs/lt-pulseaudio -	chown root $^ -	chmod u+s $^ - -CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 daemon/pulseaudio.desktop daemon/org.pulseaudio.policy +CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 daemon/pulseaudio.desktop  esdcompat: daemon/esdcompat.in Makefile  	sed -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \ @@ -1599,14 +1699,19 @@ daemon.conf: daemon/daemon.conf.in Makefile  install-exec-hook:  	chown root $(DESTDIR)$(bindir)/pulseaudio ; true -	chmod u+s $(DESTDIR)$(bindir)/pulseaudio  	-chmod u+s $(DESTDIR)$(pulselibexecdir)/proximity-helper  	ln -sf pacat $(DESTDIR)$(bindir)/parec +	ln -sf pacat $(DESTDIR)$(bindir)/pamon +	ln -sf pacat $(DESTDIR)$(bindir)/paplay +	ln -sf pacat $(DESTDIR)$(bindir)/parecord  	rm -f $(DESTDIR)$(libdir)/libpulsedsp.la  	rm -f $(DESTDIR)$(modlibexecdir)/*.la  uninstall-hook:  	rm -f $(DESTDIR)$(bindir)/parec +	rm -f $(DESTDIR)$(bindir)/pamon +	rm -f $(DESTDIR)$(bindir)/paplay +	rm -f $(DESTDIR)$(bindir)/parecord  	rm -f $(DESTDIR)$(libdir)/libpulsedsp.*  	rm -f $(DESTDIR)$(modlibexecdir)/*.so @@ -1628,10 +1733,15 @@ update-sbc:  	done  update-reserve: -	for i in reserve.c reserve.h ; do \ +	for i in reserve.c reserve.h reserve-monitor.c reserve-monitor.h ; do \  		wget -O modules/$$i http://git.0pointer.de/\?p=reserve.git\;a=blob_plain\;f=$$i\;hb=master ; \  	done +update-rtkit: +	for i in rtkit.c rtkit.h ; do \ +		wget -O pulsecore/$$i http://git.0pointer.de/\?p=rtkit.git\;a=blob_plain\;f=$$i\;hb=master ; \ +	done +  # Automatically generate linker version script. We use the same one for all public .sos  update-map-file:  	( echo "PULSE_0 {" ; \ diff --git a/src/daemon/caps.c b/src/daemon/caps.c index d2ae8d0e..294be494 100644 --- a/src/daemon/caps.c +++ b/src/daemon/caps.c @@ -39,6 +39,7 @@  #ifdef HAVE_SYS_CAPABILITY_H  #include <sys/capability.h>  #endif +  #ifdef HAVE_SYS_PRCTL_H  #include <sys/prctl.h>  #endif @@ -51,12 +52,13 @@ int setresgid(gid_t r, gid_t e, gid_t s);  int setresuid(uid_t r, uid_t e, uid_t s);  #endif -#ifdef HAVE_GETUID -  /* Drop root rights when called SUID root */  void pa_drop_root(void) { -    uid_t uid = getuid(); +#ifdef HAVE_GETUID +    uid_t uid; + +    uid = getuid();      if (uid == 0 || geteuid() != 0)          return; @@ -73,90 +75,19 @@ void pa_drop_root(void) {      pa_assert_se(getuid() == uid);      pa_assert_se(geteuid() == uid); -} - -#else - -void pa_drop_root(void) { -} - -#endif - -#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H) - -/* Limit permitted capabilities set to CAPSYS_NICE */ -void pa_limit_caps(void) { -    cap_t caps; -    cap_value_t nice_cap = CAP_SYS_NICE; - -    pa_assert_se(caps = cap_init()); -    pa_assert_se(cap_clear(caps) == 0); -    pa_assert_se(cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET) == 0); -    pa_assert_se(cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET) == 0); - -    if (cap_set_proc(caps) < 0) -        /* Hmm, so we couldn't limit our caps, which probably means we -         * hadn't any in the first place, so let's just make sure of -         * that */ -        pa_drop_caps(); -    else -        pa_log_info(_("Limited capabilities successfully to CAP_SYS_NICE.")); - -    pa_assert_se(cap_free(caps) == 0); - -    pa_assert_se(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0); -} - -/* Drop all capabilities, effectively becoming a normal user */ -void pa_drop_caps(void) { -    cap_t caps; - -#ifndef __OPTIMIZE__ -    /* Valgrind doesn't not know set_caps, so we bypass it here -- but -     * only in development builds.*/ - -    if (pa_in_valgrind() && !pa_have_caps()) -        return;  #endif +#ifdef HAVE_SYS_PRCTL_H      pa_assert_se(prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == 0); - -    pa_assert_se(caps = cap_init()); -    pa_assert_se(cap_clear(caps) == 0); -    pa_assert_se(cap_set_proc(caps) == 0); -    pa_assert_se(cap_free(caps) == 0); - -    pa_assert_se(!pa_have_caps()); -} - -pa_bool_t pa_have_caps(void) { -    cap_t caps; -    cap_flag_value_t flag = CAP_CLEAR; - -#ifdef __OPTIMIZE__ -    pa_assert_se(caps = cap_get_proc()); -#else -    if (!(caps = cap_get_proc())) -        return FALSE;  #endif -    pa_assert_se(cap_get_flag(caps, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0); -    pa_assert_se(cap_free(caps) == 0); - -    return flag == CAP_SET; -} - -#else - -/* NOOPs in case capabilities are not available. */ -void pa_limit_caps(void) { -} - -void pa_drop_caps(void) { -    pa_drop_root(); -} - -pa_bool_t pa_have_caps(void) { -    return FALSE; -} +#ifdef HAVE_SYS_CAPABILITY_H +    { +        cap_t caps; +        pa_assert_se(caps = cap_init()); +        pa_assert_se(cap_clear(caps) == 0); +        pa_assert_se(cap_set_proc(caps) == 0); +        pa_assert_se(cap_free(caps) == 0); +    }  #endif +} diff --git a/src/daemon/caps.h b/src/daemon/caps.h index 94241a9a..5d0ee62e 100644 --- a/src/daemon/caps.h +++ b/src/daemon/caps.h @@ -25,8 +25,5 @@  #include <pulsecore/macro.h>  void pa_drop_root(void); -void pa_drop_caps(void); -void pa_limit_caps(void); -pa_bool_t pa_have_caps(void);  #endif diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index d78089e1..ecb38486 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -31,6 +31,7 @@  #include <pulse/xmalloc.h>  #include <pulse/i18n.h> +#include <pulse/util.h>  #include <pulsecore/core-util.h>  #include <pulsecore/strbuf.h> @@ -109,15 +110,8 @@ static const struct option long_options[] = {  };  void pa_cmdline_help(const char *argv0) { -    const char *e; -      pa_assert(argv0); -    if ((e = strrchr(argv0, '/'))) -        e++; -    else -        e = argv0; -      printf(_("%s [options]\n\n"             "COMMANDS:\n"             "  -h, --help                            Show this help\n" @@ -172,7 +166,8 @@ void pa_cmdline_help(const char *argv0) {             "  -C                                    Open a command line on the running TTY\n"             "                                        after startup\n\n" -           "  -n                                    Don't load default script file\n"), e); +           "  -n                                    Don't load default script file\n"), +           pa_path_get_filename(argv0));  }  int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) { diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c index 64728e27..c2877ecf 100644 --- a/src/daemon/cpulimit.c +++ b/src/daemon/cpulimit.c @@ -24,13 +24,14 @@  #endif  #include <pulse/error.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/core-error.h>  #include <pulsecore/log.h>  #include <pulsecore/macro.h> -#include <pulsecore/rtclock.h>  #include "cpulimit.h" @@ -125,7 +126,7 @@ static void signal_handler(int sig) {          char t[256];  #endif -        now = pa_rtclock_usec(); +        now = pa_rtclock_now();          elapsed = now - last_time;  #ifdef PRINT_CPU_LOAD @@ -139,7 +140,7 @@ static void signal_handler(int sig) {              write_err("Soft CPU time limit exhausted, terminating.\n");              /* Try a soft cleanup */ -            write(the_pipe[1], &c, sizeof(c)); +            (void) write(the_pipe[1], &c, sizeof(c));              phase = PHASE_SOFT;              reset_cpu_time(CPUTIME_INTERVAL_HARD); @@ -184,7 +185,7 @@ int pa_cpu_limit_init(pa_mainloop_api *m) {      pa_assert(the_pipe[1] == -1);      pa_assert(!installed); -    last_time = pa_rtclock_usec(); +    last_time = pa_rtclock_now();      /* Prepare the main loop pipe */      if (pipe(the_pipe) < 0) { diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index ac6cc8aa..9010f2f6 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -60,7 +60,7 @@ static const pa_daemon_conf default_conf = {      .fail = TRUE,      .high_priority = TRUE,      .nice_level = -11, -    .realtime_scheduling = FALSE, +    .realtime_scheduling = TRUE,      .realtime_priority = 5,  /* Half of JACK's default rtprio */      .disallow_module_loading = FALSE,      .disallow_exit = FALSE, @@ -85,6 +85,7 @@ static const pa_daemon_conf default_conf = {      .system_instance = FALSE,      .no_cpu_limit = FALSE,      .disable_shm = FALSE, +    .lock_memory = FALSE,      .default_n_fragments = 4,      .default_fragment_size_msec = 25,      .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }, @@ -446,6 +447,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {          { "no-cpu-limit",               pa_config_parse_bool,     &c->no_cpu_limit, NULL },          { "disable-shm",                pa_config_parse_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 },          { "scache-idle-time",           pa_config_parse_int,      &c->scache_idle_time, NULL },          { "realtime-priority",          parse_rtprio,             c, NULL }, @@ -595,16 +597,14 @@ FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c) {      return f;  } - -static const char* const log_level_to_string[] = { -    [PA_LOG_DEBUG] = "debug", -    [PA_LOG_INFO] = "info", -    [PA_LOG_NOTICE] = "notice", -    [PA_LOG_WARN] = "warning", -    [PA_LOG_ERROR] = "error" -}; -  char *pa_daemon_conf_dump(pa_daemon_conf *c) { +    static const char* const log_level_to_string[] = { +        [PA_LOG_DEBUG] = "debug", +        [PA_LOG_INFO] = "info", +        [PA_LOG_NOTICE] = "notice", +        [PA_LOG_WARN] = "warning", +        [PA_LOG_ERROR] = "error" +    };      pa_strbuf *s;      char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; @@ -630,6 +630,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {      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));      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);      pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time);      pa_strbuf_printf(s, "dl-search-path = %s\n", pa_strempty(c->dl_search_path)); diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index 9cec189f..dd69e048 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -73,7 +73,8 @@ typedef struct pa_daemon_conf {          disallow_exit,          log_meta,          log_time, -        flat_volumes; +        flat_volumes, +        lock_memory;      int exit_idle_time,          scache_idle_time,          auto_log_target, diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index fcd2513a..6931359c 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -27,15 +27,16 @@  ; system-instance = no  ; disable-shm = no  ; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB +; lock-memory = no +; no-cpu-limit = no  ; high-priority = yes  ; nice-level = -11 -; realtime-scheduling = no +; realtime-scheduling = yes  ; realtime-priority = 5  ; exit-idle-time = 20 -; module-idle-time = 20  ; scache-idle-time = 20  ; dl-search-path = (depends on architecture) @@ -55,8 +56,6 @@  ; flat-volumes = yes -; no-cpu-limit = no -  ; rlimit-fsize = -1  ; rlimit-data = -1  ; rlimit-stack = -1 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index 02ac8e57..00c000eb 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -49,18 +49,18 @@ load-module module-augment-properties  #load-module module-pipe-sink  ### Automatically load driver modules depending on the hardware available -.ifexists module-hal-detect@PA_SOEXT@ -load-module module-hal-detect +.ifexists module-udev-detect@PA_SOEXT@ +load-module module-udev-detect  .else  ### Alternatively use the static hardware detection module (for systems that -### lack HAL support) +### lack udev support)  load-module module-detect  .endif  ### Automatically load driver modules for Bluetooth hardware -#.ifexists module-bluetooth-discover@PA_SOEXT@ -#load-module module-bluetooth-discover -#.endif +.ifexists module-bluetooth-discover@PA_SOEXT@ +load-module module-bluetooth-discover +.endif  ### Load several protocols  .ifexists module-esound-protocol-unix@PA_SOEXT@ @@ -100,6 +100,9 @@ load-module module-rescue-streams  ### Make sure we always have a sink around, even if it is a null sink.  load-module module-always-sink +### Honour intended role device property +load-module module-intended-roles +  ### Automatically suspend sinks/sources that become idle for too long  load-module module-suspend-on-idle diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c index 0ffc0fc0..92470b49 100644 --- a/src/daemon/dumpmodules.c +++ b/src/daemon/dumpmodules.c @@ -71,6 +71,8 @@ static void long_info(const char *name, const char *path, pa_modinfo *i) {          if (i->usage)              printf(_("Usage: %s\n"), i->usage);          printf(_("Load Once: %s\n"), pa_yes_no(i->load_once)); +        if (i->deprecated) +            printf(_("DEPRECATION WARNING: %s\n"), i->deprecated);      }      if (path) diff --git a/src/daemon/main.c b/src/daemon/main.c index 0048e7b7..07439675 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -37,9 +37,14 @@  #include <unistd.h>  #include <locale.h>  #include <sys/types.h> +#include <sys/stat.h>  #include <liboil/liboil.h> +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +  #ifdef HAVE_SYS_IOCTL_H  #include <sys/ioctl.h>  #endif @@ -69,6 +74,7 @@  #include <pulsecore/lock-autospawn.h>  #include <pulsecore/winsock.h>  #include <pulsecore/core-error.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core.h>  #include <pulsecore/memblock.h>  #include <pulsecore/module.h> @@ -80,13 +86,15 @@  #include <pulsecore/pid.h>  #include <pulsecore/namereg.h>  #include <pulsecore/random.h> -#include <pulsecore/rtsig.h> -#include <pulsecore/rtclock.h>  #include <pulsecore/macro.h>  #include <pulsecore/mutex.h>  #include <pulsecore/thread.h>  #include <pulsecore/once.h>  #include <pulsecore/shm.h> +#include <pulsecore/memtrap.h> +#ifdef HAVE_DBUS +#include <pulsecore/dbus-shared.h> +#endif  #include "cmdline.h"  #include "cpulimit.h" @@ -94,7 +102,6 @@  #include "dumpmodules.h"  #include "caps.h"  #include "ltdl-bind-now.h" -#include "polkit.h"  #ifdef HAVE_LIBWRAP  /* Only one instance of these variables */ @@ -125,7 +132,7 @@ static void message_cb(pa_mainloop_api*a, pa_time_event*e, const struct timeval      }      pa_timeval_add(pa_gettimeofday(&tvnext), 100000); -    a->time_restart(e, &tvnext); +    a->rtclock_time_restart(e, &tvnext);  }  #endif @@ -330,6 +337,42 @@ static void set_all_rlimits(const pa_daemon_conf *conf) {  }  #endif +#ifdef HAVE_DBUS +static pa_dbus_connection *register_dbus(pa_core *c) { +    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)) { +        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!"); +        return conn; +    } + +    if (dbus_error_is_set(&error)) +        pa_log_warn("Failed to acquire org.pulseaudio.Server: %s: %s", error.name, error.message); +    else +        pa_log_warn("D-Bus name org.pulseaudio.Server already taken. Weird shit!"); + +    /* PA cannot be started twice by the same user and hence we can +     * ignore mostly the case that org.pulseaudio.Server is already +     * taken. */ + +fail: + +    if (conn) +        pa_dbus_connection_unref(conn); + +    dbus_error_free(&error); +    return NULL; +} +#endif +  int main(int argc, char *argv[]) {      pa_core *c = NULL;      pa_strbuf *buf = NULL; @@ -337,9 +380,7 @@ int main(int argc, char *argv[]) {      pa_mainloop *mainloop = NULL;      char *s;      int r = 0, retval = 1, d = 0; -    pa_bool_t suid_root, real_root;      pa_bool_t valid_pid_file = FALSE; -    gid_t gid = (gid_t) -1;      pa_bool_t ltdl_init = FALSE;      int passed_fd = -1;      const char *e; @@ -352,16 +393,20 @@ int main(int argc, char *argv[]) {  #endif      int autospawn_fd = -1;      pa_bool_t autospawn_locked = FALSE; +#ifdef HAVE_DBUS +    pa_dbus_connection *dbus = NULL; +#endif      pa_log_set_ident("pulseaudio"); -    pa_log_set_level(PA_LOG_INFO); +    pa_log_set_level(PA_LOG_NOTICE);      pa_log_set_flags(PA_LOG_COLORS|PA_LOG_PRINT_FILE|PA_LOG_PRINT_LEVEL, PA_LOG_RESET);  #if defined(__linux__) && defined(__OPTIMIZE__)      /*         Disable lazy relocations to make usage of external libraries         more deterministic for our RT threads. We abuse __OPTIMIZE__ as -       a check whether we are a debug build or not. +       a check whether we are a debug build or not. This all is +       admittedly a bit snake-oilish.      */      if (!getenv("LD_BIND_NOW")) { @@ -372,36 +417,19 @@ int main(int argc, char *argv[]) {          pa_set_env("LD_BIND_NOW", "1"); -        if ((rp = pa_readlink("/proc/self/exe"))) -            pa_assert_se(execv(rp, argv) == 0); -        else -            pa_log_warn("Couldn't read /proc/self/exe, cannot self execute. Running in a chroot()?"); -    } -#endif - -#ifdef HAVE_GETUID -    real_root = getuid() == 0; -    suid_root = !real_root && geteuid() == 0; -#else -    real_root = FALSE; -    suid_root = FALSE; -#endif - -    if (!real_root) { -        /* Drop all capabilities except CAP_SYS_NICE  */ -        pa_limit_caps(); +        if ((rp = pa_readlink("/proc/self/exe"))) { -        /* Drop privileges, but keep CAP_SYS_NICE */ -        pa_drop_root(); +            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?"); -        /* After dropping root, the effective set is reset, hence, -         * let's raise it again */ -        pa_limit_caps(); +            pa_xfree(rp); -        /* When capabilities are not supported we will not be able to -         * aquire RT sched anymore. But yes, that's the way it is. It -         * is just too risky tun let PA run as root all the time. */ +        } else +            pa_log_warn("Couldn't read /proc/self/exe, cannot self execute. Running in a chroot()?");      } +#endif      if ((e = getenv("PULSE_PASSED_FD"))) {          passed_fd = atoi(e); @@ -410,15 +438,14 @@ int main(int argc, char *argv[]) {              passed_fd = -1;      } +    /* We might be autospawned, in which case have no idea in which +     * context we have been started. Let's cleanup our execution +     * context as good as possible */ +    pa_drop_root();      pa_close_all(passed_fd, -1); -      pa_reset_sigs(-1);      pa_unblock_sigs(-1); -    /* At this point, we are a normal user, possibly with CAP_NICE if -     * we were started SUID. If we are started as normal root, than we -     * still are normal root. */ -      setlocale(LC_ALL, "");      pa_init_i18n(); @@ -443,150 +470,6 @@ 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); -    pa_log_debug("Started as real root: %s, suid root: %s", pa_yes_no(real_root), pa_yes_no(suid_root)); - -    if (!real_root && pa_have_caps()) { -#ifdef HAVE_SYS_RESOURCE_H -        struct rlimit rl; -#endif -        pa_bool_t allow_high_priority = FALSE, allow_realtime = FALSE; - -        /* Let's better not enable high prio or RT by default */ - -        if (conf->high_priority && !allow_high_priority) { -            if (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) { -                pa_log_info(_("We're in the group '%s', allowing high-priority scheduling."), PA_REALTIME_GROUP); -                allow_high_priority = TRUE; -            } -        } - -        if (conf->realtime_scheduling && !allow_realtime) { -            if (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) { -                pa_log_info(_("We're in the group '%s', allowing real-time scheduling."), PA_REALTIME_GROUP); -                allow_realtime = TRUE; -            } -        } - -#ifdef HAVE_POLKIT -        if (conf->high_priority && !allow_high_priority) { -            if (pa_polkit_check("org.pulseaudio.acquire-high-priority") > 0) { -                pa_log_info(_("PolicyKit grants us acquire-high-priority privilege.")); -                allow_high_priority = TRUE; -            } else -                pa_log_info(_("PolicyKit refuses acquire-high-priority privilege.")); -        } - -        if (conf->realtime_scheduling && !allow_realtime) { -            if (pa_polkit_check("org.pulseaudio.acquire-real-time") > 0) { -                pa_log_info(_("PolicyKit grants us acquire-real-time privilege.")); -                allow_realtime = TRUE; -            } else -                pa_log_info(_("PolicyKit refuses acquire-real-time privilege.")); -        } -#endif - -        if (!allow_high_priority && !allow_realtime) { - -            /* OK, there's no further need to keep CAP_NICE. Hence -             * let's give it up early */ - -            pa_drop_caps(); -        } - -#ifdef RLIMIT_RTPRIO -        if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0) -            if (rl.rlim_cur > 0) { -                pa_log_info("RLIMIT_RTPRIO is set to %u, allowing real-time scheduling.", (unsigned) rl.rlim_cur); -                allow_realtime = TRUE; -            } -#endif -#ifdef RLIMIT_NICE -        if (getrlimit(RLIMIT_NICE, &rl) >= 0) -            if (rl.rlim_cur > 20 ) { -                pa_log_info("RLIMIT_NICE is set to %u, allowing high-priority scheduling.", (unsigned) rl.rlim_cur); -                allow_high_priority = TRUE; -            } -#endif - -        if ((conf->high_priority && !allow_high_priority) || -            (conf->realtime_scheduling && !allow_realtime)) -            pa_log_notice(_("Called SUID root and real-time and/or high-priority scheduling was requested in the configuration. However, we lack the necessary privileges:\n" -                            "We are not in group '%s', PolicyKit refuse to grant us the requested privileges and we have no increase RLIMIT_NICE/RLIMIT_RTPRIO resource limits.\n" -                            "For enabling real-time/high-priority scheduling please acquire the appropriate PolicyKit privileges, or become a member of '%s', or increase the RLIMIT_NICE/RLIMIT_RTPRIO resource limits for this user."), -                          PA_REALTIME_GROUP, PA_REALTIME_GROUP); - - -        if (!allow_realtime) -            conf->realtime_scheduling = FALSE; - -        if (!allow_high_priority) -            conf->high_priority = FALSE; -    } - -#ifdef HAVE_SYS_RESOURCE_H -    /* Reset resource limits. If we are run as root (for system mode) -     * this might end up increasing the limits, which is intended -     * behaviour. For all other cases, i.e. started as normal user, or -     * SUID root at this point we should have no CAP_SYS_RESOURCE and -     * increasing the limits thus should fail. Which is, too, intended -     * behaviour */ - -    set_all_rlimits(conf); -#endif - -    if (conf->high_priority && !pa_can_high_priority()) { -        pa_log_warn(_("High-priority scheduling enabled in configuration but not allowed by policy.")); -        conf->high_priority = FALSE; -    } - -    if (conf->high_priority && (conf->cmd == PA_CMD_DAEMON || conf->cmd == PA_CMD_START)) -        pa_raise_priority(conf->nice_level); - -    pa_log_debug("Can realtime: %s, can high-priority: %s", pa_yes_no(pa_can_realtime()), pa_yes_no(pa_can_high_priority())); - -    if (!real_root && pa_have_caps()) { -        pa_bool_t drop; - -        drop = (conf->cmd != PA_CMD_DAEMON && conf->cmd != PA_CMD_START) || !conf->realtime_scheduling; - -#ifdef RLIMIT_RTPRIO -        if (!drop) { -            struct rlimit rl; -            /* At this point we still have CAP_NICE if we were loaded -             * SUID root. If possible let's acquire RLIMIT_RTPRIO -             * instead and give CAP_NICE up. */ - -            if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0) { - -                if (rl.rlim_cur >= 9) -                    drop = TRUE; -                else { -                    rl.rlim_max = rl.rlim_cur = 9; - -                    if (setrlimit(RLIMIT_RTPRIO, &rl) >= 0) { -                        pa_log_info(_("Successfully increased RLIMIT_RTPRIO")); -                        drop = TRUE; -                    } else -                        pa_log_warn(_("RLIMIT_RTPRIO failed: %s"), pa_cstrerror(errno)); -                } -            } -        } -#endif - -        if (drop)  { -            pa_log_info(_("Giving up CAP_NICE")); -            pa_drop_caps(); -            suid_root = FALSE; -        } -    } - -    if (conf->realtime_scheduling && !pa_can_realtime()) { -        pa_log_warn(_("Real-time scheduling enabled in configuration but not allowed by policy.")); -        conf->realtime_scheduling = FALSE; -    } - -    pa_log_debug("Can realtime: %s, can high-priority: %s", pa_yes_no(pa_can_realtime()), pa_yes_no(pa_can_high_priority())); -      LTDL_SET_PRELOADED_SYMBOLS();      pa_ltdl_init();      ltdl_init = TRUE; @@ -671,9 +554,9 @@ int main(int argc, char *argv[]) {              pa_assert(conf->cmd == PA_CMD_DAEMON || conf->cmd == PA_CMD_START);      } -    if (real_root && !conf->system_instance) +    if (getuid() == 0 && !conf->system_instance)          pa_log_warn(_("This program is not intended to be run as root (unless --system is specified).")); -    else if (!real_root && conf->system_instance) { +    else if (getuid() != 0 && conf->system_instance) {          pa_log(_("Root privileges required."));          goto finish;      } @@ -819,6 +702,13 @@ int main(int argc, char *argv[]) {      pa_assert_se(chdir("/") == 0);      umask(0022); +#ifdef HAVE_SYS_RESOURCE_H +    set_all_rlimits(conf); +#endif +    pa_rtclock_hrtimer_enable(); + +    pa_raise_priority(conf->nice_level); +      if (conf->system_instance)          if (change_user() < 0)              goto finish; @@ -851,6 +741,14 @@ int main(int argc, char *argv[]) {      pa_log_debug(_("Optimized build: no"));  #endif +#ifdef NDEBUG +    pa_log_debug(_("NDEBUG defined, all asserts disabled.")); +#elif defined(FASTPATH) +    pa_log_debug(_("FASTPATH defined, only fast path asserts disabled.")); +#else +    pa_log_debug(_("All asserts enabled.")); +#endif +      if (!(s = pa_machine_id())) {          pa_log(_("Failed to get machine ID"));          goto finish; @@ -858,6 +756,11 @@ int main(int argc, char *argv[]) {      pa_log_info(_("Machine ID is %s."), s);      pa_xfree(s); +    if ((s = pa_session_id())) { +        pa_log_info(_("Session ID is %s."), s); +        pa_xfree(s); +    } +      if (!(s = pa_get_runtime_dir()))          goto finish;      pa_log_info(_("Using runtime directory %s."), s); @@ -870,6 +773,11 @@ int main(int argc, char *argv[]) {      pa_log_info(_("Running in system mode: %s"), pa_yes_no(pa_in_system_mode())); +    if (pa_in_system_mode()) +        pa_log_warn(_("OK, so you are running PA in system mode. Please note that you most likely shouldn't be doing that.\n" +                      "If you do it nonetheless then it's your own fault if things don't work as expected.\n" +                      "Please read http://pulseaudio.org/wiki/WhatIsWrongWithSystemMode for an explanation why system mode is usually a bad idea.")); +      if (conf->use_pid_file) {          int z; @@ -890,21 +798,25 @@ int main(int argc, char *argv[]) {          valid_pid_file = TRUE;      } -#ifdef SIGPIPE -    signal(SIGPIPE, SIG_IGN); -#endif +    pa_disable_sigpipe();      if (pa_rtclock_hrtimer())          pa_log_info(_("Fresh high-resolution timers available! Bon appetit!"));      else          pa_log_info(_("Dude, your kernel stinks! The chef's recommendation today is Linux with high-resolution timers enabled!")); -    pa_rtclock_hrtimer_enable(); - -#ifdef SIGRTMIN -    /* Valgrind uses SIGRTMAX. To easy debugging we don't use it here */ -    pa_rtsig_configure(SIGRTMIN, SIGRTMAX-1); +    if (conf->lock_memory) { +#ifdef HAVE_SYS_MMAN_H +        if (mlockall(MCL_FUTURE) < 0) +            pa_log_warn("mlockall() failed: %s", pa_cstrerror(errno)); +        else +            pa_log_info("Sucessfully locked process into memory."); +#else +        pa_log_warn("Memory locking requested but not supported on platform.");  #endif +    } + +    pa_memtrap_install();      pa_assert_se(mainloop = pa_mainloop_new()); @@ -942,7 +854,7 @@ int main(int argc, char *argv[]) {  #endif  #ifdef OS_IS_WIN32 -    win32_timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&win32_tv), message_cb, NULL); +    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(); @@ -989,6 +901,10 @@ int main(int argc, char *argv[]) {      }  #endif +#ifdef HAVE_DBUS +    dbus = register_dbus(c); +#endif +      pa_log_info(_("Daemon startup complete."));      retval = 0; @@ -998,6 +914,10 @@ int main(int argc, char *argv[]) {      pa_log_info(_("Daemon shutdown initiated."));  finish: +#ifdef HAVE_DBUS +    if (dbus) +        pa_dbus_connection_unref(dbus); +#endif      if (autospawn_fd >= 0) {          if (autospawn_locked) diff --git a/src/daemon/start-pulseaudio-x11.in b/src/daemon/start-pulseaudio-x11.in index 391a6d3c..c57c489d 100755 --- a/src/daemon/start-pulseaudio-x11.in +++ b/src/daemon/start-pulseaudio-x11.in @@ -19,6 +19,8 @@  set -e +[ -z "$PULSE_SERVER" ] +  @PA_BINARY@ --start "$@"  if [ x"$DISPLAY" != x ] ; then diff --git a/src/log b/src/log new file mode 100644 index 00000000..b4b67ace --- /dev/null +++ b/src/log @@ -0,0 +1,1555 @@ +execve("/home/lennart/projects/pulseaudio/src/.libs/lt-pulseaudio", ["/home/lennart/projects/pulseaudi"..., "-vvvC"], [/* 58 vars */]) = 0 +brk(0)                                  = 0x99a3000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb805d000 +access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/tls/i686/sse2/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/tls/i686/sse2", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/tls/i686/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/tls/i686", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/tls/sse2/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/tls/sse2", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/tls/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/tls", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/i686/sse2/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/i686/sse2", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/i686/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/i686", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/sse2/libcli.so", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/home/lennart/projects/pulseaudio/src/.libs/sse2", 0xbff5c3c4) = -1 ENOENT (No such file or directory) +open("/home/lennart/projects/pulseaudio/src/.libs/libcli.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\n\0\0004\0\0\0\204"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=26302, ...}) = 0 +mmap2(NULL, 10628, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xdd6000 +mmap2(0xdd8000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xdd8000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libprotocol-cli.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\n\0\0004\0\0\0\0"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27709, ...}) = 0 +mmap2(NULL, 11524, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xec1000 +mmap2(0xec3000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xec3000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libprotocol-simple.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\33\0\0004\0\0\0h"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=61964, ...}) = 0 +mmap2(NULL, 28384, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xc65000 +mmap2(0xc6b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5) = 0xc6b000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libprotocol-http.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\34\0\0004\0\0\0\234"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=68747, ...}) = 0 +mmap2(NULL, 29480, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xc82000 +mmap2(0xc89000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7) = 0xc89000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libprotocol-native.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260I\0\0004\0\0\0\304"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=201988, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb805c000 +mmap2(NULL, 113704, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xa58000 +mmap2(0xa73000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a) = 0xa73000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libprotocol-esound.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`$\0\0004\0\0\0\350"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=96425, ...}) = 0 +mmap2(NULL, 50376, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x110000 +mmap2(0x11c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb) = 0x11c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/librtp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\35\0\0004\0\0\0P"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=74195, ...}) = 0 +mmap2(NULL, 42332, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xeb5000 +mmap2(0xebf000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9) = 0xebf000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libavahi-wrap.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\6\0\0004\0\0\0\20"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=17486, ...}) = 0 +mmap2(NULL, 11056, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xaf2000 +mmap2(0xaf4000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xaf4000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-console-kit.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\22\0\0004\0\0\0<"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=35362, ...}) = 0 +mmap2(NULL, 16168, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xbef000 +mmap2(0xbf2000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0xbf2000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-cli.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340\t\0\0004\0\0\0\324"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=24618, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb805b000 +mmap2(NULL, 9736, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf85000 +mmap2(0xf87000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xf87000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-cli-protocol-tcp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\f\0\0004\0\0\0\20"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=26573, ...}) = 0 +mmap2(NULL, 10760, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2a6000 +mmap2(0x2a8000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x2a8000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-simple-protocol-tcp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\r\0\0004\0\0\0t"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27592, ...}) = 0 +mmap2(NULL, 11376, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x11d000 +mmap2(0x11f000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x11f000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-null-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\23\0\0004\0\0\0\224"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=39380, ...}) = 0 +mmap2(NULL, 17548, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x277000 +mmap2(0x27b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x27b000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-sine-source.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \22\0\0004\0\0\0@"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=38279, ...}) = 0 +mmap2(NULL, 17164, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3e6000 +mmap2(0x3ea000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x3ea000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-detect.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\n\0\0004\0\0\0\20"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27041, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb805a000 +mmap2(NULL, 10760, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x120000 +mmap2(0x122000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x122000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-volume-restore.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\10\0\0004\0\0\0\360"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=22990, ...}) = 0 +mmap2(NULL, 8792, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x123000 +mmap2(0x125000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x125000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-device-restore.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\20\0\0004\0\0\0\200"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=39657, ...}) = 0 +mmap2(NULL, 17680, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x225000 +mmap2(0x229000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x229000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-stream-restore.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\33\0\0004\0\0\0\4"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=63416, ...}) = 0 +mmap2(NULL, 27684, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x126000 +mmap2(0x12c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5) = 0x12c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-card-restore.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\r\0\0004\0\0\0\214"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=33621, ...}) = 0 +mmap2(NULL, 13616, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3ce000 +mmap2(0x3d1000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x3d1000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-default-device-restore.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\v\0\0004\0\0\0l"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=30701, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8059000 +mmap2(NULL, 12256, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xcbe000 +mmap2(0xcc0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xcc0000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-always-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\n\0\0004\0\0\0\350"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=29068, ...}) = 0 +mmap2(NULL, 11464, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xbe0000 +mmap2(0xbe2000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xbe2000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-rescue-streams.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\n\0\0004\0\0\0\250"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=33235, ...}) = 0 +mmap2(NULL, 11608, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf4b000 +mmap2(0xf4d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xf4d000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-suspend-on-idle.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\r\0\0004\0\0\0d"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=53693, ...}) = 0 +mmap2(NULL, 22708, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x307000 +mmap2(0x30c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x30c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-http-protocol-tcp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\f\0\0004\0\0\0`"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27326, ...}) = 0 +mmap2(NULL, 11248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xbc6000 +mmap2(0xbc8000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xbc8000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-sine.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\f\0\0004\0\0\0\330"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=33352, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8058000 +mmap2(NULL, 8744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xdcc000 +mmap2(0xdce000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0xdce000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-native-protocol-tcp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\16\0\0004\0\0\0$"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=28434, ...}) = 0 +mmap2(NULL, 11892, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xd5a000 +mmap2(0xd5c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xd5c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-native-protocol-fd.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\t\0\0004\0\0\0\\"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=23553, ...}) = 0 +mmap2(NULL, 8864, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x12d000 +mmap2(0x12f000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x12f000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-esound-protocol-tcp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\r\0\0004\0\0\0\10"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27740, ...}) = 0 +mmap2(NULL, 11440, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x450000 +mmap2(0x452000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x452000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-combine.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P \0\0004\0\0\0\374"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=78986, ...}) = 0 +mmap2(NULL, 39188, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x81a000 +mmap2(0x823000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8) = 0x823000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-remap-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\24\0\0004\0\0\0\24"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=50001, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8057000 +mmap2(NULL, 21860, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x51b000 +mmap2(0x520000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x520000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-ladspa-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\32\0\0004\0\0\0H"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=62718, ...}) = 0 +mmap2(NULL, 31020, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x64d000 +mmap2(0x654000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6) = 0x654000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-esound-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \32\0\0004\0\0\0\204"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=50510, ...}) = 0 +mmap2(NULL, 21288, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3ec000 +mmap2(0x3f1000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5) = 0x3f1000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-tunnel-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\"\0\0004\0\0\0p"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=88431, ...}) = 0 +mmap2(NULL, 42316, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x930000 +mmap2(0x93a000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa) = 0x93a000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-tunnel-source.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000!\0\0004\0\0\0|"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=80181, ...}) = 0 +mmap2(NULL, 40684, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x130000 +mmap2(0x139000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8) = 0x139000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-position-event-sounds.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\n\0\0004\0\0\0\304"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=30412, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8056000 +mmap2(NULL, 10148, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xe35000 +mmap2(0xe37000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xe37000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-augment-properties.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\16\0\0004\0\0\0\350"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=38375, ...}) = 0 +mmap2(NULL, 15512, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xaed000 +mmap2(0xaf0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0xaf0000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-cork-music-on-phone.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\v\0\0004\0\0\0d"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=35731, ...}) = 0 +mmap2(NULL, 13048, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x13a000 +mmap2(0x13d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x13d000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-rtp-send.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\25\0\0004\0\0\0\310"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=48260, ...}) = 0 +mmap2(NULL, 17124, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb74000 +mmap2(0xb78000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0xb78000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-rtp-recv.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\34\0\0004\0\0\0<"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=61117, ...}) = 0 +mmap2(NULL, 25412, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xd2a000 +mmap2(0xd30000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6) = 0xd30000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-cli-protocol-unix.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\f\0\0004\0\0\0l"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=26376, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8055000 +mmap2(NULL, 10632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb52000 +mmap2(0xb54000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xb54000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-simple-protocol-unix.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\r\0\0004\0\0\0\360"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27427, ...}) = 0 +mmap2(NULL, 11276, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x857000 +mmap2(0x859000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x859000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-http-protocol-unix.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340\f\0\0004\0\0\0,"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=26985, ...}) = 0 +mmap2(NULL, 10988, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x13e000 +mmap2(0x140000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x140000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-native-protocol-unix.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\r\0\0004\0\0\0004"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=28161, ...}) = 0 +mmap2(NULL, 11700, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x9b5000 +mmap2(0x9b7000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x9b7000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-esound-protocol-unix.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\r\0\0004\0\0\0\f"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27786, ...}) = 0 +mmap2(NULL, 11472, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x141000 +mmap2(0x143000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x143000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-pipe-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\25\0\0004\0\0\0p"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=40076, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8054000 +mmap2(NULL, 17948, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x5db000 +mmap2(0x5df000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x5df000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-pipe-source.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\25\0\0004\0\0\0\210"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=39495, ...}) = 0 +mmap2(NULL, 17788, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x144000 +mmap2(0x148000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x148000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-esound-compat-spawnfd.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\10\0\0004\0\0\0$"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=22829, ...}) = 0 +mmap2(NULL, 4584, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x338000 +mmap2(0x339000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x339000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-esound-compat-spawnpid.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\10\0\0004\0\0\0\34"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=22805, ...}) = 0 +mmap2(NULL, 4580, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xd1c000 +mmap2(0xd1d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xd1d000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-match.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\16\0\0004\0\0\0\314"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=35019, ...}) = 0 +mmap2(NULL, 13052, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x149000 +mmap2(0x14c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x14c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-x11-bell.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340\v\0\0004\0\0\0("..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=38703, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8053000 +mmap2(NULL, 11552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x4b5000 +mmap2(0x4b7000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x4b7000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-x11-publish.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\17\0\0004\0\0\0\\"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=30054, ...}) = 0 +mmap2(NULL, 13040, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x537000 +mmap2(0x53a000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x53a000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-x11-xsmp.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\16\0\0004\0\0\0\354"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=31212, ...}) = 0 +mmap2(NULL, 8788, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xcc7000 +mmap2(0xcc9000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0xcc9000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-x11-cork-request.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\v\0\0004\0\0\0\310"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=31995, ...}) = 0 +mmap2(NULL, 11352, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x14d000 +mmap2(0x14f000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x14f000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/liboss-util.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\f\0\0004\0\0\0\370"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=21591, ...}) = 0 +mmap2(NULL, 15072, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xd18000 +mmap2(0xd1b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0xd1b000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-oss.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\35\0\0004\0\0\0d"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=72703, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8052000 +mmap2(NULL, 40732, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x731000 +mmap2(0x73a000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8) = 0x73a000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libalsa-util.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300i\0\0004\0\0\0\\"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=298093, ...}) = 0 +mmap2(NULL, 159880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x150000 +mmap2(0x176000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25) = 0x176000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-alsa-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\n\0\0004\0\0\0D"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=24594, ...}) = 0 +mmap2(NULL, 10040, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xef4000 +mmap2(0xef6000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xef6000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-alsa-source.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\n\0\0004\0\0\0X"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=26504, ...}) = 0 +mmap2(NULL, 10424, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x178000 +mmap2(0x17a000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x17a000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-alsa-card.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\24\0\0004\0\0\0\30"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=36914, ...}) = 0 +mmap2(NULL, 17536, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x766000 +mmap2(0x76a000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x76a000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/i686/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/i686/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/i686/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/i686", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/tls", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/i686/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/i686/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/i686/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/i686", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/i686/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/i686/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/i686/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/i686", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/tls", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/i686/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/i686/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/i686/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/i686", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/sse2/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/sse2", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/libasound.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +stat64("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib", 0xbff5bd50) = -1 ENOENT (No such file or directory) +open("/etc/ld.so.cache", O_RDONLY)      = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=123199, ...}) = 0 +mmap2(NULL, 123199, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb8033000 +close(3)                                = 0 +open("/lib/libasound.so.2", O_RDONLY)   = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\335H\0034\0\0\0\374"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=923860, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8032000 +mmap2(0x3470000, 920976, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3470000 +mmap2(0x354d000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xdd) = 0x354d000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-zeroconf-publish.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\24\0\0004\0\0\0\4"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=54031, ...}) = 0 +mmap2(NULL, 21168, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xbcb000 +mmap2(0xbd0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5) = 0xbd0000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-zeroconf-discover.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\21\0\0004\0\0\0\230"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=38877, ...}) = 0 +mmap2(NULL, 16008, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x60d000 +mmap2(0x610000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x610000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-lirc.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\v\0\0004\0\0\0\230"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=28280, ...}) = 0 +mmap2(NULL, 11848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x54e000 +mmap2(0x550000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x550000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/liblirc_client.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/liblirc_client.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\215A\0004\0\0\0P"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=24064, ...}) = 0 +mmap2(0x418000, 22084, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x418000 +mmap2(0x41d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5) = 0x41d000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-mmkbd-evdev.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320\v\0\0004\0\0\0\210"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=29250, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8031000 +mmap2(NULL, 8736, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x6b8000 +mmap2(0x6ba000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x6ba000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-jack-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\32\0\0004\0\0\0d"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=46458, ...}) = 0 +mmap2(NULL, 22128, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x9ac000 +mmap2(0x9b1000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x9b1000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-jack-source.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\31\0\0004\0\0\0\354"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=44780, ...}) = 0 +mmap2(NULL, 17180, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x642000 +mmap2(0x646000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x646000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libjack.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libjack.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340_\266\0024\0\0\0\214"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=74812, ...}) = 0 +mmap2(0x2b62000, 109020, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2b62000 +mmap2(0x2b73000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x10) = 0x2b73000 +mmap2(0x2b75000, 31196, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2b75000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libpthread.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libpthread.so.0", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\351D\0004\0\0\0\350"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=140408, ...}) = 0 +mmap2(0x44a000, 106976, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x17b000 +mprotect(0x191000, 4096, PROT_NONE)     = 0 +mmap2(0x192000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16) = 0x192000 +mmap2(0x194000, 4576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x194000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-gconf.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\f\0\0004\0\0\0\320"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=32282, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8030000 +mmap2(NULL, 14348, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x43f000 +mmap2(0x442000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x442000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-hal-detect.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\27\0\0004\0\0\0\374"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=50927, ...}) = 0 +mmap2(NULL, 26372, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xac6000 +mmap2(0xacc000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5) = 0xacc000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libhal.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libhal.so.1", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360\326/\0034\0\0\0\270"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=68928, ...}) = 0 +mmap2(0x32fb000, 66060, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x32fb000 +mmap2(0x330b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x10) = 0x330b000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-udev-detect.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\r\0\0004\0\0\0\210"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=27771, ...}) = 0 +mmap2(NULL, 11752, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x1a9000 +mmap2(0x1ab000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x1ab000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libudev.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libudev.so.0", O_RDONLY)     = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260|a\0004\0\0\0000"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=49592, ...}) = 0 +mmap2(0x616000, 50920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x616000 +mmap2(0x622000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb) = 0x622000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-rygel-media-server.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\25\0\0004\0\0\0\274"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=71491, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb802f000 +mmap2(NULL, 42160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x196000 +mmap2(0x1a0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9) = 0x1a0000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libbluetooth-util.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\26\0\0004\0\0\0\324"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=60213, ...}) = 0 +mmap2(NULL, 32364, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x5b2000 +mmap2(0x5b9000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6) = 0x5b9000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-bluetooth-proximity.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\23\0\0004\0\0\0p"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=42108, ...}) = 0 +mmap2(NULL, 17056, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x433000 +mmap2(0x437000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x437000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-bluetooth-discover.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\r\0\0004\0\0\0<"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=28802, ...}) = 0 +mmap2(NULL, 11720, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x1a1000 +mmap2(0x1a3000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x1a3000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libbluetooth-ipc.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \10\0\0004\0\0\0008"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=12484, ...}) = 0 +mmap2(NULL, 4652, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2d1000 +mmap2(0x2d2000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x2d2000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libbluetooth-sbc.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \10\0\0004\0\0\0\270"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=89082, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb802e000 +mmap2(NULL, 65272, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x341000 +mmap2(0x350000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe) = 0x350000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-bluetooth-device.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360-\0\0004\0\0\0\230"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=103350, ...}) = 0 +mmap2(NULL, 58940, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x1ac000 +mmap2(0x1ba000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xd) = 0x1ba000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libraop.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \23\0\0004\0\0\0\260"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=46171, ...}) = 0 +mmap2(NULL, 21436, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xe7b000 +mmap2(0xe80000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0xe80000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-raop-sink.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\32\0\0004\0\0\0$"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=47967, ...}) = 0 +mmap2(NULL, 23756, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x1bb000 +mmap2(0x1c0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x1c0000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libssl.so.8", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libssl.so.8", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\362\365\0024\0\0\0\274"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=311404, ...}) = 0 +mmap2(0x2f53000, 308264, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2f53000 +mmap2(0x2f9b000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x48) = 0x2f9b000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libcrypto.so.8", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libcrypto.so.8", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300h\317\0024\0\0\0\364"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=1473700, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb802d000 +mmap2(0x2cbe000, 1488896, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2cbe000 +mmap2(0x2e10000, 94208, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x151) = 0x2e10000 +mmap2(0x2e27000, 10240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2e27000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libz.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libz.so.1", O_RDONLY)        = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \206F\0004\0\0\0\0"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=75440, ...}) = 0 +mmap2(0x467000, 76688, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x467000 +mmap2(0x479000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x11) = 0x479000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/module-raop-discover.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@\17\0\0004\0\0\0\34"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=36557, ...}) = 0 +mmap2(NULL, 14560, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xeec000 +mmap2(0xeef000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0xeef000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libavahi-common.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libavahi-common.so.3", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0PU\r\0034\0\0\0\34"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=47268, ...}) = 0 +mmap2(0x30d3000, 48592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x30d3000 +mmap2(0x30de000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa) = 0x30de000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libavahi-client.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libavahi-client.so.3", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\3408\f\0034\0\0\0,"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=63964, ...}) = 0 +mmap2(0x30c1000, 64992, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x30c1000 +mmap2(0x30d0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe) = 0x30d0000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libpulsecore-0.9.15.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\316\0\0004\0\0\0\224"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=1093558, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb802c000 +mmap2(NULL, 537684, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76b000 +mmap2(0x7ed000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x81) = 0x7ed000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libpulse.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240z\0\0004\0\0\0 "..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=535997, ...}) = 0 +mmap2(NULL, 285372, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x1c1000 +mmap2(0x206000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x45) = 0x206000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libtdb.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libtdb.so.1", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20kP\0034\0\0\0P"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=54784, ...}) = 0 +mmap2(0x3505000, 56392, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x207000 +mmap2(0x214000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xc) = 0x214000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libpulsecommon-0.9.15.so", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\255\0\0004\0\0\0\300"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0775, st_size=718884, ...}) = 0 +mmap2(NULL, 363448, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x351000 +mmap2(0x3a9000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x58) = 0x3a9000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libX11.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libX11.so.6", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\301Y\0004\0\0\0\274"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=1242732, ...}) = 0 +mmap2(0x588000, 1245108, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf8b000 +mmap2(0x10b7000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12b) = 0x10b7000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libSM.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libSM.so.6", O_RDONLY)   = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P\263\251\0024\0\0\0\264"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=30052, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb802b000 +mmap2(0x2a9a000, 31248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2a9a000 +mmap2(0x2aa1000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6) = 0x2aa1000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libICE.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libICE.so.6", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260u\252\0024\0\0\0@"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=95472, ...}) = 0 +mmap2(0x2aa4000, 104080, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2aa4000 +mmap2(0x2abb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16) = 0x2abb000 +mmap2(0x2abc000, 5776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2abc000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libXtst.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libXtst.so.6", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260n\352\0024\0\0\0H"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=20984, ...}) = 0 +mmap2(0x2ea6000, 22096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2ea6000 +mmap2(0x2eab000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x2eab000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libwrap.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libwrap.so.0", O_RDONLY)     = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260>\316\0024\0\0\0H"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=33016, ...}) = 0 +mmap2(0x2ce2000, 36348, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x215000 +mmap2(0x21d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7) = 0x21d000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libasyncns.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libasyncns.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200M\302\0024\0\0\0`"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=20456, ...}) = 0 +mmap2(0x2c24000, 21676, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2c24000 +mmap2(0x2c29000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x2c29000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libltdl.so.7", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libltdl.so.7", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\332H\0034\0\0\0D"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=35316, ...}) = 0 +mmap2(0x348c000, 36568, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x22a000 +mmap2(0x232000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7) = 0x232000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libsamplerate.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libsamplerate.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\350%\0034\0\0\0\300"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=1483632, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb802a000 +mmap2(0x325e000, 1484828, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x45f2000 +mmap2(0x475c000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x169) = 0x475c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libspeexdsp.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libspeexdsp.so.1", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300D\n\0034\0\0\0<"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=67564, ...}) = 0 +mmap2(0x30a3000, 68776, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x30a3000 +mmap2(0x30b3000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xf) = 0x30b3000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libsndfile.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libsndfile.so.1", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\2\260\0024\0\0\0\344"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=380604, ...}) = 0 +mmap2(0x2afe000, 399520, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2afe000 +mmap2(0x2b5a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5b) = 0x2b5a000 +mmap2(0x2b5c000, 14496, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2b5c000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/liboil-0.3.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/liboil-0.3.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240Tw\0004\0\0\0\4"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=487860, ...}) = 0 +mmap2(0x761000, 499016, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x85a000 +mmap2(0x8ba000, 98304, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5f) = 0x8ba000 +mmap2(0x8d2000, 7496, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x8d2000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libpolkit-dbus.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libpolkit-dbus.so.2", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\226I\0034\0\0\0\260"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=48952, ...}) = 0 +mmap2(0x3497000, 45904, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x233000 +mmap2(0x23e000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb) = 0x23e000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libpolkit.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libpolkit.so.2", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`CG\0034\0\0\0P"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=102656, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8029000 +mmap2(0x3470000, 103872, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x23f000 +mmap2(0x258000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18) = 0x258000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libdbus-1.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libdbus-1.so.3", O_RDONLY)   = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320\356\224\0024\0\0\0\314"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=261756, ...}) = 0 +mmap2(0x294a000, 263324, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x294a000 +mmap2(0x2989000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3e) = 0x2989000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libcap.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libcap.so.2", O_RDONLY)      = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0-\233\0004\0\0\0\244"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=15956, ...}) = 0 +mmap2(0x9b2000, 17168, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x1a4000 +mmap2(0x1a8000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x1a8000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/librt.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/librt.so.1", O_RDONLY)       = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\330G\0004\0\0\0T"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=43924, ...}) = 0 +mmap2(0x47c000, 33332, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x47c000 +mmap2(0x483000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6) = 0x483000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libdl.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libdl.so.2", O_RDONLY)       = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`:D\0004\0\0\0("..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=20464, ...}) = 0 +mmap2(0x443000, 16500, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x443000 +mmap2(0x446000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2) = 0x446000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libm.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libm.so.6", O_RDONLY)        = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\264A\0004\0\0\0<"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=210300, ...}) = 0 +mmap2(0x418000, 163968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2d3000 +mmap2(0x2fa000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x26) = 0x2fa000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libc.so.6", O_RDONLY)        = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\233+\0004\0\0\0\214"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=1811780, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8028000 +mmap2(0x2a3000, 1517864, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x73de000 +mmap2(0x754b000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16d) = 0x754b000 +mmap2(0x754e000, 10536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x754e000 +close(3)                                = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8027000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8026000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8025000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8024000 +open("/home/lennart/projects/pulseaudio/src/.libs/libgssapi_krb5.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libgssapi_krb5.so.2", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\375\310\0024\0\0\0\240"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=178336, ...}) = 0 +mmap2(0x2c8c000, 175068, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2c8c000 +mmap2(0x2cb6000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2a) = 0x2cb6000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libkrb5.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libkrb5.so.3", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340\361\353\0024\0\0\0d"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=642108, ...}) = 0 +mmap2(0x2eaf000, 638952, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2eaf000 +mmap2(0x2f49000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9a) = 0x2f49000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libcom_err.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libcom_err.so.2", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\275\337\0004\0\0\0\230"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=10864, ...}) = 0 +mmap2(0xdfb000, 11996, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xdfb000 +mmap2(0xdfd000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xdfd000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libk5crypto.so.3", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libk5crypto.so.3", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\206\346\0024\0\0\0\4"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=149212, ...}) = 0 +mmap2(0x2e65000, 146848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2e65000 +mmap2(0x2e88000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x23) = 0x2e88000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libresolv.so.2", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libresolv.so.2", O_RDONLY)   = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\26\250\0024\0\0\0\244"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=102252, ...}) = 0 +mmap2(0x2a7f000, 100424, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2a7f000 +mprotect(0x2a93000, 4096, PROT_NONE)    = 0 +mmap2(0x2a94000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x14) = 0x2a94000 +mmap2(0x2a96000, 6216, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2a96000 +close(3)                                = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8023000 +open("/home/lennart/projects/pulseaudio/src/.libs/libxcb.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libxcb.so.1", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\16l\0004\0\0\0|"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=114220, ...}) = 0 +mmap2(0x6ba000, 111372, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x259000 +mmap2(0x274000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b) = 0x274000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libuuid.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libuuid.so.1", O_RDONLY)     = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000_\337\0004\0\0\0\370"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=15864, ...}) = 0 +mmap2(0xdf5000, 12820, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xdf5000 +mmap2(0xdf8000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0xdf8000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libXext.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libXext.so.6", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200'r\0004\0\0\0\250"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=66136, ...}) = 0 +mmap2(0x720000, 63488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x720000 +mmap2(0x72f000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xf) = 0x72f000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libnsl.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libnsl.so.1", O_RDONLY)      = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260A$\0034\0\0\0\370"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=117912, ...}) = 0 +mmap2(0x3241000, 108520, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3241000 +mmap2(0x3258000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16) = 0x3258000 +mmap2(0x325a000, 6120, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x325a000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libFLAC.so.8", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libFLAC.so.8", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200\225\254\0024\0\0\0\370"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=244392, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8022000 +mmap2(0x2ac0000, 245576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2ac0000 +mmap2(0x2afb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3a) = 0x2afb000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libogg.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libogg.so.0", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\"a\0034\0\0\0l"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=20724, ...}) = 0 +mmap2(0x3611000, 22052, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3611000 +mmap2(0x3616000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4) = 0x3616000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libselinux.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libselinux.so.1", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\261H\0004\0\0\0P"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=118312, ...}) = 0 +mmap2(0x487000, 121836, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x487000 +mmap2(0x4a3000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b) = 0x4a3000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libexpat.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libexpat.so.1", O_RDONLY)    = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@@s\0004\0\0\0\250"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=156720, ...}) = 0 +mmap2(0x732000, 158048, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2a9000 +mmap2(0x2ce000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x24) = 0x2ce000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libattr.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libattr.so.1", O_RDONLY)     = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\36\221\0004\0\0\0\344"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=17812, ...}) = 0 +mmap2(0x911000, 19048, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x911000 +mmap2(0x915000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3) = 0x915000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libkrb5support.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libkrb5support.so.0", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0X\302\0024\0\0\0\374"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=38868, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8021000 +mmap2(0x2c24000, 39916, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2fc000 +mmap2(0x305000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8) = 0x305000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libkeyutils.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/libkeyutils.so.1", O_RDONLY) = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0`\230\313\0024\0\0\0\240"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=7976, ...}) = 0 +mmap2(0x2cb9000, 9260, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x2cb9000 +mmap2(0x2cbb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x2cbb000 +close(3)                                = 0 +open("/home/lennart/projects/pulseaudio/src/.libs/libXau.so.6", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/libXau.so.6", O_RDONLY)  = 3 +read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\211m\0004\0\0\0000"..., 512) = 512 +fstat64(3, {st_mode=S_IFREG|0755, st_size=9656, ...}) = 0 +mmap2(0x6d8000, 10992, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x6d8000 +mmap2(0x6da000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0x6da000 +close(3)                                = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8020000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb801f000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb801e000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb801d000 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb801c000 +set_thread_area({entry_number:-1 -> 6, base_addr:0xb801c9a0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 +mprotect(0x4a3000, 4096, PROT_READ)     = 0 +mprotect(0x3258000, 4096, PROT_READ)    = 0 +mprotect(0x2a94000, 4096, PROT_READ)    = 0 +mprotect(0x754b000, 8192, PROT_READ)    = 0 +mprotect(0x2fa000, 4096, PROT_READ)     = 0 +mprotect(0x446000, 4096, PROT_READ)     = 0 +mprotect(0x483000, 4096, PROT_READ)     = 0 +mprotect(0x2989000, 4096, PROT_READ)    = 0 +mprotect(0x192000, 4096, PROT_READ)     = 0 +mprotect(0x29f000, 4096, PROT_READ)     = 0 +munmap(0xb8033000, 123199)              = 0 +set_tid_address(0xb801ca08)             = 7010 +set_robust_list(0xb801ca10, 0xc)        = 0 +futex(0xbff5c990, FUTEX_WAKE_PRIVATE, 1) = 0 +futex(0xbff5c990, 0x189 /* FUTEX_??? */, 1, NULL, bff5c9a0) = -1 EAGAIN (Resource temporarily unavailable) +rt_sigaction(SIGRTMIN, {0x17f340, [], SA_SIGINFO}, NULL, 8) = 0 +rt_sigaction(SIGRT_1, {0x17f840, [], SA_RESTART|SA_SIGINFO}, NULL, 8) = 0 +rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0 +getrlimit(RLIMIT_STACK, {rlim_cur=10240*1024, rlim_max=RLIM_INFINITY}) = 0 +uname({sys="Linux", node="lambda", ...}) = 0 +brk(0)                                  = 0x99a3000 +brk(0x99c4000)                          = 0x99c4000 +open("/etc/selinux/config", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=511, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8051000 +read(3, "# This file controls the state of"..., 4096) = 511 +read(3, ""..., 4096)                    = 0 +close(3)                                = 0 +munmap(0xb8051000, 4096)                = 0 +statfs64("/selinux", 84, {f_type="EXT2_SUPER_MAGIC", f_bsize=4096, f_blocks=253926, f_bfree=124690, f_bavail=111583, f_files=262144, f_ffree=247677, f_fsid={-1122384253, -1786938661}, f_namelen=255, f_frsize=4096}) = 0 +open("/proc/mounts", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8051000 +read(3, "rootfs / rootfs rw 0 0\n/dev/root "..., 1024) = 1024 +read(3, " rw,relatime,grpid,errors=continu"..., 1024) = 347 +read(3, ""..., 1024)                    = 0 +close(3)                                = 0 +munmap(0xb8051000, 4096)                = 0 +open("/usr/lib/gconv/gconv-modules.cache", O_RDONLY) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=26048, ...}) = 0 +mmap2(NULL, 26048, PROT_READ, MAP_SHARED, 3, 0) = 0xb804b000 +close(3)                                = 0 +futex(0x754da6c, FUTEX_WAKE_PRIVATE, 2147483647) = 0 +getuid32()                              = 1000 +geteuid32()                             = 1000 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capset(0x20080522, 0, {CAP_SYS_NICE, CAP_SYS_NICE, 0}) = -1 EPERM (Operation not permitted) +prctl(0x8, 0, 0, 0, 0)                  = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capset(0x20080522, 0, {0, 0, 0})        = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +prctl(0x8, 0x1, 0, 0, 0)                = 0 +getuid32()                              = 1000 +geteuid32()                             = 1000 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capset(0x20080522, 0, {CAP_SYS_NICE, CAP_SYS_NICE, 0}) = -1 EPERM (Operation not permitted) +prctl(0x8, 0, 0, 0, 0)                  = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capset(0x20080522, 0, {0, 0, 0})        = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +prctl(0x8, 0x1, 0, 0, 0)                = 0 +open("/proc/self/fd", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3 +fcntl64(3, F_GETFD)                     = 0x1 (flags FD_CLOEXEC) +getdents64(3, /* 6 entries */, 32768)   = 144 +getdents64(3, /* 0 entries */, 32768)   = 0 +close(3)                                = 0 +rt_sigaction(SIGHUP, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGINT, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGILL, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGTRAP, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGABRT, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGBUS, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGFPE, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGUSR1, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGSEGV, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGUSR2, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGPIPE, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGALRM, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGTERM, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGSTKFLT, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGCHLD, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGCONT, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGURG, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGXCPU, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGXFSZ, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGVTALRM, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGPROF, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGWINCH, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGIO, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGPWR, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGSYS, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_2, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_3, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_4, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_5, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_6, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_7, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_8, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_9, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_10, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_11, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_12, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_13, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_14, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_15, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_16, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_17, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_18, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_19, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_20, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_21, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_22, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_23, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_24, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_25, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_26, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_27, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_28, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_29, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_30, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_31, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGRT_32, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=84438064, ...}) = 0 +mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7e1c000 +close(3)                                = 0 +open("/home/lennart/.pulse//daemon.conf", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) +open("/etc/pulse/daemon.conf", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=2149, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb804a000 +read(3, "# This file is part of PulseAudio"..., 4096) = 2149 +read(3, ""..., 4096)                    = 0 +close(3)                                = 0 +munmap(0xb804a000, 4096)                = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Started as real root: "..., 51) = 51 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +setrlimit(RLIMIT_NOFILE, {rlim_cur=256, rlim_max=256}) = 0 +setrlimit(RLIMIT_NICE, {rlim_cur=31, rlim_max=31}) = -1 EPERM (Operation not permitted) +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: setrlimit(RLIMIT_NICE,"..., 76) = 76 +setrlimit(RLIMIT_RTPRIO, {rlim_cur=9, rlim_max=9}) = -1 EPERM (Operation not permitted) +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: setrlimit(RLIMIT_RTPRI"..., 76) = 76 +setrlimit(0xf /* RLIMIT_??? */, {rlim_cur=1000000, rlim_max=1000000}) = 0 +geteuid32()                             = 1000 +getrlimit(RLIMIT_NICE, {rlim_cur=0, rlim_max=0}) = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: High-priority scheduli"..., 88) = 88 +geteuid32()                             = 1000 +getrlimit(RLIMIT_NICE, {rlim_cur=0, rlim_max=0}) = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +geteuid32()                             = 1000 +getrlimit(RLIMIT_RTPRIO, {rlim_cur=0, rlim_max=0}) = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Can realtime: no, can "..., 51) = 51 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +geteuid32()                             = 1000 +getrlimit(RLIMIT_NICE, {rlim_cur=0, rlim_max=0}) = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +geteuid32()                             = 1000 +getrlimit(RLIMIT_RTPRIO, {rlim_cur=0, rlim_max=0}) = 0 +capget(0x20080522, 0, NULL)             = -1 EFAULT (Bad address) +capget(0x20080522, 0, {0, 0, 0})        = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Can realtime: no, can "..., 51) = 51 +open("/dev/urandom", O_RDONLY|O_NOCTTY|O_LARGEFILE) = 3 +read(3, "\201\310\241\2"..., 4)         = 4 +close(3)                                = 0 +chdir("/")                              = 0 +umask(022)                              = 02 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: This is PulseAudio 0.9"..., 53) = 53 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Compilation host: i686"..., 47) = 47 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Compilation CFLAGS: -g"..., 616) = 616 +uname({sys="Linux", node="lambda", ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Running on host: Linux"..., 102) = 102 +open("/sys/devices/system/cpu", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3 +getdents64(3, /* 10 entries */, 32768)  = 288 +getdents64(3, /* 0 entries */, 32768)   = 0 +close(3)                                = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Found 2 CPUs.\n"..., 25) = 25 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Page size is 4096 byte"..., 35) = 35 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Compiled with Valgrind"..., 47) = 47 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Running in valgrind mo"..., 40) = 40 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Optimized build: no\n"..., 31) = 31 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: All asserts enabled.\n"..., 32) = 32 +open("/var/lib/dbus/machine-id", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=33, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb804a000 +read(3, "92b15d59fc15e56787599a0046a5564a\n"..., 4096) = 33 +close(3)                                = 0 +munmap(0xb804a000, 4096)                = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Machine ID is 92b15d59"..., 59) = 59 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Session ID is 92b15d59"..., 88) = 88 +stat64("/home/lennart", {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0 +getuid32()                              = 1000 +umask(077)                              = 022 +mkdir("/home/lennart/.pulse", 0700)     = -1 EEXIST (File exists) +umask(022)                              = 077 +getuid32()                              = 1000 +getgid32()                              = 1027 +chown32("/home/lennart/.pulse", 1000, 1027) = 0 +chmod("/home/lennart/.pulse", 0700)     = 0 +lstat64("/home/lennart/.pulse", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +open("/var/lib/dbus/machine-id", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=33, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb804a000 +read(3, "92b15d59fc15e56787599a0046a5564a\n"..., 4096) = 33 +close(3)                                = 0 +munmap(0xb804a000, 4096)                = 0 +readlink("/home/lennart/.pulse/92b15d59fc15e56787599a0046a5564a:runtime", "/tmp/pulse-GbKmmSips9OW"..., 99) = 23 +lstat64("/tmp/pulse-GbKmmSips9OW", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +getuid32()                              = 1000 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Using runtime director"..., 98) = 98 +stat64("/home/lennart", {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0 +getuid32()                              = 1000 +umask(077)                              = 022 +mkdir("/home/lennart/.pulse", 0700)     = -1 EEXIST (File exists) +umask(022)                              = 077 +getuid32()                              = 1000 +getgid32()                              = 1027 +chown32("/home/lennart/.pulse", 1000, 1027) = 0 +chmod("/home/lennart/.pulse", 0700)     = 0 +lstat64("/home/lennart/.pulse", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Using state directory "..., 55) = 55 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Running in system mode"..., 38) = 38 +stat64("/home/lennart", {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0 +getuid32()                              = 1000 +umask(077)                              = 022 +mkdir("/home/lennart/.pulse", 0700)     = -1 EEXIST (File exists) +umask(022)                              = 077 +getuid32()                              = 1000 +getgid32()                              = 1027 +chown32("/home/lennart/.pulse", 1000, 1027) = 0 +chmod("/home/lennart/.pulse", 0700)     = 0 +lstat64("/home/lennart/.pulse", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +open("/var/lib/dbus/machine-id", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=33, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb804a000 +read(3, "92b15d59fc15e56787599a0046a5564a\n"..., 4096) = 33 +close(3)                                = 0 +munmap(0xb804a000, 4096)                = 0 +readlink("/home/lennart/.pulse/92b15d59fc15e56787599a0046a5564a:runtime", "/tmp/pulse-GbKmmSips9OW"..., 99) = 23 +lstat64("/tmp/pulse-GbKmmSips9OW", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +getuid32()                              = 1000 +open("/home/lennart/.pulse/92b15d59fc15e56787599a0046a5564a:runtime/pid", O_RDWR|O_CREAT|O_NOCTTY|O_LARGEFILE|O_NOFOLLOW, 0600) = 3 +fcntl64(3, F_SETLKW64, {type=F_WRLCK, whence=SEEK_SET, start=0, len=0}, 0xbff5c798) = 0 +fstat64(3, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0 +read(3, ""..., 19)                      = 0 +_llseek(3, 0, [0], SEEK_SET)            = 0 +ftruncate64(3, 0)                       = 0 +send(3, "7010\n"..., 5, MSG_NOSIGNAL)   = -1 ENOTSOCK (Socket operation on non-socket) +write(3, "7010\n"..., 5)                = 5 +fcntl64(3, F_SETLKW64, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}, 0xbff5c848) = 0 +close(3)                                = 0 +rt_sigaction(SIGPIPE, NULL, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGPIPE, {0x1, [], 0}, NULL, 8) = 0 +clock_getres(CLOCK_MONOTONIC, {0, 1})   = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Fresh high-resolution "..., 64) = 64 +prctl(0x1e, 0, 0, 0, 0)                 = 50000 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: rtclock.c: Timer slack is set "..., 43) = 43 +rt_sigprocmask(SIG_BLOCK, ~[HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH IO PWR SYS RTMIN RT_1], NULL, 8) = 0 +rt_sigaction(SIGBUS, {0x37dff6, [], SA_RESTART|SA_SIGINFO}, NULL, 8) = 0 +pipe([3, 4])                            = 0 +fcntl64(3, F_GETFL)                     = 0 (flags O_RDONLY) +fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0 +fcntl64(4, F_GETFL)                     = 0x1 (flags O_WRONLY) +fcntl64(4, F_SETFL, O_WRONLY|O_NONBLOCK) = 0 +fcntl64(3, F_GETFD)                     = 0 +fcntl64(3, F_SETFD, FD_CLOEXEC)         = 0 +fcntl64(4, F_GETFD)                     = 0 +fcntl64(4, F_SETFD, FD_CLOEXEC)         = 0 +open("/dev/shm/", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 5 +getdents64(5, /* 6 entries */, 32768)   = 208 +statfs("/dev/shm/", {f_type=0x1021994, f_bsize=4096, f_blocks=256729, f_bfree=256355, f_bavail=256355, f_files=218849, f_ffree=218844, f_fsid={0, 0}, f_namelen=255, f_frsize=4096}) = 0 +futex(0x4841e0, FUTEX_WAKE_PRIVATE, 2147483647) = 0 +open("/dev/shm/pulse-shm-1732310693", O_RDONLY|O_NOFOLLOW|O_CLOEXEC) = 6 +fcntl64(6, F_GETFD)                     = 0x1 (flags FD_CLOEXEC) +fstat64(6, {st_mode=S_IFREG|0400, st_size=67108904, ...}) = 0 +mmap2(NULL, 67112960, PROT_READ, MAP_SHARED, 6, 0) = 0xb3e1b000 +close(6)                                = 0 +kill(2814, SIG_0)                       = 0 +munmap(0xb3e1b000, 67112960)            = 0 +open("/dev/shm/pulse-shm-1285363868", O_RDONLY|O_NOFOLLOW|O_CLOEXEC) = 6 +fstat64(6, {st_mode=S_IFREG|0400, st_size=67108904, ...}) = 0 +mmap2(NULL, 67112960, PROT_READ, MAP_SHARED, 6, 0) = 0xb3e1b000 +close(6)                                = 0 +kill(2757, SIG_0)                       = 0 +munmap(0xb3e1b000, 67112960)            = 0 +open("/dev/shm/pulse-shm-2271110333", O_RDONLY|O_NOFOLLOW|O_CLOEXEC) = 6 +fstat64(6, {st_mode=S_IFREG|0400, st_size=67108904, ...}) = 0 +mmap2(NULL, 67112960, PROT_READ, MAP_SHARED, 6, 0) = 0xb3e1b000 +close(6)                                = 0 +kill(2807, SIG_0)                       = 0 +munmap(0xb3e1b000, 67112960)            = 0 +open("/dev/shm/pulse-shm-4204834562", O_RDONLY|O_NOFOLLOW|O_CLOEXEC) = 6 +fstat64(6, {st_mode=S_IFREG|0400, st_size=67108904, ...}) = 0 +mmap2(NULL, 67112960, PROT_READ, MAP_SHARED, 6, 0) = 0xb3e1b000 +close(6)                                = 0 +kill(2748, SIG_0)                       = 0 +munmap(0xb3e1b000, 67112960)            = 0 +getdents64(5, /* 0 entries */, 32768)   = 0 +close(5)                                = 0 +open("/dev/urandom", O_RDONLY|O_NOCTTY|O_LARGEFILE) = 5 +read(5, "\255\317\375\343"..., 4)       = 4 +close(5)                                = 0 +open("/dev/shm/pulse-shm-3825061805", O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0400) = 5 +ftruncate64(5, 67108904)                = 0 +mmap2(NULL, 67112960, PROT_READ|PROT_WRITE, MAP_SHARED, 5, 0) = 0xb3e1b000 +close(5)                                = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: memblock.c: Using shared memor"..., 137) = 137 +open("/dev/urandom", O_RDONLY|O_NOCTTY|O_LARGEFILE) = 5 +read(5, "\202\2262\32"..., 4)           = 4 +close(5)                                = 0 +rt_sigprocmask(SIG_SETMASK, NULL, ~[HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH IO PWR SYS RTMIN RT_1], 8) = 0 +rt_sigaction(SIGPIPE, NULL, {0x1, [], 0}, 8) = 0 +pipe([5, 6])                            = 0 +fcntl64(5, F_GETFL)                     = 0 (flags O_RDONLY) +fcntl64(5, F_SETFL, O_RDONLY|O_NONBLOCK) = 0 +fcntl64(6, F_GETFL)                     = 0x1 (flags O_WRONLY) +fcntl64(6, F_SETFL, O_WRONLY|O_NONBLOCK) = 0 +fcntl64(5, F_GETFD)                     = 0 +fcntl64(5, F_SETFD, FD_CLOEXEC)         = 0 +fcntl64(6, F_GETFD)                     = 0 +fcntl64(6, F_SETFD, FD_CLOEXEC)         = 0 +rt_sigaction(SIGINT, {0x1db9e3, [], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGTERM, {0x1db9e3, [], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGUSR1, {0x1db9e3, [], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGUSR2, {0x1db9e3, [], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGHUP, {0x1db9e3, [], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 +time(NULL)                              = 1243956680 +rt_sigaction(SIGILL, {0x86eb20, [], 0}, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGILL, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGILL, {0x86eb20, [], 0}, {SIG_DFL, [], 0}, 8) = 0 +rt_sigaction(SIGILL, {SIG_DFL, [], 0}, NULL, 8) = 0 +clock_gettime(CLOCK_MONOTONIC, {5390, 142713364}) = 0 +pipe([7, 8])                            = 0 +fcntl64(7, F_GETFL)                     = 0 (flags O_RDONLY) +fcntl64(7, F_SETFL, O_RDONLY|O_NONBLOCK) = 0 +fcntl64(8, F_GETFL)                     = 0x1 (flags O_WRONLY) +fcntl64(8, F_SETFL, O_WRONLY|O_NONBLOCK) = 0 +fcntl64(7, F_GETFD)                     = 0 +fcntl64(7, F_SETFD, FD_CLOEXEC)         = 0 +fcntl64(8, F_GETFD)                     = 0 +fcntl64(8, F_SETFD, FD_CLOEXEC)         = 0 +rt_sigaction(SIGXCPU, {0x805a7e7, [], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0 +getrusage(RUSAGE_SELF, {ru_utime={0, 66989}, ru_stime={0, 20996}, ...}) = 0 +getrlimit(RLIMIT_CPU, {rlim_cur=RLIM_INFINITY, rlim_max=RLIM_INFINITY}) = 0 +setrlimit(RLIMIT_CPU, {rlim_cur=10, rlim_max=RLIM_INFINITY}) = 0 +open("/home/lennart/.pulse//default.pa", O_RDONLY|O_LARGEFILE) = 9 +fstat64(9, {st_mode=S_IFREG|0664, st_size=31, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb804a000 +read(9, "load-module module-udev-detect\n"..., 4096) = 31 +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/module-udev-detect.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib64/module-udev-detect.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib64/module-udev-detect.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/module-udev-detect.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/usr/lib/module-udev-detect.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("module-udev-detect.la", O_RDONLY) = -1 ENOENT (No such file or directory) +access("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/module-udev-detect.so", R_OK) = -1 ENOENT (No such file or directory) +access("/lib64/module-udev-detect.so", R_OK) = -1 ENOENT (No such file or directory) +access("/usr/lib64/module-udev-detect.so", R_OK) = -1 ENOENT (No such file or directory) +access("/lib/module-udev-detect.so", R_OK) = -1 ENOENT (No such file or directory) +access("/usr/lib/module-udev-detect.so", R_OK) = -1 ENOENT (No such file or directory) +futex(0x447068, FUTEX_WAKE_PRIVATE, 2147483647) = 0 +open("/etc/udev/udev.conf", O_RDONLY|O_LARGEFILE) = 10 +fstat64(10, {st_mode=S_IFREG|0644, st_size=218, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8049000 +read(10, "# The initial syslog(3) priority:"..., 4096) = 218 +read(10, ""..., 4096)                   = 0 +close(10)                               = 0 +munmap(0xb8049000, 4096)                = 0 +stat64("/tmp/udevtest", 0xbff5c270)     = -1 ENOENT (No such file or directory) +socket(PF_FILE, SOCK_DGRAM, 0)          = 10 +fcntl64(10, F_GETFD)                    = 0 +fcntl64(10, F_SETFD, FD_CLOEXEC)        = 0 +bind(10, {sa_family=AF_FILE, path=@"/tmp/udevtest"...}, 16) = 0 +setsockopt(10, SOL_SOCKET, SO_PASSCRED, [1], 4) = 0 +stat64("/sys/subsystem", 0xbff5be6c)    = -1 ENOENT (No such file or directory) +open("/sys/bus", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 11 +getdents64(11, /* 19 entries */, 32768) = 512 +getdents64(11, /* 0 entries */, 32768)  = 0 +close(11)                               = 0 +open("/sys/class", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 11 +getdents64(11, /* 44 entries */, 32768) = 1296 +open("/sys/class/sound", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 12 +getdents64(12, /* 9 entries */, 32768)  = 272 +lstat64("/sys/class/sound/timer", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/timer", "../../devices/virtual/sound/timer"..., 1024) = 33 +stat64("/sys/devices/virtual/sound/timer/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +lstat64("/sys/class/sound/card0", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/card0", "../../devices/pci0000:00/0000:00:1b.0/sound/card0"..., 1024) = 49 +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +lstat64("/sys/class/sound/pcmC0D1p", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/pcmC0D1p", "../../devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D1p"..., 1024) = 58 +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D1p/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +lstat64("/sys/class/sound/pcmC0D0p", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/pcmC0D0p", "../../devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0p"..., 1024) = 58 +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0p/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +lstat64("/sys/class/sound/pcmC0D0c", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/pcmC0D0c", "../../devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0c"..., 1024) = 58 +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0c/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +lstat64("/sys/class/sound/hwC0D0", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/hwC0D0", "../../devices/pci0000:00/0000:00:1b.0/sound/card0/hwC0D0"..., 1024) = 56 +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/hwC0D0/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +lstat64("/sys/class/sound/controlC0", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0 +readlink("/sys/class/sound/controlC0", "../../devices/pci0000:00/0000:00:1b.0/sound/card0/controlC0"..., 1024) = 59 +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/controlC0/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +getdents64(12, /* 0 entries */, 32768)  = 0 +close(12)                               = 0 +getdents64(11, /* 0 entries */, 32768)  = 0 +close(11)                               = 0 +stat64("/sys/class/block", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 85) = 85 +readlink("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 95) = 95 +readlink("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/controlC0", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/controlC0/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 92) = 92 +readlink("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/hwC0D0", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/hwC0D0/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 94) = 94 +readlink("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0c", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0c/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 94) = 94 +readlink("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0p", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D0p/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 94) = 94 +readlink("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D1p", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/pci0000:00/0000:00:1b.0/sound/card0/pcmC0D1p/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31m/"..., 69) = 69 +readlink("/sys/devices/virtual/sound/timer", 0xbff5b1bc, 1024) = -1 EINVAL (Invalid argument) +stat64("/sys/devices/virtual/sound/timer/uevent", {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ms"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "E: module-udev-detect.c: \33[1;31ma"..., 51) = 51 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: module.c: Loaded \"module-udev-"..., 68) = 68 +read(9, ""..., 4096)                    = 0 +close(9)                                = 0 +munmap(0xb804a000, 4096)                = 0 +open("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/module-cli.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib64/module-cli.la", O_RDONLY)  = -1 ENOENT (No such file or directory) +open("/usr/lib64/module-cli.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("/lib/module-cli.la", O_RDONLY)    = -1 ENOENT (No such file or directory) +open("/usr/lib/module-cli.la", O_RDONLY) = -1 ENOENT (No such file or directory) +open("module-cli.la", O_RDONLY)         = -1 ENOENT (No such file or directory) +access("/usr/local/stow/pulseaudio-0.9.15.143-8bada-dirty/lib/pulse-0.9.15/modules/module-cli.so", R_OK) = -1 ENOENT (No such file or directory) +access("/lib64/module-cli.so", R_OK)    = -1 ENOENT (No such file or directory) +access("/usr/lib64/module-cli.so", R_OK) = -1 ENOENT (No such file or directory) +access("/lib/module-cli.so", R_OK)      = -1 ENOENT (No such file or directory) +access("/usr/lib/module-cli.so", R_OK)  = -1 ENOENT (No such file or directory) +fcntl64(0, F_GETFL)                     = 0x2 (flags O_RDWR) +fcntl64(0, F_SETFL, O_RDWR|O_NONBLOCK)  = 0 +fcntl64(1, F_GETFL)                     = 0x802 (flags O_RDWR|O_NONBLOCK) +fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: client.c: Created 0 \"STDIN/STD"..., 45) = 45 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: module.c: Loaded \"module-cli\" "..., 73) = 73 +socket(PF_FILE, SOCK_STREAM, 0)         = 9 +connect(9, {sa_family=AF_FILE, path=@"/tmp/dbus-UQhE4oRaqn"...}, 23) = 0 +fcntl64(9, F_GETFL)                     = 0x2 (flags O_RDWR) +fcntl64(9, F_SETFL, O_RDWR|O_NONBLOCK)  = 0 +fcntl64(9, F_GETFD)                     = 0 +fcntl64(9, F_SETFD, FD_CLOEXEC)         = 0 +geteuid32()                             = 1000 +rt_sigaction(SIGPIPE, {0x1, [PIPE], SA_RESTART}, {0x1, [], 0}, 8) = 0 +poll([{fd=9, events=POLLOUT}], 1, 0)    = 1 ([{fd=9, revents=POLLOUT}]) +write(9, "\0"..., 1)                    = 1 +write(9, "AUTH EXTERNAL 31303030\r\n"..., 24) = 24 +poll([{fd=9, events=POLLIN}], 1, -1)    = 1 ([{fd=9, revents=POLLIN}]) +read(9, "OK 2daf0128eb4aa35798dec3624a2531"..., 2048) = 37 +poll([{fd=9, events=POLLOUT}], 1, -1)   = 1 ([{fd=9, revents=POLLOUT}]) +write(9, "BEGIN\r\n"..., 7)             = 7 +poll([{fd=9, events=POLLIN|POLLOUT}], 1, -1) = 1 ([{fd=9, revents=POLLOUT}]) +writev(9, [{"l\1\0\1\0\0\0\0\1\0\0\0n\0\0\0\1\1o\0\25\0\0\0/org/free"..., 128}, {""..., 0}], 2) = 128 +gettimeofday({1243956680, 481709}, NULL) = 0 +poll([{fd=9, events=POLLIN}], 1, 25000) = 1 ([{fd=9, revents=POLLIN}]) +read(9, "l\2\1\1\n\0\0\0\1\0\0\0=\0\0\0\6\1s\0\5\0\0\0:1.76\0\0\0\5"..., 2048) = 260 +read(9, 0x99b45c0, 2048)                = -1 EAGAIN (Resource temporarily unavailable) +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: dbus-util.c: Successfully conn"..., 102) = 102 +gettimeofday({1243956680, 482580}, NULL) = 0 +writev(9, [{"l\1\0\1 \0\0\0\2\0\0\0\200\0\0\0\1\1o\0\25\0\0\0/org/free"..., 144}, {"\25\0\0\0org.pulseaudio.Server\0\0\0\4\0\0\0"..., 32}], 2) = 176 +gettimeofday({1243956680, 484257}, NULL) = 0 +poll([{fd=9, events=POLLIN}], 1, 25000) = 1 ([{fd=9, revents=POLLIN}]) +read(9, "l\4\1\1\32\0\0\0\3\0\0\0\215\0\0\0\1\1o\0\25\0\0\0/org/free"..., 2048) = 270 +read(9, 0x99b45c0, 2048)                = -1 EAGAIN (Resource temporarily unavailable) +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "D: main.c: Got org.pulseaudio.Ser"..., 38) = 38 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Daemon startup complet"..., 36) = 36 +poll([{fd=3, events=POLLIN}, {fd=9, events=POLLIN|POLLERR|POLLHUP}, {fd=9, events=0}, {fd=1, events=POLLOUT}, {fd=0, events=POLLIN}, {fd=7, events=POLLIN}, {fd=5, events=POLLIN}], 7, -1) = 1 ([{fd=1, revents=POLLOUT}]) +send(1, "Welcome to PulseAudio! Use \"help\""..., 61, MSG_NOSIGNAL) = -1 ENOTSOCK (Socket operation on non-socket) +write(1, "Welcome to PulseAudio! Use \"help\""..., 61) = 61 +poll([{fd=3, events=POLLIN}, {fd=9, events=POLLIN|POLLERR|POLLHUP}, {fd=9, events=0}, {fd=1, events=POLLOUT}, {fd=0, events=POLLIN}, {fd=7, events=POLLIN}, {fd=5, events=POLLIN}], 7, -1) = 1 ([{fd=1, revents=POLLOUT}]) +poll([{fd=3, events=POLLIN}, {fd=9, events=POLLIN|POLLERR|POLLHUP}, {fd=9, events=0}, {fd=1, events=0}, {fd=0, events=POLLIN}, {fd=7, events=POLLIN}, {fd=5, events=POLLIN}], 7, -1) = ? ERESTART_RESTARTBLOCK (To be restarted) +--- SIGINT (Interrupt) @ 0 (0) --- +send(6, "\2\0\0\0"..., 4, MSG_NOSIGNAL) = -1 ENOTSOCK (Socket operation on non-socket) +write(6, "\2\0\0\0"..., 4)              = 4 +sigreturn()                             = ? (mask now []) +poll([{fd=3, events=POLLIN}, {fd=9, events=POLLIN|POLLERR|POLLHUP}, {fd=9, events=0}, {fd=1, events=0}, {fd=0, events=POLLIN}, {fd=7, events=POLLIN}, {fd=5, events=POLLIN}], 7, -1) = 1 ([{fd=5, revents=POLLIN}]) +read(5, "\2\0\0\0"..., 4)               = 4 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Got signal SIGINT.\n"..., 30) = 30 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Exiting.\n"..., 20) = 20 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Daemon shutdown initia"..., 38) = 38 +close(9)                                = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: module.c: Unloading \"module-ud"..., 57) = 57 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: module.c: Unloaded \"module-ude"..., 56) = 56 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: module.c: Unloading \"module-cl"..., 49) = 49 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: client.c: Freed 0 \"STDIN/STDOU"..., 43) = 43 +gettimeofday({1243956681, 236441}, NULL) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: module.c: Unloaded \"module-cli"..., 48) = 48 +munmap(0xb3e1b000, 67112960)            = 0 +unlink("/dev/shm/pulse-shm-3825061805") = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +write(2, "I: main.c: Daemon terminated.\n"..., 30) = 30 +close(7)                                = 0 +close(8)                                = 0 +rt_sigaction(SIGXCPU, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGHUP, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGUSR2, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGUSR1, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGTERM, {SIG_DFL, [], 0}, NULL, 8) = 0 +rt_sigaction(SIGINT, {SIG_DFL, [], 0}, NULL, 8) = 0 +close(5)                                = 0 +close(6)                                = 0 +close(3)                                = 0 +close(4)                                = 0 +stat64("/home/lennart", {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0 +getuid32()                              = 1000 +umask(077)                              = 022 +mkdir("/home/lennart/.pulse", 0700)     = -1 EEXIST (File exists) +umask(022)                              = 077 +getuid32()                              = 1000 +getgid32()                              = 1027 +chown32("/home/lennart/.pulse", 1000, 1027) = 0 +chmod("/home/lennart/.pulse", 0700)     = 0 +lstat64("/home/lennart/.pulse", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +open("/var/lib/dbus/machine-id", O_RDONLY|O_LARGEFILE) = 3 +fstat64(3, {st_mode=S_IFREG|0644, st_size=33, ...}) = 0 +mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb804a000 +read(3, "92b15d59fc15e56787599a0046a5564a\n"..., 4096) = 33 +close(3)                                = 0 +munmap(0xb804a000, 4096)                = 0 +readlink("/home/lennart/.pulse/92b15d59fc15e56787599a0046a5564a:runtime", "/tmp/pulse-GbKmmSips9OW"..., 99) = 23 +lstat64("/tmp/pulse-GbKmmSips9OW", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0 +getuid32()                              = 1000 +open("/home/lennart/.pulse/92b15d59fc15e56787599a0046a5564a:runtime/pid", O_RDWR|O_NOCTTY|O_LARGEFILE|O_NOFOLLOW) = 3 +fcntl64(3, F_SETLKW64, {type=F_WRLCK, whence=SEEK_SET, start=0, len=0}, 0xbff5c7b8) = 0 +fstat64(3, {st_mode=S_IFREG|0600, st_size=5, ...}) = 0 +read(3, "7010\n"..., 19)                = 5 +read(3, ""..., 14)                      = 0 +ftruncate64(3, 0)                       = 0 +unlink("/home/lennart/.pulse/92b15d59fc15e56787599a0046a5564a:runtime/pid") = 0 +fcntl64(3, F_SETLKW64, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}, 0xbff5c868) = 0 +close(3)                                = 0 +exit_group(1)                           = ? diff --git a/src/map-file b/src/map-file index d9496593..a1d0a061 100644 --- a/src/map-file +++ b/src/map-file @@ -15,17 +15,20 @@ pa_channel_map_can_balance;  pa_channel_map_can_fade;  pa_channel_map_compatible;  pa_channel_map_equal; +pa_channel_map_has_position;  pa_channel_map_init;  pa_channel_map_init_auto;  pa_channel_map_init_extend;  pa_channel_map_init_mono;  pa_channel_map_init_stereo; +pa_channel_map_mask;  pa_channel_map_parse;  pa_channel_map_snprint;  pa_channel_map_superset;  pa_channel_map_to_name;  pa_channel_map_to_pretty_name;  pa_channel_map_valid; +pa_channel_position_from_string;  pa_channel_position_to_pretty_string;  pa_channel_position_to_string;  pa_context_add_autoload; @@ -83,6 +86,8 @@ pa_context_ref;  pa_context_remove_autoload_by_index;  pa_context_remove_autoload_by_name;  pa_context_remove_sample; +pa_context_rttime_new; +pa_context_rttime_restart;  pa_context_set_card_profile_by_index;  pa_context_set_card_profile_by_name;  pa_context_set_default_sink; @@ -93,10 +98,14 @@ pa_context_set_sink_input_mute;  pa_context_set_sink_input_volume;  pa_context_set_sink_mute_by_index;  pa_context_set_sink_mute_by_name; +pa_context_set_sink_port_by_index; +pa_context_set_sink_port_by_name;  pa_context_set_sink_volume_by_index;  pa_context_set_sink_volume_by_name;  pa_context_set_source_mute_by_index;  pa_context_set_source_mute_by_name; +pa_context_set_source_port_by_index; +pa_context_set_source_port_by_name;  pa_context_set_source_volume_by_index;  pa_context_set_source_volume_by_name;  pa_context_set_state_callback; @@ -110,19 +119,24 @@ pa_context_suspend_source_by_name;  pa_context_unload_module;  pa_context_unref;  pa_cvolume_avg; +pa_cvolume_avg_mask;  pa_cvolume_channels_equal_to;  pa_cvolume_compatible;  pa_cvolume_compatible_with_channel_map;  pa_cvolume_equal;  pa_cvolume_get_balance;  pa_cvolume_get_fade; +pa_cvolume_get_position;  pa_cvolume_init;  pa_cvolume_max; +pa_cvolume_max_mask;  pa_cvolume_remap;  pa_cvolume_scale; +pa_cvolume_scale_mask;  pa_cvolume_set;  pa_cvolume_set_balance;  pa_cvolume_set_fade; +pa_cvolume_set_position;  pa_cvolume_snprint;  pa_cvolume_valid;  pa_ext_stream_restore_delete; @@ -175,6 +189,7 @@ pa_proplist_iterate;  pa_proplist_new;  pa_proplist_set;  pa_proplist_setf; +pa_proplist_setp;  pa_proplist_sets;  pa_proplist_size;  pa_proplist_to_string; @@ -182,6 +197,9 @@ pa_proplist_to_string_sep;  pa_proplist_unset;  pa_proplist_unset_many;  pa_proplist_update; +pa_rtclock_now; +pa_sample_format_is_be; +pa_sample_format_is_le;  pa_sample_format_to_string;  pa_sample_size;  pa_sample_size_of_format; @@ -233,6 +251,7 @@ pa_stream_proplist_update;  pa_stream_readable_size;  pa_stream_ref;  pa_stream_set_buffer_attr; +pa_stream_set_buffer_attr_callback;  pa_stream_set_event_callback;  pa_stream_set_latency_update_callback;  pa_stream_set_monitor_stream; @@ -253,7 +272,9 @@ pa_stream_writable_size;  pa_stream_write;  pa_strerror;  pa_sw_cvolume_divide; +pa_sw_cvolume_divide_scalar;  pa_sw_cvolume_multiply; +pa_sw_cvolume_multiply_scalar;  pa_sw_cvolume_snprint_dB;  pa_sw_volume_divide;  pa_sw_volume_from_dB; diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c new file mode 100644 index 00000000..a5515e1b --- /dev/null +++ b/src/modules/alsa/alsa-mixer.c @@ -0,0 +1,3406 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2009 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 <sys/types.h> +#include <limits.h> +#include <asoundlib.h> + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/atomic.h> +#include <pulsecore/core-error.h> +#include <pulsecore/once.h> +#include <pulsecore/thread.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/strbuf.h> + +#include "alsa-mixer.h" +#include "alsa-util.h" + +struct description_map { +    const char *name; +    const char *description; +}; + +static const char *lookup_description(const char *name, const struct description_map dm[], unsigned n) { +    unsigned i; + +    for (i = 0; i < n; i++) +        if (pa_streq(dm[i].name, name)) +            return dm[i].description; + +    return NULL; +} + +struct pa_alsa_fdlist { +    unsigned num_fds; +    struct pollfd *fds; +    /* This is a temporary buffer used to avoid lots of mallocs */ +    struct pollfd *work_fds; + +    snd_mixer_t *mixer; + +    pa_mainloop_api *m; +    pa_defer_event *defer; +    pa_io_event **ios; + +    pa_bool_t polled; + +    void (*cb)(void *userdata); +    void *userdata; +}; + +static void io_cb(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + +    struct pa_alsa_fdlist *fdl = userdata; +    int err; +    unsigned i; +    unsigned short revents; + +    pa_assert(a); +    pa_assert(fdl); +    pa_assert(fdl->mixer); +    pa_assert(fdl->fds); +    pa_assert(fdl->work_fds); + +    if (fdl->polled) +        return; + +    fdl->polled = TRUE; + +    memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); + +    for (i = 0; i < fdl->num_fds; i++) { +        if (e == fdl->ios[i]) { +            if (events & PA_IO_EVENT_INPUT) +                fdl->work_fds[i].revents |= POLLIN; +            if (events & PA_IO_EVENT_OUTPUT) +                fdl->work_fds[i].revents |= POLLOUT; +            if (events & PA_IO_EVENT_ERROR) +                fdl->work_fds[i].revents |= POLLERR; +            if (events & PA_IO_EVENT_HANGUP) +                fdl->work_fds[i].revents |= POLLHUP; +            break; +        } +    } + +    pa_assert(i != fdl->num_fds); + +    if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { +        pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err)); +        return; +    } + +    a->defer_enable(fdl->defer, 1); + +    if (revents) +        snd_mixer_handle_events(fdl->mixer); +} + +static void defer_cb(pa_mainloop_api*a, pa_defer_event* e, void *userdata) { +    struct pa_alsa_fdlist *fdl = userdata; +    unsigned num_fds, i; +    int err, n; +    struct pollfd *temp; + +    pa_assert(a); +    pa_assert(fdl); +    pa_assert(fdl->mixer); + +    a->defer_enable(fdl->defer, 0); + +    if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) { +        pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); +        return; +    } +    num_fds = (unsigned) n; + +    if (num_fds != fdl->num_fds) { +        if (fdl->fds) +            pa_xfree(fdl->fds); +        if (fdl->work_fds) +            pa_xfree(fdl->work_fds); +        fdl->fds = pa_xnew0(struct pollfd, num_fds); +        fdl->work_fds = pa_xnew(struct pollfd, num_fds); +    } + +    memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); + +    if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { +        pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err)); +        return; +    } + +    fdl->polled = FALSE; + +    if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) +        return; + +    if (fdl->ios) { +        for (i = 0; i < fdl->num_fds; i++) +            a->io_free(fdl->ios[i]); + +        if (num_fds != fdl->num_fds) { +            pa_xfree(fdl->ios); +            fdl->ios = NULL; +        } +    } + +    if (!fdl->ios) +        fdl->ios = pa_xnew(pa_io_event*, num_fds); + +    /* Swap pointers */ +    temp = fdl->work_fds; +    fdl->work_fds = fdl->fds; +    fdl->fds = temp; + +    fdl->num_fds = num_fds; + +    for (i = 0;i < num_fds;i++) +        fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, +            ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | +            ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), +            io_cb, fdl); +} + +struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { +    struct pa_alsa_fdlist *fdl; + +    fdl = pa_xnew0(struct pa_alsa_fdlist, 1); + +    return fdl; +} + +void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { +    pa_assert(fdl); + +    if (fdl->defer) { +        pa_assert(fdl->m); +        fdl->m->defer_free(fdl->defer); +    } + +    if (fdl->ios) { +        unsigned i; +        pa_assert(fdl->m); +        for (i = 0; i < fdl->num_fds; i++) +            fdl->m->io_free(fdl->ios[i]); +        pa_xfree(fdl->ios); +    } + +    if (fdl->fds) +        pa_xfree(fdl->fds); +    if (fdl->work_fds) +        pa_xfree(fdl->work_fds); + +    pa_xfree(fdl); +} + +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { +    pa_assert(fdl); +    pa_assert(mixer_handle); +    pa_assert(m); +    pa_assert(!fdl->m); + +    fdl->mixer = mixer_handle; +    fdl->m = m; +    fdl->defer = m->defer_new(m, defer_cb, fdl); + +    return 0; +} + +static int prepare_mixer(snd_mixer_t *mixer, const char *dev) { +    int err; + +    pa_assert(mixer); +    pa_assert(dev); + +    if ((err = snd_mixer_attach(mixer, dev)) < 0) { +        pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err)); +        return -1; +    } + +    if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { +        pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err)); +        return -1; +    } + +    if ((err = snd_mixer_load(mixer)) < 0) { +        pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err)); +        return -1; +    } + +    pa_log_info("Successfully attached to mixer '%s'", dev); +    return 0; +} + +snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { +    int err; +    snd_mixer_t *m; +    const char *dev; +    snd_pcm_info_t* info; +    snd_pcm_info_alloca(&info); + +    pa_assert(pcm); + +    if ((err = snd_mixer_open(&m, 0)) < 0) { +        pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); +        return NULL; +    } + +    /* First, try by name */ +    if ((dev = snd_pcm_name(pcm))) +        if (prepare_mixer(m, dev) >= 0) { +            if (ctl_device) +                *ctl_device = pa_xstrdup(dev); + +            return m; +        } + +    /* Then, try by card index */ +    if (snd_pcm_info(pcm, info) >= 0) { +        char *md; +        int card_idx; + +        if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { + +            md = pa_sprintf_malloc("hw:%i", card_idx); + +            if (!dev || !pa_streq(dev, md)) +                if (prepare_mixer(m, md) >= 0) { + +                    if (ctl_device) +                        *ctl_device = md; +                    else +                        pa_xfree(md); + +                    return m; +                } + +            pa_xfree(md); +        } +    } + +    snd_mixer_close(m); +    return NULL; +} + +static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { +    [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ + +    [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, +    [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, +    [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + +    [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, +    [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, +    [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + +    [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, + +    [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, +    [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + +    [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX9] =  SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + +    [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, +    [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN +}; + +static void setting_free(pa_alsa_setting *s) { +    pa_assert(s); + +    if (s->options) +        pa_idxset_free(s->options, NULL, NULL); + +    pa_xfree(s->name); +    pa_xfree(s->description); +    pa_xfree(s); +} + +static void option_free(pa_alsa_option *o) { +    pa_assert(o); + +    pa_xfree(o->alsa_name); +    pa_xfree(o->name); +    pa_xfree(o->description); +    pa_xfree(o); +} + +static void element_free(pa_alsa_element *e) { +    pa_alsa_option *o; +    pa_assert(e); + +    while ((o = e->options)) { +        PA_LLIST_REMOVE(pa_alsa_option, e->options, o); +        option_free(o); +    } + +    pa_xfree(e->alsa_name); +    pa_xfree(e); +} + +void pa_alsa_path_free(pa_alsa_path *p) { +    pa_alsa_element *e; +    pa_alsa_setting *s; + +    pa_assert(p); + +    while ((e = p->elements)) { +        PA_LLIST_REMOVE(pa_alsa_element, p->elements, e); +        element_free(e); +    } + +    while ((s = p->settings)) { +        PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s); +        setting_free(s); +    } + +    pa_xfree(p->name); +    pa_xfree(p->description); +    pa_xfree(p); +} + +void pa_alsa_path_set_free(pa_alsa_path_set *ps) { +    pa_alsa_path *p; +    pa_assert(ps); + +    while ((p = ps->paths)) { +        PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p); +        pa_alsa_path_free(p); +    } + +    pa_xfree(ps); +} + +static long to_alsa_dB(pa_volume_t v) { +    return (long) (pa_sw_volume_to_dB(v) * 100.0); +} + +static pa_volume_t from_alsa_dB(long v) { +    return pa_sw_volume_from_dB((double) v / 100.0); +} + +static long to_alsa_volume(pa_volume_t v, long min, long max) { +    long w; + +    w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min; +    return PA_CLAMP_UNLIKELY(w, min, max); +} + +static pa_volume_t from_alsa_volume(long v, long min, long max) { +    return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min)); +} + +#define SELEM_INIT(sid, name)                           \ +    do {                                                \ +        snd_mixer_selem_id_alloca(&(sid));              \ +        snd_mixer_selem_id_set_name((sid), (name));     \ +        snd_mixer_selem_id_set_index((sid), 0);         \ +    } while(FALSE) + +static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { +    snd_mixer_selem_id_t *sid; +    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); +    pa_assert(e); +    pa_assert(cm); +    pa_assert(v); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    pa_cvolume_mute(v, cm->channels); + +    /* We take the highest volume of all channels that match */ + +    for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { +        int r; +        pa_volume_t f; + +        if (e->has_dB) { +            long value = 0; + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +                if (snd_mixer_selem_has_playback_channel(me, c)) +                    r = snd_mixer_selem_get_playback_dB(me, c, &value); +                else +                    r = -1; +            } else { +                if (snd_mixer_selem_has_capture_channel(me, c)) +                    r = snd_mixer_selem_get_capture_dB(me, c, &value); +                else +                    r = -1; +            } + +            if (r < 0) +                continue; + +#ifdef HAVE_VALGRIND_MEMCHECK_H +                VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value)); +#endif + +            f = from_alsa_dB(value); + +        } else { +            long value = 0; + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +                if (snd_mixer_selem_has_playback_channel(me, c)) +                    r = snd_mixer_selem_get_playback_volume(me, c, &value); +                else +                    r = -1; +            } else { +                if (snd_mixer_selem_has_capture_channel(me, c)) +                    r = snd_mixer_selem_get_capture_volume(me, c, &value); +                else +                    r = -1; +            } + +            if (r < 0) +                continue; + +            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) +                    v->values[k] = f; + +        mask |= e->masks[c][e->n_channels-1]; +    } + +    for (k = 0; k < cm->channels; k++) +        if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) +            v->values[k] = max_channel_volume; + +    return 0; +} + +int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { +    pa_alsa_element *e; + +    pa_assert(m); +    pa_assert(p); +    pa_assert(cm); +    pa_assert(v); + +    if (!p->has_volume) +        return -1; + +    pa_cvolume_reset(v, cm->channels); + +    PA_LLIST_FOREACH(e, p->elements) { +        pa_cvolume ev; + +        if (e->volume_use != PA_ALSA_VOLUME_MERGE) +            continue; + +        pa_assert(!p->has_dB || e->has_dB); + +        if (element_get_volume(e, m, cm, &ev) < 0) +            return -1; + +        /* If we have no dB information all we can do is take the first element and leave */ +        if (!p->has_dB) { +            *v = ev; +            return 0; +        } + +        pa_sw_cvolume_multiply(v, v, &ev); +    } + +    return 0; +} + +static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t *b) { +    snd_mixer_selem_id_t *sid; +    snd_mixer_elem_t *me; +    snd_mixer_selem_channel_id_t c; + +    pa_assert(m); +    pa_assert(e); +    pa_assert(b); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    /* We return muted if at least one channel is muted */ + +    for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { +        int r; +        int value = 0; + +        if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +            if (snd_mixer_selem_has_playback_channel(me, c)) +                r = snd_mixer_selem_get_playback_switch(me, c, &value); +            else +                r = -1; +        } else { +            if (snd_mixer_selem_has_capture_channel(me, c)) +                r = snd_mixer_selem_get_capture_switch(me, c, &value); +            else +                r = -1; +        } + +        if (r < 0) +            continue; + +        if (!value) { +            *b = FALSE; +            return 0; +        } +    } + +    *b = TRUE; +    return 0; +} + +int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) { +    pa_alsa_element *e; + +    pa_assert(m); +    pa_assert(p); +    pa_assert(muted); + +    if (!p->has_mute) +        return -1; + +    PA_LLIST_FOREACH(e, p->elements) { +        pa_bool_t b; + +        if (e->switch_use != PA_ALSA_SWITCH_MUTE) +            continue; + +        if (element_get_switch(e, m, &b) < 0) +            return -1; + +        if (!b) { +            *muted = TRUE; +            return 0; +        } +    } + +    *muted = FALSE; +    return 0; +} + +static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { +    snd_mixer_selem_id_t *sid; +    pa_cvolume rv; +    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); +    pa_assert(e); +    pa_assert(cm); +    pa_assert(v); +    pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    pa_cvolume_mute(&rv, cm->channels); + +    for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { +        int r; +        pa_volume_t f = PA_VOLUME_MUTED; +        pa_bool_t found = FALSE; + +        for (k = 0; k < cm->channels; k++) +            if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) { +                found = TRUE; +                if (v->values[k] > f) +                    f = v->values[k]; +            } + +        if (!found) { +            /* Hmm, so this channel does not exist in the volume +             * struct, so let's bind it to the overall max of the +             * volume. */ +            f = pa_cvolume_max(v); +        } + +        if (e->has_dB) { +            long value = to_alsa_dB(f); + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +                /* If we call set_play_volume() without checking first +                 * if the channel is available, ALSA behaves ver +                 * strangely and doesn't fail the call */ +                if (snd_mixer_selem_has_playback_channel(me, c)) { +                    if ((r = snd_mixer_selem_set_playback_dB(me, c, value, +1)) >= 0) +                        r = snd_mixer_selem_get_playback_dB(me, c, &value); +                } else +                    r = -1; +            } else { +                if (snd_mixer_selem_has_capture_channel(me, c)) { +                    if ((r = snd_mixer_selem_set_capture_dB(me, c, value, +1)) >= 0) +                        r = snd_mixer_selem_get_capture_dB(me, c, &value); +                } else +                    r = -1; +            } + +            if (r < 0) +                continue; + +#ifdef HAVE_VALGRIND_MEMCHECK_H +            VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value)); +#endif + +            f = from_alsa_dB(value); + +        } else { +            long value; + +            value = to_alsa_volume(f, e->min_volume, e->max_volume); + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +                if (snd_mixer_selem_has_playback_channel(me, c)) { +                    if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0) +                        r = snd_mixer_selem_get_playback_volume(me, c, &value); +                } else +                    r = -1; +            } else { +                if (snd_mixer_selem_has_capture_channel(me, c)) { +                    if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0) +                        r = snd_mixer_selem_get_capture_volume(me, c, &value); +                } else +                    r = -1; +            } + +            if (r < 0) +                continue; + +            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) +                    rv.values[k] = f; + +        mask |= e->masks[c][e->n_channels-1]; +    } + +    for (k = 0; k < cm->channels; k++) +        if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) +            rv.values[k] = max_channel_volume; + +    *v = rv; +    return 0; +} + +int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { +    pa_alsa_element *e; +    pa_cvolume rv; + +    pa_assert(m); +    pa_assert(p); +    pa_assert(cm); +    pa_assert(v); +    pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); + +    if (!p->has_volume) +        return -1; + +    rv = *v; /* Remaining adjustment */ +    pa_cvolume_reset(v, cm->channels); /* Adjustment done */ + +    PA_LLIST_FOREACH(e, p->elements) { +        pa_cvolume ev; + +        if (e->volume_use != PA_ALSA_VOLUME_MERGE) +            continue; + +        pa_assert(!p->has_dB || e->has_dB); + +        ev = rv; +        if (element_set_volume(e, m, cm, &ev) < 0) +            return -1; + +        if (!p->has_dB) { +            *v = ev; +            return 0; +        } + +        pa_sw_cvolume_multiply(v, v, &ev); +        pa_sw_cvolume_divide(&rv, &rv, &ev); +    } + +    return 0; +} + +static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t b) { +    snd_mixer_elem_t *me; +    snd_mixer_selem_id_t *sid; +    int r; + +    pa_assert(m); +    pa_assert(e); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +        r = snd_mixer_selem_set_playback_switch_all(me, b); +    else +        r = snd_mixer_selem_set_capture_switch_all(me, b); + +    if (r < 0) +        pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + +    return r; +} + +int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t muted) { +    pa_alsa_element *e; + +    pa_assert(m); +    pa_assert(p); + +    if (!p->has_mute) +        return -1; + +    PA_LLIST_FOREACH(e, p->elements) { + +        if (e->switch_use != PA_ALSA_SWITCH_MUTE) +            continue; + +        if (element_set_switch(e, m, !muted) < 0) +            return -1; +    } + +    return 0; +} + +static int element_mute_volume(pa_alsa_element *e, snd_mixer_t *m) { +    snd_mixer_elem_t *me; +    snd_mixer_selem_id_t *sid; +    int r; + +    pa_assert(m); +    pa_assert(e); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +        r = snd_mixer_selem_set_playback_volume_all(me, e->min_volume); +    else +        r = snd_mixer_selem_set_capture_volume_all(me, e->min_volume); + +    if (r < 0) +        pa_log_warn("Faile to set volume to muted of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + +    return r; +} + +/* The volume to 0dB */ +static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) { +    snd_mixer_elem_t *me; +    snd_mixer_selem_id_t *sid; +    int r; + +    pa_assert(m); +    pa_assert(e); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +        r = snd_mixer_selem_set_playback_dB_all(me, 0, +1); +    else +        r = snd_mixer_selem_set_capture_dB_all(me, 0, +1); + +    if (r < 0) +        pa_log_warn("Faile to set volume to 0dB of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + +    return r; +} + +int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) { +    pa_alsa_element *e; +    int r; + +    pa_assert(m); +    pa_assert(p); + +    pa_log_debug("Activating path %s", p->name); +    pa_alsa_path_dump(p); + +    PA_LLIST_FOREACH(e, p->elements) { + +        switch (e->switch_use) { +            case PA_ALSA_SWITCH_MUTE: +            case PA_ALSA_SWITCH_OFF: +                r = element_set_switch(e, m, FALSE); +                break; + +            case PA_ALSA_SWITCH_ON: +                r = element_set_switch(e, m, TRUE); +                break; + +            case PA_ALSA_SWITCH_IGNORE: +            case PA_ALSA_SWITCH_SELECT: +                r = 0; +                break; +        } + +        if (r < 0) +            return -1; + +        switch (e->volume_use) { +            case PA_ALSA_VOLUME_OFF: +            case PA_ALSA_VOLUME_MERGE: +                r = element_mute_volume(e, m); +                break; + +            case PA_ALSA_VOLUME_ZERO: +                r = element_zero_volume(e, m); +                break; + +            case PA_ALSA_VOLUME_IGNORE: +                r = 0; +                break; +        } + +        if (r < 0) +            return -1; +    } + +    return 0; +} + +static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) { +    pa_bool_t has_switch; +    pa_bool_t has_enumeration; +    pa_bool_t has_volume; + +    pa_assert(e); +    pa_assert(me); + +    if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +        has_switch = +            snd_mixer_selem_has_playback_switch(me) || +            (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)); +    } else { +        has_switch = +            snd_mixer_selem_has_capture_switch(me) || +            (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)); +    } + +    if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +        has_volume = +            snd_mixer_selem_has_playback_volume(me) || +            (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)); +    } else { +        has_volume = +            snd_mixer_selem_has_capture_volume(me) || +            (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)); +    } + +    has_enumeration = snd_mixer_selem_is_enumerated(me); + +    if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) || +        (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) || +        (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration)) +        return -1; + +    if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration)) +        return -1; + +    if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) || +        (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) || +        (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration)) +        return -1; + +    if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration)) +        return -1; + +    return 0; +} + +static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { +    snd_mixer_selem_id_t *sid; +    snd_mixer_elem_t *me; + +    pa_assert(m); +    pa_assert(e); + +    SELEM_INIT(sid, e->alsa_name); + +    if (!(me = snd_mixer_find_selem(m, sid))) { + +        if (e->required != PA_ALSA_REQUIRED_IGNORE) +            return -1; + +        e->switch_use = PA_ALSA_SWITCH_IGNORE; +        e->volume_use = PA_ALSA_VOLUME_IGNORE; +        e->enumeration_use = PA_ALSA_VOLUME_IGNORE; + +        return 0; +    } + +    if (e->switch_use != PA_ALSA_SWITCH_IGNORE) { +        if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + +            if (!snd_mixer_selem_has_playback_switch(me)) { +                if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)) +                    e->direction = PA_ALSA_DIRECTION_INPUT; +                else +                    e->switch_use = PA_ALSA_SWITCH_IGNORE; +            } + +        } else { + +            if (!snd_mixer_selem_has_capture_switch(me)) { +                if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)) +                    e->direction = PA_ALSA_DIRECTION_OUTPUT; +                else +                    e->switch_use = PA_ALSA_SWITCH_IGNORE; +            } +        } + +        if (e->switch_use != PA_ALSA_SWITCH_IGNORE) +            e->direction_try_other = FALSE; +    } + +    if (e->volume_use != PA_ALSA_VOLUME_IGNORE) { + +        if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + +            if (!snd_mixer_selem_has_playback_volume(me)) { +                if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)) +                    e->direction = PA_ALSA_DIRECTION_INPUT; +                else +                    e->volume_use = PA_ALSA_VOLUME_IGNORE; +            } + +        } else { + +            if (!snd_mixer_selem_has_capture_volume(me)) { +                if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)) +                    e->direction = PA_ALSA_DIRECTION_OUTPUT; +                else +                    e->volume_use = PA_ALSA_VOLUME_IGNORE; +            } +        } + +        if (e->volume_use != PA_ALSA_VOLUME_IGNORE) { +            long min_dB = 0, max_dB = 0; +            int r; + +            e->direction_try_other = FALSE; + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +                e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0; +            else +                e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0; + +            if (e->has_dB) { +#ifdef HAVE_VALGRIND_MEMCHECK_H +                VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB)); +                VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB)); +#endif + +                e->min_dB = ((double) min_dB) / 100.0; +                e->max_dB = ((double) max_dB) / 100.0; + +                if (min_dB >= max_dB) { +                    pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB); +                    e->has_dB = FALSE; +                } +            } + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +                r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume); +            else +                r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); + +            if (r < 0) { +                pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r)); +                return -1; +            } + + +            if (e->min_volume >= e->max_volume) { +                pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", e->min_volume, e->max_volume); +                e->volume_use = PA_ALSA_VOLUME_IGNORE; + +            } else { +                pa_bool_t is_mono; +                pa_channel_position_t p; + +                if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +                    is_mono = snd_mixer_selem_is_playback_mono(me) > 0; +                else +                    is_mono = snd_mixer_selem_is_capture_mono(me) > 0; + +                if (is_mono) { +                    e->n_channels = 1; + +                    if (!e->override_map) { +                        for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) +                            e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0; +                        e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL; +                    } + +                    e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1]; +                } else { +                    e->n_channels = 0; +                    for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + +                        if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) +                            continue; + +                        if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +                            e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; +                        else +                            e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; +                    } + +                    if (e->n_channels <= 0) { +                        pa_log_warn("Volume element %s with no channels?", e->alsa_name); +                        return -1; +                    } + +                    if (!e->override_map) { +                        for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { +                            pa_bool_t has_channel; + +                            if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) +                                continue; + +                            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +                                has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; +                            else +                                has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; + +                            e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0; +                        } +                    } + +                    e->merged_mask = 0; +                    for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) +                        e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; +                } +            } +        } + +    } + +    if (check_required(e, me) < 0) +        return -1; + +    if (e->switch_use == PA_ALSA_SWITCH_SELECT) { +        pa_alsa_option *o; + +        PA_LLIST_FOREACH(o, e->options) +            o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0; +    } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { +        int n; +        pa_alsa_option *o; + +        if ((n = snd_mixer_selem_get_enum_items(me)) < 0) { +            pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n)); +            return -1; +        } + +        PA_LLIST_FOREACH(o, e->options) { +            int i; + +            for (i = 0; i < n; i++) { +                char buf[128]; + +                if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0) +                    continue; + +                if (!pa_streq(buf, o->alsa_name)) +                    continue; + +                o->alsa_idx = i; +            } +        } +    } + +    return 0; +} + +static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) { +    pa_alsa_element *e; + +    pa_assert(p); +    pa_assert(section); + +    if (prefixed) { +        if (!pa_startswith(section, "Element ")) +            return NULL; + +        section += 8; +    } + +    /* This is not an element section, but an enum section? */ +    if (strchr(section, ':')) +        return NULL; + +    if (p->last_element && pa_streq(p->last_element->alsa_name, section)) +        return p->last_element; + +    PA_LLIST_FOREACH(e, p->elements) +        if (pa_streq(e->alsa_name, section)) +            goto finish; + +    e = pa_xnew0(pa_alsa_element, 1); +    e->path = p; +    e->alsa_name = pa_xstrdup(section); +    e->direction = p->direction; + +    PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e); + +finish: +    p->last_element = e; +    return e; +} + +static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { +    char *en; +    const char *on; +    pa_alsa_option *o; +    pa_alsa_element *e; + +    if (!pa_startswith(section, "Option ")) +        return NULL; + +    section += 7; + +    /* This is not an enum section, but an element section? */ +    if (!(on = strchr(section, ':'))) +        return NULL; + +    en = pa_xstrndup(section, on - section); +    on++; + +    if (p->last_option && +        pa_streq(p->last_option->element->alsa_name, en) && +        pa_streq(p->last_option->alsa_name, on)) { +        pa_xfree(en); +        return p->last_option; +    } + +    pa_assert_se(e = element_get(p, en, FALSE)); +    pa_xfree(en); + +    PA_LLIST_FOREACH(o, e->options) +        if (pa_streq(o->alsa_name, on)) +            goto finish; + +    o = pa_xnew0(pa_alsa_option, 1); +    o->element = e; +    o->alsa_name = pa_xstrdup(on); +    o->alsa_idx = -1; + +    if (p->last_option && p->last_option->element == e) +        PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o); +    else +        PA_LLIST_PREPEND(pa_alsa_option, e->options, o); + +finish: +    p->last_option = o; +    return o; +} + +static int element_parse_switch( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; + +    pa_assert(p); + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Switch makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if (pa_streq(rvalue, "ignore")) +        e->switch_use = PA_ALSA_SWITCH_IGNORE; +    else if (pa_streq(rvalue, "mute")) +        e->switch_use = PA_ALSA_SWITCH_MUTE; +    else if (pa_streq(rvalue, "off")) +        e->switch_use = PA_ALSA_SWITCH_OFF; +    else if (pa_streq(rvalue, "on")) +        e->switch_use = PA_ALSA_SWITCH_ON; +    else if (pa_streq(rvalue, "select")) +        e->switch_use = PA_ALSA_SWITCH_SELECT; +    else { +        pa_log("[%s:%u] Switch invalid of '%s'", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int element_parse_volume( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; + +    pa_assert(p); + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Volume makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if (pa_streq(rvalue, "ignore")) +        e->volume_use = PA_ALSA_VOLUME_IGNORE; +    else if (pa_streq(rvalue, "merge")) +        e->volume_use = PA_ALSA_VOLUME_MERGE; +    else if (pa_streq(rvalue, "off")) +        e->volume_use = PA_ALSA_VOLUME_OFF; +    else if (pa_streq(rvalue, "zero")) +        e->volume_use = PA_ALSA_VOLUME_ZERO; +    else { +        pa_log("[%s:%u] Volume invalid of '%s'", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int element_parse_enumeration( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; + +    pa_assert(p); + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Enumeration makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if (pa_streq(rvalue, "ignore")) +        e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; +    else if (pa_streq(rvalue, "select")) +        e->enumeration_use = PA_ALSA_ENUMERATION_SELECT; +    else { +        pa_log("[%s:%u] Enumeration invalid of '%s'", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int option_parse_priority( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_option *o; +    uint32_t prio; + +    pa_assert(p); + +    if (!(o = option_get(p, section))) { +        pa_log("[%s:%u] Priority makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if (pa_atou(rvalue, &prio) < 0) { +        pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section); +        return -1; +    } + +    o->priority = prio; +    return 0; +} + +static int option_parse_name( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_option *o; + +    pa_assert(p); + +    if (!(o = option_get(p, section))) { +        pa_log("[%s:%u] Name makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    pa_xfree(o->name); +    o->name = pa_xstrdup(rvalue); + +    return 0; +} + +static int element_parse_required( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; +    pa_alsa_required_t req; + +    pa_assert(p); + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if (pa_streq(rvalue, "ignore")) +        req = PA_ALSA_REQUIRED_IGNORE; +    else if (pa_streq(rvalue, "switch")) +        req = PA_ALSA_REQUIRED_SWITCH; +    else if (pa_streq(rvalue, "volume")) +        req = PA_ALSA_REQUIRED_VOLUME; +    else if (pa_streq(rvalue, "enumeration")) +        req = PA_ALSA_REQUIRED_ENUMERATION; +    else if (pa_streq(rvalue, "any")) +        req = PA_ALSA_REQUIRED_ANY; +    else { +        pa_log("[%s:%u] Required invalid of '%s'", filename, line, section); +        return -1; +    } + +    if (pa_streq(lvalue, "required-absent")) +        e->required_absent = req; +    else +        e->required = req; + +    return 0; +} + +static int element_parse_direction( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; + +    pa_assert(p); + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if (pa_streq(rvalue, "playback")) +        e->direction = PA_ALSA_DIRECTION_OUTPUT; +    else if (pa_streq(rvalue, "capture")) +        e->direction = PA_ALSA_DIRECTION_INPUT; +    else { +        pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int element_parse_direction_try_other( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; +    int yes; + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    if ((yes = pa_parse_boolean(rvalue)) < 0) { +        pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section); +        return -1; +    } + +    e->direction_try_other = !!yes; +    return 0; +} + +static pa_channel_position_mask_t parse_mask(const char *m) { +    pa_channel_position_mask_t v; + +    if (pa_streq(m, "all-left")) +        v = PA_CHANNEL_POSITION_MASK_LEFT; +    else if (pa_streq(m, "all-right")) +        v = PA_CHANNEL_POSITION_MASK_RIGHT; +    else if (pa_streq(m, "all-center")) +        v = PA_CHANNEL_POSITION_MASK_CENTER; +    else if (pa_streq(m, "all-front")) +        v = PA_CHANNEL_POSITION_MASK_FRONT; +    else if (pa_streq(m, "all-rear")) +        v = PA_CHANNEL_POSITION_MASK_REAR; +    else if (pa_streq(m, "all-side")) +        v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER; +    else if (pa_streq(m, "all-top")) +        v = PA_CHANNEL_POSITION_MASK_TOP; +    else if (pa_streq(m, "all-no-lfe")) +        v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE); +    else if (pa_streq(m, "all")) +        v = PA_CHANNEL_POSITION_MASK_ALL; +    else { +        pa_channel_position_t p; + +        if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID) +            return 0; + +        v = PA_CHANNEL_POSITION_MASK(p); +    } + +    return v; +} + +static int element_parse_override_map( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_path *p = userdata; +    pa_alsa_element *e; +    const char *state = NULL; +    unsigned i = 0; +    char *n; + +    if (!(e = element_get(p, section, TRUE))) { +        pa_log("[%s:%u] Override map makes no sense in '%s'", filename, line, section); +        return -1; +    } + +    while ((n = pa_split(rvalue, ",", &state))) { +        pa_channel_position_mask_t m; + +        if (!*n) +            m = 0; +        else { +            if ((m = parse_mask(n)) == 0) { +                pa_log("[%s:%u] Override map '%s' invalid in '%s'", filename, line, n, section); +                pa_xfree(n); +                return -1; +            } +        } + +        if (pa_streq(lvalue, "override-map.1")) +            e->masks[i++][0] = m; +        else +            e->masks[i++][1] = m; + +        /* Later on we might add override-map.3 and so on here ... */ + +        pa_xfree(n); +    } + +    e->override_map = TRUE; + +    return 0; +} + +static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) { +    snd_mixer_selem_id_t *sid; +    snd_mixer_elem_t *me; +    int r; + +    pa_assert(e); +    pa_assert(m); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return -1; +    } + +    if (e->switch_use == PA_ALSA_SWITCH_SELECT) { + +        if (e->direction == PA_ALSA_DIRECTION_OUTPUT) +            r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx); +        else +            r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); + +        if (r < 0) +            pa_log_warn("Faile to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + +    } else { +        pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); + +        if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) +            pa_log_warn("Faile to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); +    } + +    return r; +} + +int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m) { +    pa_alsa_option *o; +    uint32_t idx; + +    pa_assert(s); +    pa_assert(m); + +    PA_IDXSET_FOREACH(o, s->options, idx) +        element_set_option(o->element, m, o->alsa_idx); + +    return 0; +} + +static int option_verify(pa_alsa_option *o) { +    static const struct description_map well_known_descriptions[] = { +        { "input",                     N_("Input") }, +        { "input-docking",             N_("Docking Station Input") }, +        { "input-docking-microphone",  N_("Docking Station Microphone") }, +        { "input-linein",              N_("Line-In") }, +        { "input-microphone",          N_("Microphone") }, +        { "input-microphone-external", N_("External Microphone") }, +        { "input-microphone-internal", N_("Internal Microphone") }, +        { "input-radio",               N_("Radio") }, +        { "input-video",               N_("Video") }, +        { "input-agc-on",              N_("Automatic Gain Control") }, +        { "input-agc-off",             "" }, +        { "input-boost-on",            N_("Boost") }, +        { "input-boost-off",           "" }, +        { "output-amplifier-on",       N_("Amplifier") }, +        { "output-amplifier-off",      "" } +    }; + +    pa_assert(o); + +    if (!o->name) { +        pa_log("No name set for option %s", o->alsa_name); +        return -1; +    } + +    if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT && +        o->element->switch_use != PA_ALSA_SWITCH_SELECT) { +        pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name); +        return -1; +    } + +    if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && +        !pa_streq(o->alsa_name, "on") && +        !pa_streq(o->alsa_name, "off")) { +        pa_log("Switch %s options need be named off or on ", o->element->alsa_name); +        return -1; +    } + +    if (!o->description) +        o->description = pa_xstrdup(lookup_description(o->name, +                                                       well_known_descriptions, +                                                       PA_ELEMENTSOF(well_known_descriptions))); +    if (!o->description) +        o->description = pa_xstrdup(o->name); + +    return 0; +} + +static int element_verify(pa_alsa_element *e) { +    pa_alsa_option *o; + +    pa_assert(e); + +    if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) || +        (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) { +        pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name); +        return -1; +    } + +    if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { +        pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name); +        return -1; +    } + +    PA_LLIST_FOREACH(o, e->options) +        if (option_verify(o) < 0) +            return -1; + +    return 0; +} + +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") } +    }; + +    pa_alsa_element *e; + +    pa_assert(p); + +    PA_LLIST_FOREACH(e, p->elements) +        if (element_verify(e) < 0) +            return -1; + +    if (!p->description) +        p->description = pa_xstrdup(lookup_description(p->name, +                                                       well_known_descriptions, +                                                       PA_ELEMENTSOF(well_known_descriptions))); + +    if (!p->description) +        p->description = pa_xstrdup(p->name); + +    return 0; +} + +pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction) { +    pa_alsa_path *p; +    char *fn; +    int r; +    const char *n; + +    pa_config_item items[] = { +        /* [General] */ +        { "priority",            pa_config_parse_unsigned,          NULL, "General" }, +        { "description",         pa_config_parse_string,            NULL, "General" }, +        { "name",                pa_config_parse_string,            NULL, "General" }, + +        /* [Option ...] */ +        { "priority",            option_parse_priority,             NULL, NULL }, +        { "name",                option_parse_name,                 NULL, NULL }, + +        /* [Element ...] */ +        { "switch",              element_parse_switch,              NULL, NULL }, +        { "volume",              element_parse_volume,              NULL, NULL }, +        { "enumeration",         element_parse_enumeration,         NULL, NULL }, +        { "override-map.1",      element_parse_override_map,        NULL, NULL }, +        { "override-map.2",      element_parse_override_map,        NULL, NULL }, +        /* ... later on we might add override-map.3 and so on here ... */ +        { "required",            element_parse_required,            NULL, NULL }, +        { "required-absent",     element_parse_required,            NULL, NULL }, +        { "direction",           element_parse_direction,           NULL, NULL }, +        { "direction-try-other", element_parse_direction_try_other, NULL, NULL }, +        { NULL, NULL, NULL, NULL } +    }; + +    pa_assert(fname); + +    p = pa_xnew0(pa_alsa_path, 1); +    n = pa_path_get_filename(fname); +    p->name = pa_xstrndup(n, strcspn(n, ".")); +    p->direction = direction; + +    items[0].data = &p->priority; +    items[1].data = &p->description; +    items[2].data = &p->name; + +    fn = pa_maybe_prefix_path(fname, PA_ALSA_PATHS_DIR); +    r = pa_config_parse(fn, NULL, items, p); +    pa_xfree(fn); + +    if (r < 0) +        goto fail; + +    if (path_verify(p) < 0) +        goto fail; + +    return p; + +fail: +    pa_alsa_path_free(p); +    return NULL; +} + +pa_alsa_path* pa_alsa_path_synthesize(const char*element, pa_alsa_direction_t direction) { +    pa_alsa_path *p; +    pa_alsa_element *e; + +    pa_assert(element); + +    p = pa_xnew0(pa_alsa_path, 1); +    p->name = pa_xstrdup(element); +    p->direction = direction; + +    e = pa_xnew0(pa_alsa_element, 1); +    e->path = p; +    e->alsa_name = pa_xstrdup(element); +    e->direction = direction; + +    e->switch_use = PA_ALSA_SWITCH_MUTE; +    e->volume_use = PA_ALSA_VOLUME_MERGE; + +    PA_LLIST_PREPEND(pa_alsa_element, p->elements, e); +    return p; +} + +static pa_bool_t element_drop_unsupported(pa_alsa_element *e) { +    pa_alsa_option *o, *n; + +    pa_assert(e); + +    for (o = e->options; o; o = n) { +        n = o->next; + +        if (o->alsa_idx < 0) { +            PA_LLIST_REMOVE(pa_alsa_option, e->options, o); +            option_free(o); +        } +    } + +    return +        e->switch_use != PA_ALSA_SWITCH_IGNORE || +        e->volume_use != PA_ALSA_VOLUME_IGNORE || +        e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE; +} + +static void path_drop_unsupported(pa_alsa_path *p) { +    pa_alsa_element *e, *n; + +    pa_assert(p); + +    for (e = p->elements; e; e = n) { +        n = e->next; + +        if (!element_drop_unsupported(e)) { +            PA_LLIST_REMOVE(pa_alsa_element, p->elements, e); +            element_free(e); +        } +    } +} + +static void path_make_options_unique(pa_alsa_path *p) { +    pa_alsa_element *e; +    pa_alsa_option *o, *u; + +    PA_LLIST_FOREACH(e, p->elements) { +        PA_LLIST_FOREACH(o, e->options) { +            unsigned i; +            char *m; + +            for (u = o->next; u; u = u->next) +                if (pa_streq(u->name, o->name)) +                    break; + +            if (!u) +                continue; + +            m = pa_xstrdup(o->name); + +            /* OK, this name is not unique, hence let's rename */ +            for (i = 1, u = o; u; u = u->next) { +                char *nn, *nd; + +                if (!pa_streq(u->name, m)) +                    continue; + +                nn = pa_sprintf_malloc("%s-%u", m, i); +                pa_xfree(u->name); +                u->name = nn; + +                nd = pa_sprintf_malloc("%s %u", u->description, i); +                pa_xfree(u->description); +                u->description = nd; + +                i++; +            } + +            pa_xfree(m); +        } +    } +} + +static pa_bool_t element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) { +    pa_alsa_option *o; + +    for (; e; e = e->next) +        if (e->switch_use == PA_ALSA_SWITCH_SELECT || +            e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) +            break; + +    if (!e) +        return FALSE; + +    for (o = e->options; o; o = o->next) { +        pa_alsa_setting *s; + +        if (template) { +            s = pa_xnewdup(pa_alsa_setting, template, 1); +            s->options = pa_idxset_copy(template->options); +            s->name = pa_sprintf_malloc(_("%s+%s"), template->name, o->name); +            s->description = +                (template->description[0] && o->description[0]) +                ? pa_sprintf_malloc(_("%s / %s"), template->description, o->description) +                : (template->description[0] +                   ? pa_xstrdup(template->description) +                   : pa_xstrdup(o->description)); + +            s->priority = PA_MAX(template->priority, o->priority); +        } else { +            s = pa_xnew0(pa_alsa_setting, 1); +            s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); +            s->name = pa_xstrdup(o->name); +            s->description = pa_xstrdup(o->description); +            s->priority = o->priority; +        } + +        pa_idxset_put(s->options, o, NULL); + +        if (element_create_settings(e->next, s)) +            /* This is not a leaf, so let's get rid of it */ +            setting_free(s); +        else { +            /* This is a leaf, so let's add it */ +            PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s); + +            e->path->last_setting = s; +        } +    } + +    return TRUE; +} + +static void path_create_settings(pa_alsa_path *p) { +    pa_assert(p); + +    element_create_settings(p->elements, NULL); +} + +int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) { +    pa_alsa_element *e; +    double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX]; +    pa_channel_position_t t; + +    pa_assert(p); +    pa_assert(m); + +    if (p->probed) +        return 0; + +    pa_zero(min_dB); +    pa_zero(max_dB); + +    pa_log_debug("Probing path '%s'", p->name); + +    PA_LLIST_FOREACH(e, p->elements) { +        if (element_probe(e, m) < 0) { +            p->supported = FALSE; +            pa_log_debug("Probe of element '%s' failed.", e->alsa_name); +            return -1; +        } + +        if (ignore_dB) +            e->has_dB = FALSE; + +        if (e->volume_use == PA_ALSA_VOLUME_MERGE) { + +            if (!p->has_volume) { +                p->min_volume = e->min_volume; +                p->max_volume = e->max_volume; +            } + +            if (e->has_dB) { +                if (!p->has_volume) { +                    for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) +                        if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) { +                            min_dB[t] = e->min_dB; +                            max_dB[t] = e->max_dB; +                        } + +                    p->has_dB = TRUE; +                } else { + +                    if (p->has_dB) { +                        for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) +                            if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) { +                                min_dB[t] += e->min_dB; +                                max_dB[t] += e->max_dB; +                            } +                    } else +                        /* Hmm, there's another element before us +                         * which cannot do dB volumes, so we we need +                         * to 'neutralize' this slider */ +                        e->volume_use = PA_ALSA_VOLUME_ZERO; +                } +            } else if (p->has_volume) +                /* We can't use this volume, so let's ignore it */ +                e->volume_use = PA_ALSA_VOLUME_IGNORE; + +            p->has_volume = TRUE; +        } + +        if (e->switch_use == PA_ALSA_SWITCH_MUTE) +            p->has_mute = TRUE; +    } + +    path_drop_unsupported(p); +    path_make_options_unique(p); +    path_create_settings(p); + +    p->supported = TRUE; +    p->probed = TRUE; + +    p->min_dB = INFINITY; +    p->max_dB = -INFINITY; + +    for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) { +        if (p->min_dB > min_dB[t]) +            p->min_dB = min_dB[t]; + +        if (p->max_dB < max_dB[t]) +            p->max_dB = max_dB[t]; +    } + +    return 0; +} + +void pa_alsa_setting_dump(pa_alsa_setting *s) { +    pa_assert(s); + +    pa_log_debug("Setting %s (%s) priority=%u", +                 s->name, +                 pa_strnull(s->description), +                 s->priority); +} + +void pa_alsa_option_dump(pa_alsa_option *o) { +    pa_assert(o); + +    pa_log_debug("Option %s (%s/%s) index=%i, priority=%u", +                 o->alsa_name, +                 pa_strnull(o->name), +                 pa_strnull(o->description), +                 o->alsa_idx, +                 o->priority); +} + +void pa_alsa_element_dump(pa_alsa_element *e) { +    pa_alsa_option *o; +    pa_assert(e); + +    pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, enumeration=%i, required=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s", +                 e->alsa_name, +                 e->direction, +                 e->switch_use, +                 e->volume_use, +                 e->enumeration_use, +                 e->required, +                 e->required_absent, +                 (long long unsigned) e->merged_mask, +                 e->n_channels, +                 pa_yes_no(e->override_map)); + +    PA_LLIST_FOREACH(o, e->options) +        pa_alsa_option_dump(o); +} + +void pa_alsa_path_dump(pa_alsa_path *p) { +    pa_alsa_element *e; +    pa_alsa_setting *s; +    pa_assert(p); + +    pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, " +                 "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g", +                 p->name, +                 pa_strnull(p->description), +                 p->direction, +                 p->priority, +                 pa_yes_no(p->probed), +                 pa_yes_no(p->supported), +                 pa_yes_no(p->has_mute), +                 pa_yes_no(p->has_volume), +                 pa_yes_no(p->has_dB), +                 p->min_volume, p->max_volume, +                 p->min_dB, p->max_dB); + +    PA_LLIST_FOREACH(e, p->elements) +        pa_alsa_element_dump(e); + +    PA_LLIST_FOREACH(s, p->settings) +        pa_alsa_setting_dump(s); +} + +static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { +    snd_mixer_selem_id_t *sid; +    snd_mixer_elem_t *me; + +    pa_assert(e); +    pa_assert(m); +    pa_assert(cb); + +    SELEM_INIT(sid, e->alsa_name); +    if (!(me = snd_mixer_find_selem(m, sid))) { +        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); +        return; +    } + +    snd_mixer_elem_set_callback(me, cb); +    snd_mixer_elem_set_callback_private(me, userdata); +} + +void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { +    pa_alsa_element *e; + +    pa_assert(p); +    pa_assert(m); +    pa_assert(cb); + +    PA_LLIST_FOREACH(e, p->elements) +        element_set_callback(e, m, cb, userdata); +} + +void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { +    pa_alsa_path *p; + +    pa_assert(ps); +    pa_assert(m); +    pa_assert(cb); + +    PA_LLIST_FOREACH(p, ps->paths) +        pa_alsa_path_set_callback(p, m, cb, userdata); +} + +pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction) { +    pa_alsa_path_set *ps; +    char **pn = NULL, **en = NULL, **ie; + +    pa_assert(m); +    pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT); + +    if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction) +        return NULL; + +    ps = pa_xnew0(pa_alsa_path_set, 1); +    ps->direction = direction; + +    if (direction == PA_ALSA_DIRECTION_OUTPUT) +        pn = m->output_path_names; +    else if (direction == PA_ALSA_DIRECTION_INPUT) +        pn = m->input_path_names; + +    if (pn) { +        char **in; + +        for (in = pn; *in; in++) { +            pa_alsa_path *p; +            pa_bool_t duplicate = FALSE; +            char **kn, *fn; + +            for (kn = pn; kn != in; kn++) +                if (pa_streq(*kn, *in)) { +                    duplicate = TRUE; +                    break; +                } + +            if (duplicate) +                continue; + +            fn = pa_sprintf_malloc("%s.conf", *in); + +            if ((p = pa_alsa_path_new(fn, direction))) { +                p->path_set = ps; +                PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p); +                ps->last_path = p; +            } + +            pa_xfree(fn); +        } + +        return ps; +    } + +    if (direction == PA_ALSA_DIRECTION_OUTPUT) +        en = m->output_element; +    else if (direction == PA_ALSA_DIRECTION_INPUT) +        en = m->input_element; + +    if (!en) { +        pa_alsa_path_set_free(ps); +        return NULL; +    } + +    for (ie = en; *ie; ie++) { +        char **je; +        pa_alsa_path *p; + +        p = pa_alsa_path_synthesize(*ie, direction); +        p->path_set = ps; + +        /* Mark all other passed elements for require-absent */ +        for (je = en; *je; je++) { +            pa_alsa_element *e; +            e = pa_xnew0(pa_alsa_element, 1); +            e->path = p; +            e->alsa_name = pa_xstrdup(*je); +            e->direction = direction; +            e->required_absent = PA_ALSA_REQUIRED_ANY; + +            PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e); +            p->last_element = e; +        } + +        PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p); +        ps->last_path = p; +    } + +    return ps; +} + +void pa_alsa_path_set_dump(pa_alsa_path_set *ps) { +    pa_alsa_path *p; +    pa_assert(ps); + +    pa_log_debug("Path Set %p, direction=%i, probed=%s", +                 (void*) ps, +                 ps->direction, +                 pa_yes_no(ps->probed)); + +    PA_LLIST_FOREACH(p, ps->paths) +        pa_alsa_path_dump(p); +} + +static void path_set_unify(pa_alsa_path_set *ps) { +    pa_alsa_path *p; +    pa_bool_t has_dB = TRUE, has_volume = TRUE, has_mute = TRUE; +    pa_assert(ps); + +    /* We have issues dealing with paths that vary too wildly. That +     * means for now we have to have all paths support volume/mute/dB +     * or none. */ + +    PA_LLIST_FOREACH(p, ps->paths) { +        pa_assert(p->probed); + +        if (!p->has_volume) +            has_volume = FALSE; +        else if (!p->has_dB) +            has_dB = FALSE; + +        if (!p->has_mute) +            has_mute = FALSE; +    } + +    if (!has_volume || !has_dB || !has_mute) { + +        if (!has_volume) +            pa_log_debug("Some paths of the device lack hardware volume control, disabling hardware control altogether."); +        else if (!has_dB) +            pa_log_debug("Some paths of the device lack dB information, disabling dB logic altogether."); + +        if (!has_mute) +            pa_log_debug("Some paths of the device lack hardware mute control, disabling hardware control altogether."); + +        PA_LLIST_FOREACH(p, ps->paths) { +            if (!has_volume) +                p->has_volume = FALSE; +            else if (!has_dB) +                p->has_dB = FALSE; + +            if (!has_mute) +                p->has_mute = FALSE; +        } +    } +} + +static void path_set_make_paths_unique(pa_alsa_path_set *ps) { +    pa_alsa_path *p, *q; + +    PA_LLIST_FOREACH(p, ps->paths) { +        unsigned i; +        char *m; + +        for (q = p->next; q; q = q->next) +            if (pa_streq(q->name, p->name)) +                break; + +        if (!q) +            continue; + +        m = pa_xstrdup(p->name); + +        /* OK, this name is not unique, hence let's rename */ +        for (i = 1, q = p; q; q = q->next) { +            char *nn, *nd; + +            if (!pa_streq(q->name, m)) +                continue; + +            nn = pa_sprintf_malloc("%s-%u", m, i); +            pa_xfree(q->name); +            q->name = nn; + +            nd = pa_sprintf_malloc("%s %u", q->description, i); +            pa_xfree(q->description); +            q->description = nd; + +            i++; +        } + +        pa_xfree(m); +    } +} + +void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t ignore_dB) { +    pa_alsa_path *p, *n; + +    pa_assert(ps); + +    if (ps->probed) +        return; + +    for (p = ps->paths; p; p = n) { +        n = p->next; + +        if (pa_alsa_path_probe(p, m, ignore_dB) < 0) { +            PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p); +            pa_alsa_path_free(p); +        } +    } + +    path_set_unify(ps); +    path_set_make_paths_unique(ps); +    ps->probed = TRUE; +} + +static void mapping_free(pa_alsa_mapping *m) { +    pa_assert(m); + +    pa_xfree(m->name); +    pa_xfree(m->description); + +    pa_xstrfreev(m->device_strings); +    pa_xstrfreev(m->input_path_names); +    pa_xstrfreev(m->output_path_names); +    pa_xstrfreev(m->input_element); +    pa_xstrfreev(m->output_element); + +    pa_assert(!m->input_pcm); +    pa_assert(!m->output_pcm); + +    pa_xfree(m); +} + +static void profile_free(pa_alsa_profile *p) { +    pa_assert(p); + +    pa_xfree(p->name); +    pa_xfree(p->description); + +    pa_xstrfreev(p->input_mapping_names); +    pa_xstrfreev(p->output_mapping_names); + +    if (p->input_mappings) +        pa_idxset_free(p->input_mappings, NULL, NULL); + +    if (p->output_mappings) +        pa_idxset_free(p->output_mappings, NULL, NULL); + +    pa_xfree(p); +} + +void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) { +    pa_assert(ps); + +    if (ps->profiles) { +        pa_alsa_profile *p; + +        while ((p = pa_hashmap_steal_first(ps->profiles))) +            profile_free(p); + +        pa_hashmap_free(ps->profiles, NULL, NULL); +    } + +    if (ps->mappings) { +        pa_alsa_mapping *m; + +        while ((m = pa_hashmap_steal_first(ps->mappings))) +            mapping_free(m); + +        pa_hashmap_free(ps->mappings, NULL, NULL); +    } + +    pa_xfree(ps); +} + +static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) { +    pa_alsa_mapping *m; + +    if (!pa_startswith(name, "Mapping ")) +        return NULL; + +    name += 8; + +    if ((m = pa_hashmap_get(ps->mappings, name))) +        return m; + +    m = pa_xnew0(pa_alsa_mapping, 1); +    m->profile_set = ps; +    m->name = pa_xstrdup(name); +    pa_channel_map_init(&m->channel_map); + +    pa_hashmap_put(ps->mappings, m->name, m); + +    return m; +} + +static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) { +    pa_alsa_profile *p; + +    if (!pa_startswith(name, "Profile ")) +        return NULL; + +    name += 8; + +    if ((p = pa_hashmap_get(ps->profiles, name))) +        return p; + +    p = pa_xnew0(pa_alsa_profile, 1); +    p->profile_set = ps; +    p->name = pa_xstrdup(name); + +    pa_hashmap_put(ps->profiles, p->name, p); + +    return p; +} + +static int mapping_parse_device_strings( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_mapping *m; + +    pa_assert(ps); + +    if (!(m = mapping_get(ps, section))) { +        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); +        return -1; +    } + +    pa_xstrfreev(m->device_strings); +    if (!(m->device_strings = pa_split_spaces_strv(rvalue))) { +        pa_log("[%s:%u] Device string list empty of '%s'", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int mapping_parse_channel_map( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_mapping *m; + +    pa_assert(ps); + +    if (!(m = mapping_get(ps, section))) { +        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); +        return -1; +    } + +    if (!(pa_channel_map_parse(&m->channel_map, rvalue))) { +        pa_log("[%s:%u] Channel map invalid of '%s'", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int mapping_parse_paths( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_mapping *m; + +    pa_assert(ps); + +    if (!(m = mapping_get(ps, section))) { +        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); +        return -1; +    } + +    if (pa_streq(lvalue, "paths-input")) { +        pa_xstrfreev(m->input_path_names); +        m->input_path_names = pa_split_spaces_strv(rvalue); +    } else { +        pa_xstrfreev(m->output_path_names); +        m->output_path_names = pa_split_spaces_strv(rvalue); +    } + +    return 0; +} + +static int mapping_parse_element( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_mapping *m; + +    pa_assert(ps); + +    if (!(m = mapping_get(ps, section))) { +        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); +        return -1; +    } + +    if (pa_streq(lvalue, "element-input")) { +        pa_xstrfreev(m->input_element); +        m->input_element = pa_split_spaces_strv(rvalue); +    } else { +        pa_xstrfreev(m->output_element); +        m->output_element = pa_split_spaces_strv(rvalue); +    } + +    return 0; +} + +static int mapping_parse_direction( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_mapping *m; + +    pa_assert(ps); + +    if (!(m = mapping_get(ps, section))) { +        pa_log("[%s:%u] Section name %s invalid.", filename, line, section); +        return -1; +    } + +    if (pa_streq(rvalue, "input")) +        m->direction = PA_ALSA_DIRECTION_INPUT; +    else if (pa_streq(rvalue, "output")) +        m->direction = PA_ALSA_DIRECTION_OUTPUT; +    else if (pa_streq(rvalue, "any")) +        m->direction = PA_ALSA_DIRECTION_ANY; +    else { +        pa_log("[%s:%u] Direction %s invalid.", filename, line, rvalue); +        return -1; +    } + +    return 0; +} + +static int mapping_parse_description( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_profile *p; +    pa_alsa_mapping *m; + +    pa_assert(ps); + +    if ((m = mapping_get(ps, section))) { +        pa_xstrdup(m->description); +        m->description = pa_xstrdup(rvalue); +    } else if ((p = profile_get(ps, section))) { +        pa_xfree(p->description); +        p->description = pa_xstrdup(rvalue); +    } else { +        pa_log("[%s:%u] Section name %s invalid.", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int mapping_parse_priority( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_profile *p; +    pa_alsa_mapping *m; +    uint32_t prio; + +    pa_assert(ps); + +    if (pa_atou(rvalue, &prio) < 0) { +        pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section); +        return -1; +    } + +    if ((m = mapping_get(ps, section))) +        m->priority = prio; +    else if ((p = profile_get(ps, section))) +        p->priority = prio; +    else { +        pa_log("[%s:%u] Section name %s invalid.", filename, line, section); +        return -1; +    } + +    return 0; +} + +static int profile_parse_mappings( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_profile *p; + +    pa_assert(ps); + +    if (!(p = profile_get(ps, section))) { +        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); +        return -1; +    } + +    if (pa_streq(lvalue, "input-mappings")) { +        pa_xstrfreev(p->input_mapping_names); +        p->input_mapping_names = pa_split_spaces_strv(rvalue); +    } else { +        pa_xstrfreev(p->output_mapping_names); +        p->output_mapping_names = pa_split_spaces_strv(rvalue); +    } + +    return 0; +} + +static int profile_parse_skip_probe( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    pa_alsa_profile_set *ps = userdata; +    pa_alsa_profile *p; +    int b; + +    pa_assert(ps); + +    if (!(p = profile_get(ps, section))) { +        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); +        return -1; +    } + +    if ((b = pa_parse_boolean(rvalue)) < 0) { +        pa_log("[%s:%u] Skip probe invalid of '%s'", filename, line, section); +        return -1; +    } + +    p->supported = b; + +    return 0; +} + +static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { + +    static const struct description_map well_known_descriptions[] = { +        { "analog-mono",            N_("Analog Mono") }, +        { "analog-stereo",          N_("Analog Stereo") }, +        { "analog-surround-21",     N_("Analog Surround 2.1") }, +        { "analog-surround-30",     N_("Analog Surround 3.0") }, +        { "analog-surround-31",     N_("Analog Surround 3.1") }, +        { "analog-surround-40",     N_("Analog Surround 4.0") }, +        { "analog-surround-41",     N_("Analog Surround 4.1") }, +        { "analog-surround-50",     N_("Analog Surround 5.0") }, +        { "analog-surround-51",     N_("Analog Surround 5.1") }, +        { "analog-surround-61",     N_("Analog Surround 6.0") }, +        { "analog-surround-61",     N_("Analog Surround 6.1") }, +        { "analog-surround-70",     N_("Analog Surround 7.0") }, +        { "analog-surround-71",     N_("Analog Surround 7.1") }, +        { "iec958-stereo",          N_("Digital Stereo (IEC958)") }, +        { "iec958-surround-40",     N_("Digital Surround 4.0 (IEC958)") }, +        { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") }, +        { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") }, +        { "hdmi-stereo",            N_("Digital Stereo (HDMI)") } +    }; + +    pa_assert(m); + +    if (!pa_channel_map_valid(&m->channel_map)) { +        pa_log("Mapping %s is missing channel map.", m->name); +        return -1; +    } + +    if (!m->device_strings) { +        pa_log("Mapping %s is missing device strings.", m->name); +        return -1; +    } + +    if ((m->input_path_names && m->input_element) || +        (m->output_path_names && m->output_element)) { +        pa_log("Mapping %s must have either mixer path or mixer elment, not both.", m->name); +        return -1; +    } + +    if (!m->description) +        m->description = pa_xstrdup(lookup_description(m->name, +                                                       well_known_descriptions, +                                                       PA_ELEMENTSOF(well_known_descriptions))); + +    if (!m->description) +        m->description = pa_xstrdup(m->name); + +    if (bonus) { +        if (pa_channel_map_equal(&m->channel_map, bonus)) +            m->priority += 5000; +        else if (m->channel_map.channels == bonus->channels) +            m->priority += 4000; +    } + +    return 0; +} + +void pa_alsa_mapping_dump(pa_alsa_mapping *m) { +    char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + +    pa_assert(m); + +    pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i", +                 m->name, +                 pa_strnull(m->description), +                 m->priority, +                 pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map), +                 pa_yes_no(m->supported), +                 m->direction); +} + +static void profile_set_add_auto_pair( +        pa_alsa_profile_set *ps, +        pa_alsa_mapping *m, /* output */ +        pa_alsa_mapping *n  /* input */) { + +    char *name; +    pa_alsa_profile *p; + +    pa_assert(ps); +    pa_assert(m || n); + +    if (m && m->direction == PA_ALSA_DIRECTION_INPUT) +        return; + +    if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT) +        return; + +    if (m && n) +        name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name); +    else if (m) +        name = pa_sprintf_malloc("output:%s", m->name); +    else +        name = pa_sprintf_malloc("input:%s", n->name); + +    if ((p = pa_hashmap_get(ps->profiles, name))) { +        pa_xfree(name); +        return; +    } + +    p = pa_xnew0(pa_alsa_profile, 1); +    p->profile_set = ps; +    p->name = name; + +    if (m) { +        p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); +        pa_idxset_put(p->output_mappings, m, NULL); +        p->priority += m->priority * 100; +    } + +    if (n) { +        p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); +        pa_idxset_put(p->input_mappings, n, NULL); +        p->priority += n->priority; +    } + +    pa_hashmap_put(ps->profiles, p->name, p); +} + +static void profile_set_add_auto(pa_alsa_profile_set *ps) { +    pa_alsa_mapping *m, *n; +    void *m_state, *n_state; + +    pa_assert(ps); + +    PA_HASHMAP_FOREACH(m, ps->mappings, m_state) { +        profile_set_add_auto_pair(ps, m, NULL); + +        PA_HASHMAP_FOREACH(n, ps->mappings, n_state) +            profile_set_add_auto_pair(ps, m, n); +    } + +    PA_HASHMAP_FOREACH(n, ps->mappings, n_state) +        profile_set_add_auto_pair(ps, NULL, n); +} + +static int profile_verify(pa_alsa_profile *p) { + +    static const struct description_map well_known_descriptions[] = { +        { "output:analog-mono+input:analog-mono",     N_("Analog Mono Duplex") }, +        { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") }, +        { "output:iec958-stereo",                     N_("Digital Stereo Duplex (IEC958)") }, +        { "off",                                      N_("Off") } +    }; + +    pa_assert(p); + +    /* Replace the output mapping names by the actual mappings */ +    if (p->output_mapping_names) { +        char **name; + +        pa_assert(!p->output_mappings); +        p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + +        for (name = p->output_mapping_names; *name; name++) { +            pa_alsa_mapping *m; +            char **in; +            pa_bool_t duplicate = FALSE; + +            for (in = name + 1; *in; in++) +                if (pa_streq(*name, *in)) { +                    duplicate = TRUE; +                    break; +                } + +            if (duplicate) +                continue; + +            if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) { +                pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name); +                return -1; +            } + +            pa_idxset_put(p->output_mappings, m, NULL); + +            if (p->supported) +                m->supported++; +        } + +        pa_xstrfreev(p->output_mapping_names); +        p->output_mapping_names = NULL; +    } + +    /* Replace the input mapping names by the actual mappings */ +    if (p->input_mapping_names) { +        char **name; + +        pa_assert(!p->input_mappings); +        p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + +        for (name = p->input_mapping_names; *name; name++) { +            pa_alsa_mapping *m; +            char **in; +            pa_bool_t duplicate = FALSE; + +            for (in = name + 1; *in; in++) +                if (pa_streq(*name, *in)) { +                    duplicate = TRUE; +                    break; +                } + +            if (duplicate) +                continue; + +            if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) { +                pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name); +                return -1; +            } + +            pa_idxset_put(p->input_mappings, m, NULL); + +            if (p->supported) +                m->supported++; +        } + +        pa_xstrfreev(p->input_mapping_names); +        p->input_mapping_names = NULL; +    } + +    if (!p->input_mappings && !p->output_mappings) { +        pa_log("Profile '%s' lacks mappings.", p->name); +        return -1; +    } + +    if (!p->description) +        p->description = pa_xstrdup(lookup_description(p->name, +                                                       well_known_descriptions, +                                                       PA_ELEMENTSOF(well_known_descriptions))); + +    if (!p->description) { +        pa_strbuf *sb; +        uint32_t idx; +        pa_alsa_mapping *m; + +        sb = pa_strbuf_new(); + +        if (p->output_mappings) +            PA_IDXSET_FOREACH(m, p->output_mappings, idx) { +                if (!pa_strbuf_isempty(sb)) +                    pa_strbuf_puts(sb, " + "); + +                pa_strbuf_printf(sb, "%s Output", m->description); +            } + +        if (p->input_mappings) +            PA_IDXSET_FOREACH(m, p->input_mappings, idx) { +                if (!pa_strbuf_isempty(sb)) +                    pa_strbuf_puts(sb, " + "); + +                pa_strbuf_printf(sb, "%s Input", m->description); +            } + +        p->description = pa_strbuf_tostring_free(sb); +    } + +    return 0; +} + +void pa_alsa_profile_dump(pa_alsa_profile *p) { +    uint32_t idx; +    pa_alsa_mapping *m; +    pa_assert(p); + +    pa_log_debug("Profile %s (%s), priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u", +                 p->name, +                 pa_strnull(p->description), +                 p->priority, +                 pa_yes_no(p->supported), +                 p->input_mappings ? pa_idxset_size(p->input_mappings) : 0, +                 p->output_mappings ? pa_idxset_size(p->output_mappings) : 0); + +    if (p->input_mappings) +        PA_IDXSET_FOREACH(m, p->input_mappings, idx) +            pa_log_debug("Input %s", m->name); + +    if (p->output_mappings) +        PA_IDXSET_FOREACH(m, p->output_mappings, idx) +            pa_log_debug("Output %s", m->name); +} + +pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) { +    pa_alsa_profile_set *ps; +    pa_alsa_profile *p; +    pa_alsa_mapping *m; +    char *fn; +    int r; +    void *state; + +    static pa_config_item items[] = { +        /* [General] */ +        { "auto-profiles",          pa_config_parse_bool,         NULL, "General" }, + +        /* [Mapping ...] */ +        { "device-strings",         mapping_parse_device_strings, NULL, NULL }, +        { "channel-map",            mapping_parse_channel_map,    NULL, NULL }, +        { "paths-input",            mapping_parse_paths,          NULL, NULL }, +        { "paths-output",           mapping_parse_paths,          NULL, NULL }, +        { "element-input",          mapping_parse_element,        NULL, NULL }, +        { "element-output",         mapping_parse_element,        NULL, NULL }, +        { "direction",              mapping_parse_direction,      NULL, NULL }, + +        /* Shared by [Mapping ...] and [Profile ...] */ +        { "description",            mapping_parse_description,    NULL, NULL }, +        { "priority",               mapping_parse_priority,       NULL, NULL }, + +        /* [Profile ...] */ +        { "input-mappings",         profile_parse_mappings,       NULL, NULL }, +        { "output-mappings",        profile_parse_mappings,       NULL, NULL }, +        { "skip-probe",             profile_parse_skip_probe,     NULL, NULL }, +        { NULL, NULL, NULL, NULL } +    }; + +    ps = pa_xnew0(pa_alsa_profile_set, 1); +    ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); +    ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + +    items[0].data = &ps->auto_profiles; + +    if (!fname) +        fname = "default.conf"; + +    fn = pa_maybe_prefix_path(fname, PA_ALSA_PROFILE_SETS_DIR); +    r = pa_config_parse(fn, NULL, items, ps); +    pa_xfree(fn); + +    if (r < 0) +        goto fail; + +    PA_HASHMAP_FOREACH(m, ps->mappings, state) +        if (mapping_verify(m, bonus) < 0) +            goto fail; + +    if (ps->auto_profiles) +        profile_set_add_auto(ps); + +    PA_HASHMAP_FOREACH(p, ps->profiles, state) +        if (profile_verify(p) < 0) +            goto fail; + +    return ps; + +fail: +    pa_alsa_profile_set_free(ps); +    return NULL; +} + +void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss) { +    void *state; +    pa_alsa_profile *p, *last = NULL; +    pa_alsa_mapping *m; + +    pa_assert(ps); +    pa_assert(dev_id); +    pa_assert(ss); + +    if (ps->probed) +        return; + +    PA_HASHMAP_FOREACH(p, ps->profiles, state) { +        pa_sample_spec try_ss; +        pa_channel_map try_map; +        uint32_t idx; + +        /* Is this already marked that it is supported? (i.e. from the config file) */ +        if (p->supported) +            continue; + +        pa_log_debug("Looking at profile %s", p->name); + +        /* Close PCMs from the last iteration we don't need anymore */ +        if (last && last->output_mappings) +            PA_IDXSET_FOREACH(m, last->output_mappings, idx) { + +                if (!m->output_pcm) +                    break; + +                if (last->supported) +                    m->supported++; + +                if (!p->output_mappings || !pa_idxset_get_by_data(p->output_mappings, m, NULL)) { +                    snd_pcm_close(m->output_pcm); +                    m->output_pcm = NULL; +                } +            } + +        if (last && last->input_mappings) +            PA_IDXSET_FOREACH(m, last->input_mappings, idx) { + +                if (!m->input_pcm) +                    break; + +                if (last->supported) +                    m->supported++; + +                if (!p->input_mappings || !pa_idxset_get_by_data(p->input_mappings, m, NULL)) { +                    snd_pcm_close(m->input_pcm); +                    m->input_pcm = NULL; +                } +            } + +        p->supported = TRUE; + +        /* Check if we can open all new ones */ +        if (p->output_mappings) +            PA_IDXSET_FOREACH(m, p->output_mappings, idx) { + +                if (m->output_pcm) +                    continue; + +                pa_log_debug("Checking for playback on %s (%s)", m->description, m->name); +                try_map = m->channel_map; +                try_ss = *ss; +                try_ss.channels = try_map.channels; + +                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, +                              TRUE))) { +                    p->supported = FALSE; +                    break; +                } +            } + +        if (p->input_mappings && p->supported) +            PA_IDXSET_FOREACH(m, p->input_mappings, idx) { + +                if (m->input_pcm) +                    continue; + +                pa_log_debug("Checking for recording on %s (%s)", m->description, m->name); +                try_map = m->channel_map; +                try_ss = *ss; +                try_ss.channels = try_map.channels; + +                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, +                              TRUE))) { +                    p->supported = FALSE; +                    break; +                } +            } + +        last = p; + +        if (p->supported) +            pa_log_debug("Profile %s supported.", p->name); +    } + +    /* Clean up */ +    if (last) { +        uint32_t idx; + +        if (last->output_mappings) +            PA_IDXSET_FOREACH(m, last->output_mappings, idx) +                if (m->output_pcm) { + +                    if (last->supported) +                        m->supported++; + +                    snd_pcm_close(m->output_pcm); +                    m->output_pcm = NULL; +                } + +        if (last->input_mappings) +            PA_IDXSET_FOREACH(m, last->input_mappings, idx) +                if (m->input_pcm) { + +                    if (last->supported) +                        m->supported++; + +                    snd_pcm_close(m->input_pcm); +                    m->input_pcm = NULL; +                } +    } + +    PA_HASHMAP_FOREACH(p, ps->profiles, state) +        if (!p->supported) { +            pa_hashmap_remove(ps->profiles, p->name); +            profile_free(p); +        } + +    PA_HASHMAP_FOREACH(m, ps->mappings, state) +        if (m->supported <= 0) { +            pa_hashmap_remove(ps->mappings, m->name); +            mapping_free(m); +        } + +    ps->probed = TRUE; +} + +void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) { +    pa_alsa_profile *p; +    pa_alsa_mapping *m; +    void *state; + +    pa_assert(ps); + +    pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u", +                 (void*) +                 ps, +                 pa_yes_no(ps->auto_profiles), +                 pa_yes_no(ps->probed), +                 pa_hashmap_size(ps->mappings), +                 pa_hashmap_size(ps->profiles)); + +    PA_HASHMAP_FOREACH(m, ps->mappings, state) +        pa_alsa_mapping_dump(m); + +    PA_HASHMAP_FOREACH(p, ps->profiles, state) +        pa_alsa_profile_dump(p); +} + +void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps) { +    pa_alsa_path *path; + +    pa_assert(p); +    pa_assert(!*p); +    pa_assert(ps); + +    /* if there is no path, we don't want a port list */ +    if (!ps->paths) +        return; + +    if (!ps->paths->next){ +        pa_alsa_setting *s; + +        /* If there is only one path, but no or only one setting, then +         * we want a port list either */ +        if (!ps->paths->settings || !ps->paths->settings->next) +            return; + +        /* Ok, there is only one path, however with multiple settings, +         * so let's create a port for each setting */ +        *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + +        PA_LLIST_FOREACH(s, ps->paths->settings) { +            pa_device_port *port; +            pa_alsa_port_data *data; + +            port = pa_device_port_new(s->name, s->description, sizeof(pa_alsa_port_data)); +            port->priority = s->priority; + +            data = PA_DEVICE_PORT_DATA(port); +            data->path = ps->paths; +            data->setting = s; + +            pa_hashmap_put(*p, port->name, port); +        } + +    } else { + +        /* We have multiple paths, so let's create a port for each +         * one, and each of each settings */ +        *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + +        PA_LLIST_FOREACH(path, ps->paths) { + +            if (!path->settings || !path->settings->next) { +                pa_device_port *port; +                pa_alsa_port_data *data; + +                /* If there is no or just one setting we only need a +                 * single entry */ + +                port = pa_device_port_new(path->name, path->description, sizeof(pa_alsa_port_data)); +                port->priority = path->priority * 100; + + +                data = PA_DEVICE_PORT_DATA(port); +                data->path = path; +                data->setting = path->settings; + +                pa_hashmap_put(*p, port->name, port); +            } else { +                pa_alsa_setting *s; + +                PA_LLIST_FOREACH(s, path->settings) { +                    pa_device_port *port; +                    pa_alsa_port_data *data; +                    char *n, *d; + +                    n = pa_sprintf_malloc("%s;%s", path->name, s->name); + +                    if (s->description[0]) +                        d = pa_sprintf_malloc(_("%s / %s"), path->description, s->description); +                    else +                        d = pa_xstrdup(path->description); + +                    port = pa_device_port_new(n, d, sizeof(pa_alsa_port_data)); +                    port->priority = path->priority * 100 + s->priority; + +                    pa_xfree(n); +                    pa_xfree(d); + +                    data = PA_DEVICE_PORT_DATA(port); +                    data->path = path; +                    data->setting = s; + +                    pa_hashmap_put(*p, port->name, port); +                } +            } +        } +    } + +    pa_log_debug("Added %u ports", pa_hashmap_size(*p)); +} diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h new file mode 100644 index 00000000..76788183 --- /dev/null +++ b/src/modules/alsa/alsa-mixer.h @@ -0,0 +1,292 @@ +#ifndef fooalsamixerhfoo +#define fooalsamixerhfoo + +/*** +  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. +***/ + +#include <asoundlib.h> + +#include <pulse/sample.h> +#include <pulse/volume.h> +#include <pulse/mainloop-api.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> +#include <pulse/volume.h> + +#include <pulsecore/llist.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/core.h> +#include <pulsecore/log.h> + +typedef struct pa_alsa_fdlist pa_alsa_fdlist; +typedef struct pa_alsa_setting pa_alsa_setting; +typedef struct pa_alsa_option pa_alsa_option; +typedef struct pa_alsa_element pa_alsa_element; +typedef struct pa_alsa_path pa_alsa_path; +typedef struct pa_alsa_path_set pa_alsa_path_set; +typedef struct pa_alsa_mapping pa_alsa_mapping; +typedef struct pa_alsa_profile pa_alsa_profile; +typedef struct pa_alsa_profile_set pa_alsa_profile_set; +typedef struct pa_alsa_port_data pa_alsa_port_data; + +#include "alsa-util.h" + +typedef enum pa_alsa_switch_use { +    PA_ALSA_SWITCH_IGNORE, +    PA_ALSA_SWITCH_MUTE,   /* make this switch follow mute status */ +    PA_ALSA_SWITCH_OFF,    /* set this switch to 'off' unconditionally */ +    PA_ALSA_SWITCH_ON,     /* set this switch to 'on' unconditionally */ +    PA_ALSA_SWITCH_SELECT  /* allow the user to select switch status through a setting */ +} pa_alsa_switch_use_t; + +typedef enum pa_alsa_volume_use { +    PA_ALSA_VOLUME_IGNORE, +    PA_ALSA_VOLUME_MERGE,  /* merge this volume slider into the global volume slider */ +    PA_ALSA_VOLUME_OFF,    /* set this volume to minimal unconditionally */ +    PA_ALSA_VOLUME_ZERO    /* set this volume to 0dB unconditionally */ +} pa_alsa_volume_use_t; + +typedef enum pa_alsa_enumeration_use { +    PA_ALSA_ENUMERATION_IGNORE, +    PA_ALSA_ENUMERATION_SELECT +} pa_alsa_enumeration_use_t; + +typedef enum pa_alsa_required { +    PA_ALSA_REQUIRED_IGNORE, +    PA_ALSA_REQUIRED_SWITCH, +    PA_ALSA_REQUIRED_VOLUME, +    PA_ALSA_REQUIRED_ENUMERATION, +    PA_ALSA_REQUIRED_ANY +} pa_alsa_required_t; + +typedef enum pa_alsa_direction { +    PA_ALSA_DIRECTION_ANY, +    PA_ALSA_DIRECTION_OUTPUT, +    PA_ALSA_DIRECTION_INPUT +} pa_alsa_direction_t; + +/* A setting combines a couple of options into a single entity that + * may be selected. Only one setting can be active at the same + * time. */ +struct pa_alsa_setting { +    pa_alsa_path *path; +    PA_LLIST_FIELDS(pa_alsa_setting); + +    pa_idxset *options; + +    char *name; +    char *description; +    unsigned priority; +}; + +/* An option belongs to an element and refers to one enumeration item + * of the element is an enumeration item, or a switch status if the + * element is a switch item. */ +struct pa_alsa_option { +    pa_alsa_element *element; +    PA_LLIST_FIELDS(pa_alsa_option); + +    char *alsa_name; +    int alsa_idx; + +    char *name; +    char *description; +    unsigned priority; +}; + +/* And element wraps one specific ALSA element. A series of elements * +make up a path (see below). If the element is an enumeration or switch +* element it may includes a list of options. */ +struct pa_alsa_element { +    pa_alsa_path *path; +    PA_LLIST_FIELDS(pa_alsa_element); + +    char *alsa_name; +    pa_alsa_direction_t direction; + +    pa_alsa_switch_use_t switch_use; +    pa_alsa_volume_use_t volume_use; +    pa_alsa_enumeration_use_t enumeration_use; + +    pa_alsa_required_t required; +    pa_alsa_required_t required_absent; + +    pa_bool_t override_map:1; +    pa_bool_t direction_try_other:1; + +    pa_bool_t has_dB:1; +    long min_volume, max_volume; +    double min_dB, max_dB; + +    pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2]; +    unsigned n_channels; + +    pa_channel_position_mask_t merged_mask; + +    PA_LLIST_HEAD(pa_alsa_option, options); +}; + +/* A path wraps a series of elements into a single entity which can be + * used to control it as if it had a single volume slider, a single + * mute switch and a single list of selectable options. */ +struct pa_alsa_path { +    pa_alsa_path_set *path_set; +    PA_LLIST_FIELDS(pa_alsa_path); + +    pa_alsa_direction_t direction; + +    char *name; +    char *description; +    unsigned priority; + +    pa_bool_t probed:1; +    pa_bool_t supported:1; +    pa_bool_t has_mute:1; +    pa_bool_t has_volume:1; +    pa_bool_t has_dB:1; + +    long min_volume, max_volume; +    double min_dB, max_dB; + +    /* This is used during parsing only, as a shortcut so that we +     * don't have to iterate the list all the time */ +    pa_alsa_element *last_element; +    pa_alsa_option *last_option; +    pa_alsa_setting *last_setting; + +    PA_LLIST_HEAD(pa_alsa_element, elements); +    PA_LLIST_HEAD(pa_alsa_setting, settings); +}; + +/* A path set is simply a set of paths that are applicable to a + * device */ +struct pa_alsa_path_set { +    PA_LLIST_HEAD(pa_alsa_path, paths); +    pa_alsa_direction_t direction; +    pa_bool_t probed:1; + +    /* This is used during parsing only, as a shortcut so that we +     * don't have to iterate the list all the time */ +    pa_alsa_path *last_path; +}; + +int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m); +void pa_alsa_setting_dump(pa_alsa_setting *s); + +void pa_alsa_option_dump(pa_alsa_option *o); + +void pa_alsa_element_dump(pa_alsa_element *e); + +pa_alsa_path *pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction); +pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction); +int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB); +void pa_alsa_path_dump(pa_alsa_path *p); +int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v); +int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t *muted); +int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v); +int pa_alsa_path_set_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t muted); +int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m); +void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); +void pa_alsa_path_free(pa_alsa_path *p); + +pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction); +void pa_alsa_path_set_probe(pa_alsa_path_set *s, snd_mixer_t *m, pa_bool_t ignore_dB); +void pa_alsa_path_set_dump(pa_alsa_path_set *s); +void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); +void pa_alsa_path_set_free(pa_alsa_path_set *s); + +struct pa_alsa_mapping { +    pa_alsa_profile_set *profile_set; + +    char *name; +    char *description; +    unsigned priority; +    pa_alsa_direction_t direction; + +    pa_channel_map channel_map; + +    char **device_strings; + +    char **input_path_names; +    char **output_path_names; +    char **input_element; /* list of fallbacks */ +    char **output_element; + +    unsigned supported; + +    /* Temporarily used during probing */ +    snd_pcm_t *input_pcm; +    snd_pcm_t *output_pcm; + +    pa_sink *sink; +    pa_source *source; +}; + +struct pa_alsa_profile { +    pa_alsa_profile_set *profile_set; + +    char *name; +    char *description; +    unsigned priority; + +    pa_bool_t supported:1; + +    char **input_mapping_names; +    char **output_mapping_names; + +    pa_idxset *input_mappings; +    pa_idxset *output_mappings; +}; + +struct pa_alsa_profile_set { +    pa_hashmap *mappings; +    pa_hashmap *profiles; + +    pa_bool_t auto_profiles; +    pa_bool_t probed:1; +}; + +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_free(pa_alsa_profile_set *s); +void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); + +snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device); + +pa_alsa_fdlist *pa_alsa_fdlist_new(void); +void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl); +int pa_alsa_fdlist_set_mixer(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); + +/* Data structure for inclusion in pa_device_port for alsa + * sinks/sources. This contains nothing that needs to be freed + * individually */ +struct pa_alsa_port_data { +    pa_alsa_path *path; +    pa_alsa_setting *setting; +}; + +void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps); + +#endif diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index d95f3c2f..b14afd0a 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -32,16 +32,18 @@  #include <valgrind/memcheck.h>  #endif -#include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulse/timeval.h>  #include <pulse/i18n.h> +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/xmalloc.h>  #include <pulsecore/core.h>  #include <pulsecore/module.h>  #include <pulsecore/memchunk.h>  #include <pulsecore/sink.h>  #include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/sample-util.h>  #include <pulsecore/log.h> @@ -50,7 +52,6 @@  #include <pulsecore/core-error.h>  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h>  #include <pulsecore/time-smoother.h>  #include <modules/reserve-wrap.h> @@ -80,11 +81,9 @@ struct userdata {      pa_alsa_fdlist *mixer_fdl;      snd_mixer_t *mixer_handle; -    snd_mixer_elem_t *mixer_elem; -    long hw_volume_max, hw_volume_min; -    long hw_dB_max, hw_dB_min; -    pa_bool_t hw_dB_supported:1; -    pa_bool_t mixer_seperate_channels:1; +    pa_alsa_path_set *mixer_path_set; +    pa_alsa_path *mixer_path; +      pa_cvolume hardware_volume;      size_t @@ -100,7 +99,8 @@ struct userdata {      unsigned nfragments;      pa_memchunk memchunk; -    char *device_name; +    char *device_name;  /* name of the PCM device */ +    char *control_device; /* name of the control device */      pa_bool_t use_mmap:1, use_tsched:1; @@ -116,6 +116,8 @@ struct userdata {      pa_reserve_wrapper *reserve;      pa_hook_slot *reserve_slot; +    pa_reserve_monitor_wrapper *monitor; +    pa_hook_slot *monitor_slot;  };  static void userdata_free(struct userdata *u); @@ -124,7 +126,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u      pa_assert(r);      pa_assert(u); -    if (pa_sink_suspend(u->sink, TRUE) < 0) +    if (pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_APPLICATION) < 0)          return PA_HOOK_CANCEL;      return PA_HOOK_OK; @@ -167,10 +169,10 @@ static int reserve_init(struct userdata *u, const char *dname) {      if (pa_in_system_mode())          return 0; -    /* We are resuming, try to lock the device */      if (!(rname = pa_alsa_get_reserve_name(dname)))          return 0; +    /* We are resuming, try to lock the device */      u->reserve = pa_reserve_wrapper_get(u->core, rname);      pa_xfree(rname); @@ -185,6 +187,56 @@ static int reserve_init(struct userdata *u, const char *dname) {      return 0;  } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { +    pa_bool_t b; + +    pa_assert(w); +    pa_assert(u); + +    b = PA_PTR_TO_UINT(busy) && !u->reserve; + +    pa_sink_suspend(u->sink, b, PA_SUSPEND_APPLICATION); +    return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { +    pa_assert(u); + +    if (u->monitor_slot) { +        pa_hook_slot_free(u->monitor_slot); +        u->monitor_slot = NULL; +    } + +    if (u->monitor) { +        pa_reserve_monitor_wrapper_unref(u->monitor); +        u->monitor = NULL; +    } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { +    char *rname; + +    pa_assert(u); +    pa_assert(dname); + +    if (pa_in_system_mode()) +        return 0; + +    if (!(rname = pa_alsa_get_reserve_name(dname))) +        return 0; + +    u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); +    pa_xfree(rname); + +    if (!(u->monitor)) +        return -1; + +    pa_assert(!u->monitor_slot); +    u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + +    return 0; +} +  static void fix_min_sleep_wakeup(struct userdata *u) {      size_t max_use, max_use_2; @@ -240,7 +292,7 @@ static void adjust_after_underrun(struct userdata *u) {          pa_log_notice("Increasing minimal latency to %0.2f ms",                        (double) new_min_latency / PA_USEC_PER_MSEC); -        pa_sink_update_latency_range(u->sink, new_min_latency, u->sink->thread_info.max_latency); +        pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency);          return;      } @@ -281,7 +333,7 @@ static int try_recover(struct userdata *u, const char *call, int err) {      pa_assert(call);      pa_assert(err < 0); -    pa_log_debug("%s: %s", call, snd_strerror(err)); +    pa_log_debug("%s: %s", call, pa_alsa_strerror(err));      pa_assert(err != -EAGAIN); @@ -289,7 +341,7 @@ static int try_recover(struct userdata *u, const char *call, int err) {          pa_log_debug("%s: Buffer underrun!", call);      if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) { -        pa_log("%s: %s", call, snd_strerror(err)); +        pa_log("%s: %s", call, pa_alsa_strerror(err));          return -1;      } @@ -473,7 +525,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle              u->since_start += frames * u->frame_size;  #ifdef DEBUG_TIMING -            pa_log_debug("Wrote %lu bytes", (unsigned long) (frames * u->frame_size)); +            pa_log_debug("Wrote %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes);  #endif              if ((size_t) frames * u->frame_size >= n_bytes) @@ -483,7 +535,13 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle          }      } -    *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; +    *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec); + +    if (*sleep_usec > process_usec) +        *sleep_usec -= process_usec; +    else +        *sleep_usec = 0; +      return work_done ? 1 : 0;  } @@ -605,7 +663,13 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle          }      } -    *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; +    *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec); + +    if (*sleep_usec > process_usec) +        *sleep_usec -= process_usec; +    else +        *sleep_usec = 0; +      return work_done ? 1 : 0;  } @@ -624,12 +688,12 @@ static void update_smoother(struct userdata *u) {      /* Let's update the time smoother */      if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { -        pa_log_warn("Failed to query DSP status data: %s", snd_strerror(err)); +        pa_log_warn("Failed to query DSP status data: %s", pa_alsa_strerror(err));          return;      }      if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) -        pa_log_warn("Failed to get timestamp: %s", snd_strerror(err)); +        pa_log_warn("Failed to get timestamp: %s", pa_alsa_strerror(err));      else {          snd_htimestamp_t htstamp = { 0, 0 };          snd_pcm_status_get_htstamp(status, &htstamp); @@ -643,7 +707,7 @@ static void update_smoother(struct userdata *u) {      /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */      if (now1 <= 0) -        now1 = pa_rtclock_usec(); +        now1 = pa_rtclock_now();      now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec); @@ -657,7 +721,7 @@ static pa_usec_t sink_get_latency(struct userdata *u) {      pa_assert(u); -    now1 = pa_rtclock_usec(); +    now1 = pa_rtclock_now();      now2 = pa_smoother_get(u->smoother, now1);      delay = (int64_t) pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now2; @@ -688,7 +752,7 @@ static int suspend(struct userdata *u) {      pa_assert(u);      pa_assert(u->pcm_handle); -    pa_smoother_pause(u->smoother, pa_rtclock_usec()); +    pa_smoother_pause(u->smoother, pa_rtclock_now());      /* Let's suspend -- we don't call snd_pcm_drain() here since that might       * take awfully long with our long buffer sizes today. */ @@ -752,11 +816,11 @@ 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) { -        pa_log("Failed to set software parameters: %s", snd_strerror(err)); +        pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));          return err;      } -    pa_sink_set_max_request(u->sink, u->hwbuf_size - u->hwbuf_unused); +    pa_sink_set_max_request_within_thread(u->sink, u->hwbuf_size - u->hwbuf_unused);      return 0;  } @@ -774,13 +838,12 @@ static int unsuspend(struct userdata *u) {      pa_log_info("Trying resume..."); -    snd_config_update_free_global();      if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK,                              /*SND_PCM_NONBLOCK|*/                              SND_PCM_NO_AUTO_RESAMPLE|                              SND_PCM_NO_AUTO_CHANNELS|                              SND_PCM_NO_AUTO_FORMAT)) < 0) { -        pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); +        pa_log("Error opening PCM device %s: %s", u->device_name, pa_alsa_strerror(err));          goto fail;      } @@ -791,7 +854,7 @@ static int unsuspend(struct userdata *u) {      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) { -        pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); +        pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));          goto fail;      } @@ -919,198 +982,65 @@ 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); +        pa_sink_get_volume(u->sink, TRUE, FALSE);          pa_sink_get_mute(u->sink, TRUE);      }      return 0;  } -static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { - -    return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / -                               (double) (u->hw_volume_max - u->hw_volume_min)); -} - -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { -    long alsa_vol; - -    alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) -                            / PA_VOLUME_NORM) + u->hw_volume_min; - -    return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); -} -  static void sink_get_volume_cb(pa_sink *s) {      struct userdata *u = s->userdata; -    int err; -    unsigned i;      pa_cvolume r;      char t[PA_CVOLUME_SNPRINT_MAX];      pa_assert(u); -    pa_assert(u->mixer_elem); - -    if (u->mixer_seperate_channels) { - -        r.channels = s->sample_spec.channels; - -        for (i = 0; i < s->sample_spec.channels; i++) { -            long alsa_vol; - -            if (u->hw_dB_supported) { - -                if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -                VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -                r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); -            } else { - -                if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -                r.values[i] = from_alsa_volume(u, alsa_vol); -            } -        } - -    } else { -        long alsa_vol; - -        if (u->hw_dB_supported) { - -            if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) +        return; -            pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); -    if (!pa_cvolume_equal(&u->hardware_volume, &r)) { +    if (pa_cvolume_equal(&u->hardware_volume, &r)) +        return; -        s->virtual_volume = u->hardware_volume = r; +    s->virtual_volume = u->hardware_volume = r; -        if (u->hw_dB_supported) { -            pa_cvolume reset; +    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 */ +        pa_cvolume_reset(&reset, s->sample_spec.channels); +        pa_sink_set_soft_volume(s, &reset);      } - -    return; - -fail: -    pa_log_error("Unable to read volume: %s", snd_strerror(err));  }  static void sink_set_volume_cb(pa_sink *s) {      struct userdata *u = s->userdata; -    int err; -    unsigned i;      pa_cvolume r; +    char t[PA_CVOLUME_SNPRINT_MAX];      pa_assert(u); -    pa_assert(u->mixer_elem); - -    if (u->mixer_seperate_channels) { - -        r.channels = s->sample_spec.channels; - -        for (i = 0; i < s->sample_spec.channels; i++) { -            long alsa_vol; -            pa_volume_t vol; - -            vol = s->virtual_volume.values[i]; - -            if (u->hw_dB_supported) { - -                alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); -                alsa_vol += u->hw_dB_max; -                alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - -                if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) -                    goto fail; - -                if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -                VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -                r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - -            } else { -                alsa_vol = to_alsa_volume(u, vol); - -                if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) -                    goto fail; - -                if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -                r.values[i] = from_alsa_volume(u, alsa_vol); -            } -        } - -    } else { -        pa_volume_t vol; -        long alsa_vol; - -        vol = pa_cvolume_max(&s->virtual_volume); - -        if (u->hw_dB_supported) { -            alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); -            alsa_vol += u->hw_dB_max; -            alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - -            if ((err = snd_mixer_selem_set_playback_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) -                goto fail; - -            if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { -            alsa_vol = to_alsa_volume(u, vol); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_set_playback_volume_all(u->mixer_elem, alsa_vol)) < 0) -                goto fail; +    /* Shift up by the base volume */ +    pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume); -            if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) +        return; -            pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      u->hardware_volume = r; -    if (u->hw_dB_supported) { -        char t[PA_CVOLUME_SNPRINT_MAX]; +    if (u->mixer_path->has_dB) {          /* Match exactly what the user requested by software */          pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume); @@ -1119,45 +1049,75 @@ static void sink_set_volume_cb(pa_sink *s) {          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)); -    } else +    } else { +        pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));          /* We can't match exactly what the user requested, hence let's           * at least tell the user about it */          s->virtual_volume = r; - -    return; - -fail: -    pa_log_error("Unable to set volume: %s", snd_strerror(err)); +    }  }  static void sink_get_mute_cb(pa_sink *s) {      struct userdata *u = s->userdata; -    int err, sw; +    pa_bool_t b;      pa_assert(u); -    pa_assert(u->mixer_elem); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -    if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) { -        pa_log_error("Unable to get switch: %s", snd_strerror(err)); +    if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)          return; -    } -    s->muted = !sw; +    s->muted = b;  }  static void sink_set_mute_cb(pa_sink *s) {      struct userdata *u = s->userdata; -    int err;      pa_assert(u); -    pa_assert(u->mixer_elem); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -    if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) { -        pa_log_error("Unable to set switch: %s", snd_strerror(err)); -        return; +    pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted); +} + +static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { +    struct userdata *u = s->userdata; +    pa_alsa_port_data *data; + +    pa_assert(u); +    pa_assert(p); +    pa_assert(u->mixer_handle); + +    data = PA_DEVICE_PORT_DATA(p); + +    pa_assert_se(u->mixer_path = data->path); +    pa_alsa_path_select(u->mixer_path, u->mixer_handle); + +    if (u->mixer_path->has_volume && u->mixer_path->has_dB) { +        s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); +        s->n_volume_steps = PA_VOLUME_NORM+1; + +        if (u->mixer_path->max_dB > 0.0) +            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume)); +        else +            pa_log_info("No particular base volume set, fixing to 0 dB"); +    } else { +        s->base_volume = PA_VOLUME_NORM; +        s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;      } + +    if (data->setting) +        pa_alsa_setting_select(data->setting, u->mixer_handle); + +    if (s->set_mute) +        s->set_mute(s); +    if (s->set_volume) +        s->set_volume(s); + +    return 0;  }  static void sink_update_requested_latency_cb(pa_sink *s) { @@ -1173,7 +1133,7 @@ static void sink_update_requested_latency_cb(pa_sink *s) {      /* Let's check whether we now use only a smaller part of the      buffer then before. If so, we need to make sure that subsequent -    rewinds are relative to the new maxium fill level and not to the +    rewinds are relative to the new maximum fill level and not to the      current fill level. Thus, let's do a full rewind once, to clear      things up. */ @@ -1194,7 +1154,7 @@ static int process_rewind(struct userdata *u) {      pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);      if (PA_UNLIKELY((unused = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { -        pa_log("snd_pcm_avail() failed: %s", snd_strerror((int) unused)); +        pa_log("snd_pcm_avail() failed: %s", pa_alsa_strerror((int) unused));          return -1;      } @@ -1216,7 +1176,7 @@ static int process_rewind(struct userdata *u) {          in_frames = (snd_pcm_sframes_t) (rewind_nbytes / u->frame_size);          pa_log_debug("before: %lu", (unsigned long) in_frames);          if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) { -            pa_log("snd_pcm_rewind() failed: %s", snd_strerror((int) out_frames)); +            pa_log("snd_pcm_rewind() failed: %s", pa_alsa_strerror((int) out_frames));              return -1;          }          pa_log_debug("after: %lu", (unsigned long) out_frames); @@ -1252,7 +1212,6 @@ static void thread_func(void *userdata) {          pa_make_realtime(u->core->realtime_priority);      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          int ret; @@ -1286,7 +1245,7 @@ static void thread_func(void *userdata) {                      pa_log_info("Starting playback.");                      snd_pcm_start(u->pcm_handle); -                    pa_smoother_resume(u->smoother, pa_rtclock_usec()); +                    pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);                  }                  update_smoother(u); @@ -1300,7 +1259,7 @@ static void thread_func(void *userdata) {                      /* USB devices on ALSA seem to hit a buffer                       * underrun during the first iterations much                       * quicker then we calculate here, probably due to -                     * the transport latency. To accomodate for that +                     * the transport latency. To accommodate for that                       * we artificially decrease the sleep time until                       * we have filled the buffer at least once                       * completely.*/ @@ -1315,7 +1274,7 @@ static void thread_func(void *userdata) {                  /* Convert from the sound card time domain to the                   * system time domain */ -                cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); +                cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec);  /*                 pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ @@ -1347,7 +1306,7 @@ static void thread_func(void *userdata) {              pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);              if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { -                pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); +                pa_log("snd_pcm_poll_descriptors_revents() failed: %s", pa_alsa_strerror(err));                  goto fail;              } @@ -1374,7 +1333,7 @@ finish:      pa_log_debug("Thread shutting down");  } -static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name) { +static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) {      const char *n;      char *t; @@ -1395,82 +1354,136 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de          data->namereg_fail = FALSE;      } -    t = pa_sprintf_malloc("alsa_output.%s", n); +    if (mapping) +        t = pa_sprintf_malloc("alsa_output.%s.%s", n, mapping->name); +    else +        t = pa_sprintf_malloc("alsa_output.%s", n); +      pa_sink_new_data_set_name(data, t);      pa_xfree(t);  } +static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) { + +    if (!mapping && !element) +        return; + +    if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { +        pa_log_info("Failed to find a working mixer device."); +        return; +    } + +    if (element) { + +        if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT))) +            goto fail; + +        if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0) +            goto fail; + +        pa_log_debug("Probed mixer path %s:", u->mixer_path->name); +        pa_alsa_path_dump(u->mixer_path); +    } else { + +        if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_OUTPUT))) +            goto fail; + +        pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB); + +        pa_log_debug("Probed mixer paths:"); +        pa_alsa_path_set_dump(u->mixer_path_set); +    } + +    return; + +fail: + +    if (u->mixer_path_set) { +        pa_alsa_path_set_free(u->mixer_path_set); +        u->mixer_path_set = NULL; +    } else if (u->mixer_path) { +        pa_alsa_path_free(u->mixer_path); +        u->mixer_path = NULL; +    } + +    if (u->mixer_handle) { +        snd_mixer_close(u->mixer_handle); +        u->mixer_handle = NULL; +    } +} +  static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {      pa_assert(u);      if (!u->mixer_handle)          return 0; -    pa_assert(u->mixer_elem); +    if (u->sink->active_port) { +        pa_alsa_port_data *data; -    if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { -        pa_bool_t suitable = FALSE; +        /* We have a list of supported paths, so let's activate the +         * one that has been chosen as active */ -        if (snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) -            pa_log_info("Failed to get volume range. Falling back to software volume control."); -        else if (u->hw_volume_min >= u->hw_volume_max) -            pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); -        else { -            pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); -            suitable = TRUE; -        } +        data = PA_DEVICE_PORT_DATA(u->sink->active_port); +        u->mixer_path = data->path; -        if (suitable) { -            if (ignore_dB || snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) -                pa_log_info("Mixer doesn't support dB information or data is ignored."); -            else { -#ifdef HAVE_VALGRIND_MEMCHECK_H -                VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); -                VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); -#endif +        pa_alsa_path_select(data->path, u->mixer_handle); -                if (u->hw_dB_min >= u->hw_dB_max) -                    pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); -                else { -                    pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); -                    u->hw_dB_supported = TRUE; - -                    if (u->hw_dB_max > 0) { -                        u->sink->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); -                        pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); -                    } else -                        pa_log_info("No particular base volume set, fixing to 0 dB"); -                } -            } +        if (data->setting) +            pa_alsa_setting_select(data->setting, u->mixer_handle); -            if (!u->hw_dB_supported && -                u->hw_volume_max - u->hw_volume_min < 3) { +    } else { -                pa_log_info("Device doesn't do dB volume and has less than 4 volume levels. Falling back to software volume control."); -                suitable = FALSE; -            } -        } +        if (!u->mixer_path && u->mixer_path_set) +            u->mixer_path = u->mixer_path_set->paths; -        if (suitable) { -            u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &u->sink->channel_map, u->mixer_map, TRUE) >= 0; +        if (u->mixer_path) { +            /* Hmm, we have only a single path, then let's activate it */ -            u->sink->get_volume = sink_get_volume_cb; -            u->sink->set_volume = sink_set_volume_cb; -            u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); -            pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); +            pa_alsa_path_select(u->mixer_path, u->mixer_handle); -            if (!u->hw_dB_supported) -                u->sink->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1; +            if (u->mixer_path->settings) +                pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle);          } else -            pa_log_info("Using software volume control."); +            return 0;      } -    if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { +    if (!u->mixer_path->has_volume) +        pa_log_info("Driver does not support hardware volume control, falling back to software volume control."); +    else { + +        if (u->mixer_path->has_dB) { +            pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB); + +            u->sink->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); +            u->sink->n_volume_steps = PA_VOLUME_NORM+1; + +            if (u->mixer_path->max_dB > 0.0) +                pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); +            else +                pa_log_info("No particular base volume set, fixing to 0 dB"); + +        } else { +            pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume); +            u->sink->base_volume = PA_VOLUME_NORM; +            u->sink->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1; +        } + +        u->sink->get_volume = sink_get_volume_cb; +        u->sink->set_volume = sink_set_volume_cb; + +        u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SINK_DECIBEL_VOLUME : 0); +        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); +    } + +    if (!u->mixer_path->has_mute) { +        pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); +    } else {          u->sink->get_mute = sink_get_mute_cb;          u->sink->set_mute = sink_set_mute_cb;          u->sink->flags |= PA_SINK_HW_MUTE_CTRL; -    } else -        pa_log_info("Using software mute control."); +        pa_log_info("Using hardware mute control."); +    }      u->mixer_fdl = pa_alsa_fdlist_new(); @@ -1479,13 +1492,15 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {          return -1;      } -    snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); -    snd_mixer_elem_set_callback_private(u->mixer_elem, u); +    if (u->mixer_path_set) +        pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u); +    else +        pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u);      return 0;  } -pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile) { +pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) {      struct userdata *u = NULL;      const char *dev_id = NULL; @@ -1495,8 +1510,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      snd_pcm_uframes_t period_frames, tsched_frames;      size_t frame_size;      pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; -    pa_usec_t usec;      pa_sink_new_data data; +    pa_alsa_profile_set *profile_set = NULL;      pa_assert(m);      pa_assert(ma); @@ -1559,45 +1574,57 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      u->rtpoll = pa_rtpoll_new();      pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); -    u->smoother = pa_smoother_new(DEFAULT_TSCHED_BUFFER_USEC*2, DEFAULT_TSCHED_BUFFER_USEC*2, TRUE, 5); -    usec = pa_rtclock_usec(); -    pa_smoother_set_time_offset(u->smoother, usec); -    pa_smoother_pause(u->smoother, usec); +    u->smoother = pa_smoother_new( +            DEFAULT_TSCHED_BUFFER_USEC*2, +            DEFAULT_TSCHED_BUFFER_USEC*2, +            TRUE, +            TRUE, +            5, +            pa_rtclock_now(), +            TRUE); + +    dev_id = pa_modargs_get_value( +            ma, "device_id", +            pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + +    if (reserve_init(u, dev_id) < 0) +        goto fail; -    if (reserve_init(u, pa_modargs_get_value( -                             ma, "device_id", -                             pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) +    if (reserve_monitor_init(u, dev_id) < 0)          goto fail;      b = use_mmap;      d = use_tsched; -    if (profile) { +    if (mapping) {          if (!(dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {              pa_log("device_id= not set");              goto fail;          } -        if (!(u->pcm_handle = pa_alsa_open_by_device_id_profile( +        if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping(                        dev_id,                        &u->device_name,                        &ss, &map,                        SND_PCM_STREAM_PLAYBACK,                        &nfrags, &period_frames, tsched_frames, -                      &b, &d, profile))) +                      &b, &d, mapping)))              goto fail;      } else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { +        if (!(profile_set = pa_alsa_profile_set_new(NULL, &map))) +            goto fail; +          if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto(                        dev_id,                        &u->device_name,                        &ss, &map,                        SND_PCM_STREAM_PLAYBACK,                        &nfrags, &period_frames, tsched_frames, -                      &b, &d, &profile))) +                      &b, &d, profile_set, &mapping)))              goto fail; @@ -1611,14 +1638,18 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca                        &nfrags, &period_frames, tsched_frames,                        &b, &d, FALSE)))              goto fail; -      }      pa_assert(u->device_name);      pa_log_info("Successfully opened device %s.", u->device_name); -    if (profile) -        pa_log_info("Selected configuration '%s' (%s).", profile->description, profile->name); +    if (pa_alsa_pcm_is_modem(u->pcm_handle)) { +        pa_log_notice("Device %s is modem, refusing further initialization.", u->device_name); +        goto fail; +    } + +    if (mapping) +        pa_log_info("Selected mapping '%s' (%s).", mapping->description, mapping->name);      if (use_mmap && !b) {          pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); @@ -1630,6 +1661,11 @@ 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."); @@ -1639,13 +1675,13 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      /* ALSA might tweak the sample spec, so recalculate the frame size */      frame_size = pa_frame_size(&ss); -    pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem); +    find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);      pa_sink_new_data_init(&data);      data.driver = driver;      data.module = m;      data.card = card; -    set_sink_name(&data, ma, dev_id, u->device_name); +    set_sink_name(&data, ma, dev_id, u->device_name, mapping);      pa_sink_new_data_set_sample_spec(&data, &ss);      pa_sink_new_data_set_channel_map(&data, &map); @@ -1655,14 +1691,26 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      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")); -    if (profile) { -        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, profile->name); -        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, profile->description); +    if (mapping) { +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name); +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description);      }      pa_alsa_init_description(data.proplist); -    u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); +    if (u->control_device) +        pa_alsa_init_proplist_ctl(data.proplist, u->control_device); + +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } + +    if (u->mixer_path_set) +        pa_alsa_add_ports(&data.ports, u->mixer_path_set); + +    u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY|(u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0));      pa_sink_new_data_done(&data);      if (!u->sink) { @@ -1673,6 +1721,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      u->sink->parent.process_msg = sink_process_msg;      u->sink->update_requested_latency = sink_update_requested_latency_cb;      u->sink->set_state = sink_set_state_cb; +    u->sink->set_port = sink_set_port_cb;      u->sink->userdata = u;      pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); @@ -1685,26 +1734,27 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);      pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels); -    if (use_tsched) { -        fix_min_sleep_wakeup(u); -        fix_tsched_watermark(u); +    pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", +                nfrags, (long unsigned) u->fragment_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); -    } -    pa_sink_set_max_rewind(u->sink, use_tsched ? u->hwbuf_size : 0); -    pa_sink_set_max_request(u->sink, u->hwbuf_size); -    pa_sink_set_latency_range(u->sink, -                              use_tsched ? (pa_usec_t) -1 : pa_bytes_to_usec(u->hwbuf_size, &ss), -                              pa_bytes_to_usec(u->hwbuf_size, &ss)); +        fix_min_sleep_wakeup(u); +        fix_tsched_watermark(u); -    pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", -                nfrags, (long unsigned) u->fragment_size, -                (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); +        pa_sink_set_latency_range(u->sink, +                                  0, +                                  pa_bytes_to_usec(u->hwbuf_size, &ss)); -    if (use_tsched)          pa_log_info("Time scheduling watermark is %0.2fms",                      (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); +    } else +        pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss));      reserve_update(u); @@ -1714,7 +1764,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      if (setup_mixer(u, ignore_dB) < 0)          goto fail; -    pa_alsa_dump(u->pcm_handle); +    pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log("Failed to create thread."); @@ -1740,11 +1790,18 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      pa_sink_put(u->sink); +    if (profile_set) +        pa_alsa_profile_set_free(profile_set); +      return u->sink;  fail: -    userdata_free(u); +    if (u) +        userdata_free(u); + +    if (profile_set) +        pa_alsa_profile_set_free(profile_set);      return NULL;  } @@ -1774,23 +1831,30 @@ static void userdata_free(struct userdata *u) {      if (u->rtpoll)          pa_rtpoll_free(u->rtpoll); +    if (u->pcm_handle) { +        snd_pcm_drop(u->pcm_handle); +        snd_pcm_close(u->pcm_handle); +    } +      if (u->mixer_fdl)          pa_alsa_fdlist_free(u->mixer_fdl); +    if (u->mixer_path_set) +        pa_alsa_path_set_free(u->mixer_path_set); +    else if (u->mixer_path) +        pa_alsa_path_free(u->mixer_path); +      if (u->mixer_handle)          snd_mixer_close(u->mixer_handle); -    if (u->pcm_handle) { -        snd_pcm_drop(u->pcm_handle); -        snd_pcm_close(u->pcm_handle); -    } -      if (u->smoother)          pa_smoother_free(u->smoother);      reserve_done(u); +    monitor_done(u);      pa_xfree(u->device_name); +    pa_xfree(u->control_device);      pa_xfree(u);  } diff --git a/src/modules/alsa/alsa-sink.h b/src/modules/alsa/alsa-sink.h index bbf64234..b9a4ac2a 100644 --- a/src/modules/alsa/alsa-sink.h +++ b/src/modules/alsa/alsa-sink.h @@ -28,8 +28,9 @@  #include <pulsecore/sink.h>  #include "alsa-util.h" +#include "alsa-mixer.h" -pa_sink* pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile); +pa_sink* pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping);  void pa_alsa_sink_free(pa_sink *s); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 63b5e460..13a2c186 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -28,14 +28,11 @@  #include <asoundlib.h> -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include <valgrind/memcheck.h> -#endif - -#include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulse/timeval.h>  #include <pulse/i18n.h> +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/xmalloc.h>  #include <pulsecore/core-error.h>  #include <pulsecore/core.h> @@ -43,6 +40,7 @@  #include <pulsecore/memchunk.h>  #include <pulsecore/sink.h>  #include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/sample-util.h>  #include <pulsecore/log.h> @@ -52,7 +50,6 @@  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h>  #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h>  #include <modules/reserve-wrap.h> @@ -81,11 +78,8 @@ struct userdata {      pa_alsa_fdlist *mixer_fdl;      snd_mixer_t *mixer_handle; -    snd_mixer_elem_t *mixer_elem; -    long hw_volume_max, hw_volume_min; -    long hw_dB_max, hw_dB_min; -    pa_bool_t hw_dB_supported:1; -    pa_bool_t mixer_seperate_channels:1; +    pa_alsa_path_set *mixer_path_set; +    pa_alsa_path *mixer_path;      pa_cvolume hardware_volume; @@ -102,6 +96,7 @@ struct userdata {      unsigned nfragments;      char *device_name; +    char *control_device;      pa_bool_t use_mmap:1, use_tsched:1; @@ -114,6 +109,8 @@ struct userdata {      pa_reserve_wrapper *reserve;      pa_hook_slot *reserve_slot; +    pa_reserve_monitor_wrapper *monitor; +    pa_hook_slot *monitor_slot;  };  static void userdata_free(struct userdata *u); @@ -122,7 +119,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u      pa_assert(r);      pa_assert(u); -    if (pa_source_suspend(u->source, TRUE) < 0) +    if (pa_source_suspend(u->source, TRUE, PA_SUSPEND_APPLICATION) < 0)          return PA_HOOK_CANCEL;      return PA_HOOK_OK; @@ -183,6 +180,57 @@ static int reserve_init(struct userdata *u, const char *dname) {      return 0;  } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { +    pa_bool_t b; + +    pa_assert(w); +    pa_assert(u); + +    b = PA_PTR_TO_UINT(busy) && !u->reserve; + +    pa_source_suspend(u->source, b, PA_SUSPEND_APPLICATION); +    return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { +    pa_assert(u); + +    if (u->monitor_slot) { +        pa_hook_slot_free(u->monitor_slot); +        u->monitor_slot = NULL; +    } + +    if (u->monitor) { +        pa_reserve_monitor_wrapper_unref(u->monitor); +        u->monitor = NULL; +    } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { +    char *rname; + +    pa_assert(u); +    pa_assert(dname); + +    if (pa_in_system_mode()) +        return 0; + +    /* We are resuming, try to lock the device */ +    if (!(rname = pa_alsa_get_reserve_name(dname))) +        return 0; + +    u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); +    pa_xfree(rname); + +    if (!(u->monitor)) +        return -1; + +    pa_assert(!u->monitor_slot); +    u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + +    return 0; +} +  static void fix_min_sleep_wakeup(struct userdata *u) {      size_t max_use, max_use_2;      pa_assert(u); @@ -238,7 +286,7 @@ static void adjust_after_overrun(struct userdata *u) {          pa_log_notice("Increasing minimal latency to %0.2f ms",                        (double) new_min_latency / PA_USEC_PER_MSEC); -        pa_source_update_latency_range(u->source, new_min_latency, u->source->thread_info.max_latency); +        pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);          return;      } @@ -278,7 +326,7 @@ static int try_recover(struct userdata *u, const char *call, int err) {      pa_assert(call);      pa_assert(err < 0); -    pa_log_debug("%s: %s", call, snd_strerror(err)); +    pa_log_debug("%s: %s", call, pa_alsa_strerror(err));      pa_assert(err != -EAGAIN); @@ -286,7 +334,7 @@ static int try_recover(struct userdata *u, const char *call, int err) {          pa_log_debug("%s: Buffer overrun!", call);      if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) { -        pa_log("%s: %s", call, snd_strerror(err)); +        pa_log("%s: %s", call, pa_alsa_strerror(err));          return -1;      } @@ -455,7 +503,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled              u->read_count += frames * u->frame_size;  #ifdef DEBUG_TIMING -            pa_log_debug("Read %lu bytes", (unsigned long) (frames * u->frame_size)); +            pa_log_debug("Read %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes);  #endif              if ((size_t) frames * u->frame_size >= n_bytes) @@ -465,7 +513,13 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled          }      } -    *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; +    *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec); + +    if (*sleep_usec > process_usec) +        *sleep_usec -= process_usec; +    else +        *sleep_usec = 0; +      return work_done ? 1 : 0;  } @@ -575,7 +629,13 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled          }      } -    *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; +    *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec); + +    if (*sleep_usec > process_usec) +        *sleep_usec -= process_usec; +    else +        *sleep_usec = 0; +      return work_done ? 1 : 0;  } @@ -594,12 +654,12 @@ static void update_smoother(struct userdata *u) {      /* Let's update the time smoother */      if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->source->sample_spec)) < 0)) { -        pa_log_warn("Failed to get delay: %s", snd_strerror(err)); +        pa_log_warn("Failed to get delay: %s", pa_alsa_strerror(err));          return;      }      if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) -        pa_log_warn("Failed to get timestamp: %s", snd_strerror(err)); +        pa_log_warn("Failed to get timestamp: %s", pa_alsa_strerror(err));      else {          snd_htimestamp_t htstamp = { 0, 0 };          snd_pcm_status_get_htstamp(status, &htstamp); @@ -610,7 +670,7 @@ static void update_smoother(struct userdata *u) {      /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */      if (now1 <= 0) -        now1 = pa_rtclock_usec(); +        now1 = pa_rtclock_now();      now2 = pa_bytes_to_usec(position, &u->source->sample_spec); @@ -623,7 +683,7 @@ static pa_usec_t source_get_latency(struct userdata *u) {      pa_assert(u); -    now1 = pa_rtclock_usec(); +    now1 = pa_rtclock_now();      now2 = pa_smoother_get(u->smoother, now1);      delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec(u->read_count, &u->source->sample_spec); @@ -648,7 +708,7 @@ static int suspend(struct userdata *u) {      pa_assert(u);      pa_assert(u->pcm_handle); -    pa_smoother_pause(u->smoother, pa_rtclock_usec()); +    pa_smoother_pause(u->smoother, pa_rtclock_now());      /* Let's suspend */      snd_pcm_close(u->pcm_handle); @@ -709,7 +769,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) { -        pa_log("Failed to set software parameters: %s", snd_strerror(err)); +        pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));          return err;      } @@ -728,14 +788,12 @@ static int unsuspend(struct userdata *u) {      pa_log_info("Trying resume..."); -    snd_config_update_free_global(); -      if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE,                              /*SND_PCM_NONBLOCK|*/                              SND_PCM_NO_AUTO_RESAMPLE|                              SND_PCM_NO_AUTO_CHANNELS|                              SND_PCM_NO_AUTO_FORMAT)) < 0) { -        pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); +        pa_log("Error opening PCM device %s: %s", u->device_name, pa_alsa_strerror(err));          goto fail;      } @@ -746,7 +804,7 @@ static int unsuspend(struct userdata *u) {      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) { -        pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); +        pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));          goto fail;      } @@ -776,7 +834,7 @@ static int unsuspend(struct userdata *u) {      /* FIXME: We need to reload the volume somehow */      snd_pcm_start(u->pcm_handle); -    pa_smoother_resume(u->smoother, pa_rtclock_usec()); +    pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);      pa_log_info("Resumed successfully..."); @@ -884,239 +942,135 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {      return 0;  } -static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { - -    return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / -                               (double) (u->hw_volume_max - u->hw_volume_min)); -} - -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { -    long alsa_vol; - -    alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) -                            / PA_VOLUME_NORM) + u->hw_volume_min; - -    return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); -} -  static void source_get_volume_cb(pa_source *s) {      struct userdata *u = s->userdata; -    int err; -    unsigned i;      pa_cvolume r;      char t[PA_CVOLUME_SNPRINT_MAX];      pa_assert(u); -    pa_assert(u->mixer_elem); - -    if (u->mixer_seperate_channels) { - -        r.channels = s->sample_spec.channels; - -        for (i = 0; i < s->sample_spec.channels; i++) { -            long alsa_vol; - -            if (u->hw_dB_supported) { - -                if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -                VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -                r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); -            } else { - -                if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -                r.values[i] = from_alsa_volume(u, alsa_vol); -            } -        } - -    } else { -        long alsa_vol; - -        if (u->hw_dB_supported) { - -            if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) +        return; -            pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); -    if (!pa_cvolume_equal(&u->hardware_volume, &r)) { +    if (pa_cvolume_equal(&u->hardware_volume, &r)) +        return; -        s->virtual_volume = u->hardware_volume = r; +    s->virtual_volume = u->hardware_volume = r; -        if (u->hw_dB_supported) { -            pa_cvolume reset; +    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_source_set_soft_volume(s, &reset); -        } +        /* 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);      } - -    return; - -fail: -    pa_log_error("Unable to read volume: %s", snd_strerror(err));  }  static void source_set_volume_cb(pa_source *s) {      struct userdata *u = s->userdata; -    int err; -    unsigned i;      pa_cvolume r; +    char t[PA_CVOLUME_SNPRINT_MAX];      pa_assert(u); -    pa_assert(u->mixer_elem); - -    if (u->mixer_seperate_channels) { - -        r.channels = s->sample_spec.channels; - -        for (i = 0; i < s->sample_spec.channels; i++) { -            long alsa_vol; -            pa_volume_t vol; - -            vol = s->virtual_volume.values[i]; - -            if (u->hw_dB_supported) { - -                alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); -                alsa_vol += u->hw_dB_max; -                alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - -                if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) -                    goto fail; - -                if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -                VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -                r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - -            } else { -                alsa_vol = to_alsa_volume(u, vol); - -                if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) -                    goto fail; - -                if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) -                    goto fail; - -                r.values[i] = from_alsa_volume(u, alsa_vol); -            } -        } - -    } else { -        pa_volume_t vol; -        long alsa_vol; - -        vol = pa_cvolume_max(&s->virtual_volume); - -        if (u->hw_dB_supported) { -            alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); -            alsa_vol += u->hw_dB_max; -            alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - -            if ((err = snd_mixer_selem_set_capture_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) -                goto fail; - -            if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { -            alsa_vol = to_alsa_volume(u, vol); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0) -                goto fail; +    /* Shift up by the base volume */ +    pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume); -            if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) +        return; -            pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      u->hardware_volume = r; -    if (u->hw_dB_supported) { -        char t[PA_CVOLUME_SNPRINT_MAX]; +    if (u->mixer_path->has_dB) {          /* Match exactly what the user requested by software */ -          pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);          pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_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)); -    } else +    } else { +        pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));          /* We can't match exactly what the user requested, hence let's           * at least tell the user about it */          s->virtual_volume = r; - -    return; - -fail: -    pa_log_error("Unable to set volume: %s", snd_strerror(err)); +    }  }  static void source_get_mute_cb(pa_source *s) {      struct userdata *u = s->userdata; -    int err, sw; +    pa_bool_t b;      pa_assert(u); -    pa_assert(u->mixer_elem); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -    if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) { -        pa_log_error("Unable to get switch: %s", snd_strerror(err)); +    if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)          return; -    } -    s->muted = !sw; +    s->muted = b;  }  static void source_set_mute_cb(pa_source *s) {      struct userdata *u = s->userdata; -    int err;      pa_assert(u); -    pa_assert(u->mixer_elem); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -    if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) { -        pa_log_error("Unable to set switch: %s", snd_strerror(err)); -        return; +    pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted); +} + +static int source_set_port_cb(pa_source *s, pa_device_port *p) { +    struct userdata *u = s->userdata; +    pa_alsa_port_data *data; + +    pa_assert(u); +    pa_assert(p); +    pa_assert(u->mixer_handle); + +    data = PA_DEVICE_PORT_DATA(p); + +    pa_assert_se(u->mixer_path = data->path); +    pa_alsa_path_select(u->mixer_path, u->mixer_handle); + +    if (u->mixer_path->has_volume && u->mixer_path->has_dB) { +        s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); +        s->n_volume_steps = PA_VOLUME_NORM+1; + +        if (u->mixer_path->max_dB > 0.0) +            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume)); +        else +            pa_log_info("No particular base volume set, fixing to 0 dB"); +    } else { +        s->base_volume = PA_VOLUME_NORM; +        s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;      } + +    if (data->setting) +        pa_alsa_setting_select(data->setting, u->mixer_handle); + +    if (s->set_mute) +        s->set_mute(s); +    if (s->set_volume) +        s->set_volume(s); + +    return 0;  }  static void source_update_requested_latency_cb(pa_source *s) { @@ -1141,7 +1095,6 @@ static void thread_func(void *userdata) {          pa_make_realtime(u->core->realtime_priority);      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          int ret; @@ -1178,7 +1131,7 @@ static void thread_func(void *userdata) {                  /* Convert from the sound card time domain to the                   * system time domain */ -                cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); +                cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec);  /*                 pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ @@ -1206,7 +1159,7 @@ static void thread_func(void *userdata) {              pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);              if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { -                pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); +                pa_log("snd_pcm_poll_descriptors_revents() failed: %s", pa_alsa_strerror(err));                  goto fail;              } @@ -1232,7 +1185,7 @@ finish:      pa_log_debug("Thread shutting down");  } -static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name) { +static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) {      const char *n;      char *t; @@ -1253,82 +1206,136 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char          data->namereg_fail = FALSE;      } -    t = pa_sprintf_malloc("alsa_input.%s", n); +    if (mapping) +        t = pa_sprintf_malloc("alsa_input.%s.%s", n, mapping->name); +    else +        t = pa_sprintf_malloc("alsa_input.%s", n); +      pa_source_new_data_set_name(data, t);      pa_xfree(t);  } +static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) { + +    if (!mapping && !element) +        return; + +    if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { +        pa_log_info("Failed to find a working mixer device."); +        return; +    } + +    if (element) { + +        if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT))) +            goto fail; + +        if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0) +            goto fail; + +        pa_log_debug("Probed mixer path %s:", u->mixer_path->name); +        pa_alsa_path_dump(u->mixer_path); +    } else { + +        if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_INPUT))) +            goto fail; + +        pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB); + +        pa_log_debug("Probed mixer paths:"); +        pa_alsa_path_set_dump(u->mixer_path_set); +    } + +    return; + +fail: + +    if (u->mixer_path_set) { +        pa_alsa_path_set_free(u->mixer_path_set); +        u->mixer_path_set = NULL; +    } else if (u->mixer_path) { +        pa_alsa_path_free(u->mixer_path); +        u->mixer_path = NULL; +    } + +    if (u->mixer_handle) { +        snd_mixer_close(u->mixer_handle); +        u->mixer_handle = NULL; +    } +} +  static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {      pa_assert(u);      if (!u->mixer_handle)          return 0; -    pa_assert(u->mixer_elem); +    if (u->source->active_port) { +        pa_alsa_port_data *data; -    if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { -        pa_bool_t suitable = FALSE; +        /* We have a list of supported paths, so let's activate the +         * one that has been chosen as active */ -        if (snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) -            pa_log_info("Failed to get volume range. Falling back to software volume control."); -        else if (u->hw_volume_min >= u->hw_volume_max) -            pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); -        else { -            pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); -            suitable = TRUE; -        } +        data = PA_DEVICE_PORT_DATA(u->source->active_port); +        u->mixer_path = data->path; -        if (suitable) { -            if (ignore_dB || snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) -                pa_log_info("Mixer doesn't support dB information or data is ignored."); -            else { -#ifdef HAVE_VALGRIND_MEMCHECK_H -                VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); -                VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); -#endif +        pa_alsa_path_select(data->path, u->mixer_handle); -                if (u->hw_dB_min >= u->hw_dB_max) -                    pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); -                else { -                    pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); -                    u->hw_dB_supported = TRUE; - -                    if (u->hw_dB_max > 0) { -                        u->source->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); -                        pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume)); -                    } else -                        pa_log_info("No particular base volume set, fixing to 0 dB"); -                } -            } +        if (data->setting) +            pa_alsa_setting_select(data->setting, u->mixer_handle); -            if (!u->hw_dB_supported && -                u->hw_volume_max - u->hw_volume_min < 3) { +    } else { -                pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); -                suitable = FALSE; -            } -        } +        if (!u->mixer_path && u->mixer_path_set) +            u->mixer_path = u->mixer_path_set->paths; -        if (suitable) { -            u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &u->source->channel_map, u->mixer_map, FALSE) >= 0; +        if (u->mixer_path) { +            /* Hmm, we have only a single path, then let's activate it */ -            u->source->get_volume = source_get_volume_cb; -            u->source->set_volume = source_set_volume_cb; -            u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0); -            pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); +            pa_alsa_path_select(u->mixer_path, u->mixer_handle); -            if (!u->hw_dB_supported) -                u->source->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1; +            if (u->mixer_path->settings) +                pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle);          } else -            pa_log_info("Using software volume control."); +            return 0; +    } + +    if (!u->mixer_path->has_volume) +        pa_log_info("Driver does not support hardware volume control, falling back to software volume control."); +    else { + +        if (u->mixer_path->has_dB) { +            pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB); + +            u->source->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); +            u->source->n_volume_steps = PA_VOLUME_NORM+1; + +            if (u->mixer_path->max_dB > 0.0) +                pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume)); +            else +                pa_log_info("No particular base volume set, fixing to 0 dB"); + +        } else { +            pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume); +            u->source->base_volume = PA_VOLUME_NORM; +            u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1; +        } + +        u->source->get_volume = source_get_volume_cb; +        u->source->set_volume = source_set_volume_cb; + +        u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SOURCE_DECIBEL_VOLUME : 0); +        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");      } -    if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { +    if (!u->mixer_path->has_mute) { +        pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); +    } else {          u->source->get_mute = source_get_mute_cb;          u->source->set_mute = source_set_mute_cb;          u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; -    } else -        pa_log_info("Using software mute control."); +        pa_log_info("Using hardware mute control."); +    }      u->mixer_fdl = pa_alsa_fdlist_new(); @@ -1337,13 +1344,15 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {          return -1;      } -    snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); -    snd_mixer_elem_set_callback_private(u->mixer_elem, u); +    if (u->mixer_path_set) +        pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u); +    else +        pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u);      return 0;  } -pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile) { +pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) {      struct userdata *u = NULL;      const char *dev_id = NULL; @@ -1354,6 +1363,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      size_t frame_size;      pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;      pa_source_new_data data; +    pa_alsa_profile_set *profile_set = NULL;      pa_assert(m);      pa_assert(ma); @@ -1414,44 +1424,57 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      u->use_tsched = use_tsched;      u->rtpoll = pa_rtpoll_new();      pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); -    u->alsa_rtpoll_item = NULL; -    u->smoother = pa_smoother_new(DEFAULT_TSCHED_WATERMARK_USEC*2, DEFAULT_TSCHED_WATERMARK_USEC*2, TRUE, 5); -    pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); +    u->smoother = pa_smoother_new( +            DEFAULT_TSCHED_WATERMARK_USEC*2, +            DEFAULT_TSCHED_WATERMARK_USEC*2, +            TRUE, +            TRUE, +            5, +            pa_rtclock_now(), +            FALSE); -    if (reserve_init(u, pa_modargs_get_value( -                             ma, "device_id", -                             pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) +    dev_id = pa_modargs_get_value( +            ma, "device_id", +            pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + +    if (reserve_init(u, dev_id) < 0) +        goto fail; + +    if (reserve_monitor_init(u, dev_id) < 0)          goto fail;      b = use_mmap;      d = use_tsched; -    if (profile) { +    if (mapping) {          if (!(dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {              pa_log("device_id= not set");              goto fail;          } -        if (!(u->pcm_handle = pa_alsa_open_by_device_id_profile( +        if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping(                        dev_id,                        &u->device_name,                        &ss, &map,                        SND_PCM_STREAM_CAPTURE,                        &nfrags, &period_frames, tsched_frames, -                      &b, &d, profile))) +                      &b, &d, mapping)))              goto fail;      } else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { +        if (!(profile_set = pa_alsa_profile_set_new(NULL, &map))) +            goto fail; +          if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto(                        dev_id,                        &u->device_name,                        &ss, &map,                        SND_PCM_STREAM_CAPTURE,                        &nfrags, &period_frames, tsched_frames, -                      &b, &d, &profile))) +                      &b, &d, profile_set, &mapping)))              goto fail;      } else { @@ -1469,8 +1492,13 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      pa_assert(u->device_name);      pa_log_info("Successfully opened device %s.", u->device_name); -    if (profile) -        pa_log_info("Selected configuration '%s' (%s).", profile->description, profile->name); +    if (pa_alsa_pcm_is_modem(u->pcm_handle)) { +        pa_log_notice("Device %s is modem, refusing further initialization.", u->device_name); +        goto fail; +    } + +    if (mapping) +        pa_log_info("Selected mapping '%s' (%s).", mapping->description, mapping->name);      if (use_mmap && !b) {          pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); @@ -1482,6 +1510,11 @@ 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."); @@ -1491,13 +1524,13 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      /* ALSA might tweak the sample spec, so recalculate the frame size */      frame_size = pa_frame_size(&ss); -    pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem); +    find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);      pa_source_new_data_init(&data);      data.driver = driver;      data.module = m;      data.card = card; -    set_source_name(&data, ma, dev_id, u->device_name); +    set_source_name(&data, ma, dev_id, u->device_name, mapping);      pa_source_new_data_set_sample_spec(&data, &ss);      pa_source_new_data_set_channel_map(&data, &map); @@ -1507,14 +1540,26 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      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")); -    if (profile) { -        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, profile->name); -        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, profile->description); +    if (mapping) { +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name); +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description);      }      pa_alsa_init_description(data.proplist); -    u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); +    if (u->control_device) +        pa_alsa_init_proplist_ctl(data.proplist, u->control_device); + +    if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_source_new_data_done(&data); +        goto fail; +    } + +    if (u->mixer_path_set) +        pa_alsa_add_ports(&data.ports, u->mixer_path_set); + +    u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|(u->use_tsched ? PA_SOURCE_DYNAMIC_LATENCY : 0));      pa_source_new_data_done(&data);      if (!u->source) { @@ -1525,6 +1570,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      u->source->parent.process_msg = source_process_msg;      u->source->update_requested_latency = source_update_requested_latency_cb;      u->source->set_state = source_set_state_cb; +    u->source->set_port = source_set_port_cb;      u->source->userdata = u;      pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); @@ -1537,24 +1583,24 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);      pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels); -    if (use_tsched) { -        fix_min_sleep_wakeup(u); -        fix_tsched_watermark(u); +    pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", +                nfrags, (long unsigned) u->fragment_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); -    } -    pa_source_set_latency_range(u->source, -                                use_tsched ? (pa_usec_t) -1 : pa_bytes_to_usec(u->hwbuf_size, &ss), -                                pa_bytes_to_usec(u->hwbuf_size, &ss)); +        fix_min_sleep_wakeup(u); +        fix_tsched_watermark(u); -    pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", -                nfrags, (long unsigned) u->fragment_size, -                (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); +        pa_source_set_latency_range(u->source, +                                    0, +                                    pa_bytes_to_usec(u->hwbuf_size, &ss)); -    if (use_tsched)          pa_log_info("Time scheduling watermark is %0.2fms",                      (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); +    } else +        pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->hwbuf_size, &ss));      reserve_update(u); @@ -1564,7 +1610,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      if (setup_mixer(u, ignore_dB) < 0)          goto fail; -    pa_alsa_dump(u->pcm_handle); +    pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log("Failed to create thread."); @@ -1589,11 +1635,18 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      pa_source_put(u->source); +    if (profile_set) +        pa_alsa_profile_set_free(profile_set); +      return u->source;  fail: -    userdata_free(u); +    if (u) +        userdata_free(u); + +    if (profile_set) +        pa_alsa_profile_set_free(profile_set);      return NULL;  } @@ -1620,23 +1673,30 @@ static void userdata_free(struct userdata *u) {      if (u->rtpoll)          pa_rtpoll_free(u->rtpoll); +    if (u->pcm_handle) { +        snd_pcm_drop(u->pcm_handle); +        snd_pcm_close(u->pcm_handle); +    } +      if (u->mixer_fdl)          pa_alsa_fdlist_free(u->mixer_fdl); +    if (u->mixer_path_set) +        pa_alsa_path_set_free(u->mixer_path_set); +    else if (u->mixer_path) +        pa_alsa_path_free(u->mixer_path); +      if (u->mixer_handle)          snd_mixer_close(u->mixer_handle); -    if (u->pcm_handle) { -        snd_pcm_drop(u->pcm_handle); -        snd_pcm_close(u->pcm_handle); -    } -      if (u->smoother)          pa_smoother_free(u->smoother);      reserve_done(u); +    monitor_done(u);      pa_xfree(u->device_name); +    pa_xfree(u->control_device);      pa_xfree(u);  } diff --git a/src/modules/alsa/alsa-source.h b/src/modules/alsa/alsa-source.h index 9cbb0e17..5d9409e2 100644 --- a/src/modules/alsa/alsa-source.h +++ b/src/modules/alsa/alsa-source.h @@ -29,7 +29,7 @@  #include "alsa-util.h" -pa_source* pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile); +pa_source* pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping);  void pa_alsa_source_free(pa_source *s); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index 454cfd4e..1f3e5dcd 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -33,6 +33,7 @@  #include <pulse/timeval.h>  #include <pulse/util.h>  #include <pulse/i18n.h> +#include <pulse/utf8.h>  #include <pulsecore/log.h>  #include <pulsecore/macro.h> @@ -40,8 +41,11 @@  #include <pulsecore/atomic.h>  #include <pulsecore/core-error.h>  #include <pulsecore/once.h> +#include <pulsecore/thread.h> +#include <pulsecore/conf-parser.h>  #include "alsa-util.h" +#include "alsa-mixer.h"  #ifdef HAVE_HAL  #include "hal-util.h" @@ -51,191 +55,6 @@  #include "udev-util.h"  #endif -struct pa_alsa_fdlist { -    unsigned num_fds; -    struct pollfd *fds; -    /* This is a temporary buffer used to avoid lots of mallocs */ -    struct pollfd *work_fds; - -    snd_mixer_t *mixer; - -    pa_mainloop_api *m; -    pa_defer_event *defer; -    pa_io_event **ios; - -    pa_bool_t polled; - -    void (*cb)(void *userdata); -    void *userdata; -}; - -static void io_cb(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { - -    struct pa_alsa_fdlist *fdl = userdata; -    int err; -    unsigned i; -    unsigned short revents; - -    pa_assert(a); -    pa_assert(fdl); -    pa_assert(fdl->mixer); -    pa_assert(fdl->fds); -    pa_assert(fdl->work_fds); - -    if (fdl->polled) -        return; - -    fdl->polled = TRUE; - -    memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); - -    for (i = 0; i < fdl->num_fds; i++) { -        if (e == fdl->ios[i]) { -            if (events & PA_IO_EVENT_INPUT) -                fdl->work_fds[i].revents |= POLLIN; -            if (events & PA_IO_EVENT_OUTPUT) -                fdl->work_fds[i].revents |= POLLOUT; -            if (events & PA_IO_EVENT_ERROR) -                fdl->work_fds[i].revents |= POLLERR; -            if (events & PA_IO_EVENT_HANGUP) -                fdl->work_fds[i].revents |= POLLHUP; -            break; -        } -    } - -    pa_assert(i != fdl->num_fds); - -    if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { -        pa_log_error("Unable to get poll revent: %s", snd_strerror(err)); -        return; -    } - -    a->defer_enable(fdl->defer, 1); - -    if (revents) -        snd_mixer_handle_events(fdl->mixer); -} - -static void defer_cb(pa_mainloop_api*a, pa_defer_event* e, void *userdata) { -    struct pa_alsa_fdlist *fdl = userdata; -    unsigned num_fds, i; -    int err, n; -    struct pollfd *temp; - -    pa_assert(a); -    pa_assert(fdl); -    pa_assert(fdl->mixer); - -    a->defer_enable(fdl->defer, 0); - -    if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) { -        pa_log("snd_mixer_poll_descriptors_count() failed: %s", snd_strerror(n)); -        return; -    } -    num_fds = (unsigned) n; - -    if (num_fds != fdl->num_fds) { -        if (fdl->fds) -            pa_xfree(fdl->fds); -        if (fdl->work_fds) -            pa_xfree(fdl->work_fds); -        fdl->fds = pa_xnew0(struct pollfd, num_fds); -        fdl->work_fds = pa_xnew(struct pollfd, num_fds); -    } - -    memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); - -    if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { -        pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err)); -        return; -    } - -    fdl->polled = FALSE; - -    if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) -        return; - -    if (fdl->ios) { -        for (i = 0; i < fdl->num_fds; i++) -            a->io_free(fdl->ios[i]); - -        if (num_fds != fdl->num_fds) { -            pa_xfree(fdl->ios); -            fdl->ios = NULL; -        } -    } - -    if (!fdl->ios) -        fdl->ios = pa_xnew(pa_io_event*, num_fds); - -    /* Swap pointers */ -    temp = fdl->work_fds; -    fdl->work_fds = fdl->fds; -    fdl->fds = temp; - -    fdl->num_fds = num_fds; - -    for (i = 0;i < num_fds;i++) -        fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, -            ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | -            ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), -            io_cb, fdl); -} - -struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { -    struct pa_alsa_fdlist *fdl; - -    fdl = pa_xnew0(struct pa_alsa_fdlist, 1); - -    fdl->num_fds = 0; -    fdl->fds = NULL; -    fdl->work_fds = NULL; -    fdl->mixer = NULL; -    fdl->m = NULL; -    fdl->defer = NULL; -    fdl->ios = NULL; -    fdl->polled = FALSE; - -    return fdl; -} - -void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { -    pa_assert(fdl); - -    if (fdl->defer) { -        pa_assert(fdl->m); -        fdl->m->defer_free(fdl->defer); -    } - -    if (fdl->ios) { -        unsigned i; -        pa_assert(fdl->m); -        for (i = 0; i < fdl->num_fds; i++) -            fdl->m->io_free(fdl->ios[i]); -        pa_xfree(fdl->ios); -    } - -    if (fdl->fds) -        pa_xfree(fdl->fds); -    if (fdl->work_fds) -        pa_xfree(fdl->work_fds); - -    pa_xfree(fdl); -} - -int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { -    pa_assert(fdl); -    pa_assert(mixer_handle); -    pa_assert(m); -    pa_assert(!fdl->m); - -    fdl->mixer = mixer_handle; -    fdl->m = m; -    fdl->defer = m->defer_new(m, defer_cb, fdl); - -    return 0; -} -  static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) {      static const snd_pcm_format_t format_trans[] = { @@ -267,11 +86,11 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s          PA_SAMPLE_S16RE,          PA_SAMPLE_ALAW,          PA_SAMPLE_ULAW, -        PA_SAMPLE_U8, -        PA_SAMPLE_INVALID +        PA_SAMPLE_U8      }; -    int i, ret; +    unsigned i; +    int ret;      pa_assert(pcm_handle);      pa_assert(f); @@ -279,6 +98,10 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s      if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)          return ret; +    pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", +                 snd_pcm_format_description(format_trans[*f]), +                 pa_alsa_strerror(ret)); +      if (*f == PA_SAMPLE_FLOAT32BE)          *f = PA_SAMPLE_FLOAT32LE;      else if (*f == PA_SAMPLE_FLOAT32LE) @@ -305,13 +128,21 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s      if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)          return ret; +    pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", +                 snd_pcm_format_description(format_trans[*f]), +                 pa_alsa_strerror(ret)); +  try_auto: -    for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { +    for (i = 0; i < PA_ELEMENTSOF(try_order); i++) {          *f = try_order[i];          if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)              return ret; + +        pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", +                     snd_pcm_format_description(format_trans[*f]), +                     pa_alsa_strerror(ret));      }      return -1; @@ -332,7 +163,6 @@ int pa_alsa_set_hw_params(      int ret = -1;      snd_pcm_uframes_t _period_size = period_size ? *period_size : 0;      unsigned int _periods = periods ? *periods : 0; -    snd_pcm_uframes_t buffer_size;      unsigned int r = ss->rate;      unsigned int c = ss->channels;      pa_sample_format_t f = ss->format; @@ -346,11 +176,15 @@ int pa_alsa_set_hw_params(      snd_pcm_hw_params_alloca(&hwparams); -    if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) +    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));          goto finish; +    } -    if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) +    if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) { +        pa_log_debug("snd_pcm_hw_params_set_rate_resample() failed: %s", pa_alsa_strerror(ret));          goto finish; +    }      if (_use_mmap) { @@ -358,14 +192,18 @@ int pa_alsa_set_hw_params(              /* mmap() didn't work, fall back to interleaved */ -            if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) +            if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { +                pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret));                  goto finish; +            }              _use_mmap = FALSE;          } -    } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) +    } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { +        pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret));          goto finish; +    }      if (!_use_mmap)          _use_tsched = FALSE; @@ -373,54 +211,72 @@ int pa_alsa_set_hw_params(      if ((ret = set_format(pcm_handle, hwparams, &f)) < 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, &r, 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) +        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));              goto finish; +        }      } else { -        if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) +        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));              goto finish; +        }      } -    if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) +    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; +    }      if (_period_size && tsched_size && _periods) { +          /* 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 (_use_tsched) { +            snd_pcm_uframes_t buffer_size = 0; + +            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); +              _period_size = tsched_size;              _periods = 1; - -            pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); -            pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r);          } -        buffer_size = _periods * _period_size; +        if (_period_size > 0 && _periods > 0) { +            snd_pcm_uframes_t 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 (_periods > 0) { -            /* First we pass 0 as direction to get exactly what we asked -             * for. That this is necessary is presumably a bug in ALSA */ +            /* 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. */              dir = 0; -            if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { +            if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) {                  dir = 1; -                if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { +                if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) {                      dir = -1;                      if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) -                        goto finish; +                        pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret));                  }              }          } - -        if (_period_size > 0) -            if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) -                goto finish;      }      if  ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) @@ -435,12 +291,16 @@ int pa_alsa_set_hw_params(      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 ((ret = snd_pcm_prepare(pcm_handle)) < 0) +    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_get_period_size(hwparams, &_period_size, &dir)) < 0 || -        (ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &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));          goto finish; +    }      /* If the sample rate deviates too much, we need to resample */      if (r < ss->rate*.95 || r > ss->rate*1.05) @@ -482,131 +342,48 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) {      snd_pcm_sw_params_alloca(&swparams);      if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) { -        pa_log_warn("Unable to determine current swparams: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to determine current swparams: %s\n", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, 0)) < 0) { -        pa_log_warn("Unable to disable period event: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to disable period event: %s\n", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm, swparams, SND_PCM_TSTAMP_ENABLE)) < 0) { -        pa_log_warn("Unable to enable time stamping: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to enable time stamping: %s\n", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params_get_boundary(swparams, &boundary)) < 0) { -        pa_log_warn("Unable to get boundary: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to get boundary: %s\n", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, boundary)) < 0) { -        pa_log_warn("Unable to set stop threshold: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to set stop threshold: %s\n", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { -        pa_log_warn("Unable to set start threshold: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to set start threshold: %s\n", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0) { -        pa_log_error("snd_pcm_sw_params_set_avail_min() failed: %s", snd_strerror(err)); +        pa_log_error("snd_pcm_sw_params_set_avail_min() failed: %s", pa_alsa_strerror(err));          return err;      }      if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) { -        pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err)); +        pa_log_warn("Unable to set sw params: %s\n", pa_alsa_strerror(err));          return err;      }      return 0;  } -static const struct pa_alsa_profile_info device_table[] = { -    {{ 1, { PA_CHANNEL_POSITION_MONO }}, -     "hw", -     N_("Analog Mono"), -     "analog-mono", -     1 }, - -    {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, -     "front", -     N_("Analog Stereo"), -     "analog-stereo", -     10 }, - -    {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, -     "iec958", -     N_("Digital Stereo (IEC958)"), -     "iec958-stereo", -     5 }, - -    {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, -     "hdmi", -     N_("Digital Stereo (HDMI)"), -     "hdmi-stereo", -     4 }, - -    {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, -            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, -     "surround40", -     N_("Analog Surround 4.0"), -     "analog-surround-40", -     7 }, - -    {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, -            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, -     "a52", -     N_("Digital Surround 4.0 (IEC958/AC3)"), -     "iec958-ac3-surround-40", -     2 }, - -    {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, -            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -            PA_CHANNEL_POSITION_LFE }}, -     "surround41", -     N_("Analog Surround 4.1"), -     "analog-surround-41", -     7 }, - -    {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, -            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -            PA_CHANNEL_POSITION_CENTER }}, -     "surround50", -     N_("Analog Surround 5.0"), -     "analog-surround-50", -     7 }, - -    {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, -            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -            PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, -     "surround51", -     N_("Analog Surround 5.1"), -     "analog-surround-51", -     8 }, - -    {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_CENTER, -            PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, -            PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE}}, -     "a52", -     N_("Digital Surround 5.1 (IEC958/AC3)"), -     "iec958-ac3-surround-51", -     3 }, - -    {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, -            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -            PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, -            PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }}, -     "surround71", -     N_("Analog Surround 7.1"), -     "analog-surround-71", -     7 }, - -    {{ 0, { 0 }}, NULL, NULL, NULL, 0 } -}; -  snd_pcm_t *pa_alsa_open_by_device_id_auto(          const char *dev_id,          char **dev, @@ -618,12 +395,13 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(          snd_pcm_uframes_t tsched_size,          pa_bool_t *use_mmap,          pa_bool_t *use_tsched, -        const pa_alsa_profile_info **profile) { +        pa_alsa_profile_set *ps, +        pa_alsa_mapping **mapping) { -    int i; -    int direction = 1;      char *d;      snd_pcm_t *pcm_handle; +    void *state; +    pa_alsa_mapping *m;      pa_assert(dev_id);      pa_assert(dev); @@ -631,114 +409,82 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(      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 -     * requested channel map and open it without the plug: prefix. We -     * iterate through our device table from top to bottom and take -     * the first that matches. If we didn't find a working device that -     * way, we iterate backwards, and check all devices that do not -     * provide a superset of the requested channel map.*/ - -    i = 0; -    for (;;) { - -        if ((direction > 0) == pa_channel_map_superset(&device_table[i].map, map)) { -            pa_sample_spec try_ss; - -            pa_log_debug("Checking for %s (%s)", device_table[i].name, device_table[i].alsa_name); - -            d = pa_sprintf_malloc("%s:%s", device_table[i].alsa_name, dev_id); - -            try_ss.channels = device_table[i].map.channels; -            try_ss.rate = ss->rate; -            try_ss.format = ss->format; - -            pcm_handle = pa_alsa_open_by_device_string( -                    d, -                    dev, -                    &try_ss, -                    map, -                    mode, -                    nfrags, -                    period_size, -                    tsched_size, -                    use_mmap, -                    use_tsched, -                    TRUE); - -            pa_xfree(d); - -            if (pcm_handle) { - -                *ss = try_ss; -                *map = device_table[i].map; -                pa_assert(map->channels == ss->channels); - -                if (profile) -                    *profile = &device_table[i]; - -                return pcm_handle; -            } -        } - -        if (direction > 0) { -            if (!device_table[i+1].alsa_name) { -                /* OK, so we are at the end of our list. Let's turn -                 * back. */ -                direction = -1; -            } else { -                /* We are not at the end of the list, so let's simply -                 * try the next entry */ -                i++; -            } +     * requested channel map. We iterate through our device table from +     * top to bottom and take the first that matches. If we didn't +     * find a working device that way, we iterate backwards, and check +     * all devices that do not provide a superset of the requested +     * channel map.*/ + +    PA_HASHMAP_FOREACH(m, ps->mappings, state) { +        if (!pa_channel_map_superset(&m->channel_map, map)) +            continue; + +        pa_log_debug("Checking for superset %s (%s)", m->name, m->device_strings[0]); + +        pcm_handle = pa_alsa_open_by_device_id_mapping( +                dev_id, +                dev, +                ss, +                map, +                mode, +                nfrags, +                period_size, +                tsched_size, +                use_mmap, +                use_tsched, +                m); + +        if (pcm_handle) { +            if (mapping) +                *mapping = m; + +            return pcm_handle;          } +    } -        if (direction < 0) { - -            if (device_table[i+1].alsa_name && -                device_table[i].map.channels == device_table[i+1].map.channels) { - -                /* OK, the next entry has the same number of channels, -                 * let's try it */ -                i++; - -            } else { -                /* Hmm, so the next entry does not have the same -                 * number of channels, so let's go backwards until we -                 * find the next entry with a differnt number of -                 * channels */ - -                for (i--; i >= 0; i--) -                    if (device_table[i].map.channels != device_table[i+1].map.channels) -                        break; - -                /* Hmm, there is no entry with a different number of -                 * entries, then we're done */ -                if (i < 0) -                    break; - -                /* OK, now lets find go back as long as we have the same number of channels */ -                for (; i > 0; i--) -                    if (device_table[i].map.channels != device_table[i-1].map.channels) -                        break; -            } +    PA_HASHMAP_FOREACH_BACKWARDS(m, ps->mappings, state) { +        if (pa_channel_map_superset(&m->channel_map, map)) +            continue; + +        pa_log_debug("Checking for subset %s (%s)", m->name, m->device_strings[0]); + +        pcm_handle = pa_alsa_open_by_device_id_mapping( +                dev_id, +                dev, +                ss, +                map, +                mode, +                nfrags, +                period_size, +                tsched_size, +                use_mmap, +                use_tsched, +                m); + +        if (pcm_handle) { +            if (mapping) +                *mapping = m; + +            return pcm_handle;          }      } -    /* OK, we didn't find any good device, so let's try the raw plughw: stuff */ - +    /* 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);      pa_xfree(d); -    if (pcm_handle && profile) -        *profile = NULL; +    if (pcm_handle && mapping) +        *mapping = NULL;      return pcm_handle;  } -snd_pcm_t *pa_alsa_open_by_device_id_profile( +snd_pcm_t *pa_alsa_open_by_device_id_mapping(          const char *dev_id,          char **dev,          pa_sample_spec *ss, @@ -749,11 +495,11 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile(          snd_pcm_uframes_t tsched_size,          pa_bool_t *use_mmap,          pa_bool_t *use_tsched, -        const pa_alsa_profile_info *profile) { +        pa_alsa_mapping *m) { -    char *d;      snd_pcm_t *pcm_handle;      pa_sample_spec try_ss; +    pa_channel_map try_map;      pa_assert(dev_id);      pa_assert(dev); @@ -761,19 +507,19 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile(      pa_assert(map);      pa_assert(nfrags);      pa_assert(period_size); -    pa_assert(profile); - -    d = pa_sprintf_malloc("%s:%s", profile->alsa_name, dev_id); +    pa_assert(m); -    try_ss.channels = profile->map.channels; +    try_ss.channels = m->channel_map.channels;      try_ss.rate = ss->rate;      try_ss.format = ss->format; +    try_map = m->channel_map; -    pcm_handle = pa_alsa_open_by_device_string( -            d, +    pcm_handle = pa_alsa_open_by_template( +            m->device_strings, +            dev_id,              dev,              &try_ss, -            map, +            &try_map,              mode,              nfrags,              period_size, @@ -782,13 +528,11 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile(              use_tsched,              TRUE); -    pa_xfree(d); -      if (!pcm_handle)          return NULL;      *ss = try_ss; -    *map = profile->map; +    *map = try_map;      pa_assert(map->channels == ss->channels);      return pcm_handle; @@ -821,21 +565,17 @@ snd_pcm_t *pa_alsa_open_by_device_string(      for (;;) {          pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); -        /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= -         * 1.0.17a would then ignore the SND_PCM_NO_xxx flags. Instead -         * we enable nonblock mode afterwards via -         * snd_pcm_nonblock(). Also see -         * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ -          if ((err = snd_pcm_open(&pcm_handle, d, mode, -                                /*SND_PCM_NONBLOCK|*/ +                                SND_PCM_NONBLOCK|                                  SND_PCM_NO_AUTO_RESAMPLE|                                  SND_PCM_NO_AUTO_CHANNELS|                                  (reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) { -            pa_log_info("Error opening PCM device %s: %s", d, snd_strerror(err)); +            pa_log_info("Error opening PCM device %s: %s", d, pa_alsa_strerror(err));              goto fail;          } +        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 (!reformat) { @@ -846,7 +586,6 @@ snd_pcm_t *pa_alsa_open_by_device_string(              }              /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ -              if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) {                  char *t; @@ -860,7 +599,7 @@ snd_pcm_t *pa_alsa_open_by_device_string(                  continue;              } -            pa_log_info("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); +            pa_log_info("Failed to set hardware parameters on %s: %s", d, pa_alsa_strerror(err));              snd_pcm_close(pcm_handle);              goto fail; @@ -883,414 +622,51 @@ fail:      return NULL;  } -int pa_alsa_probe_profiles( +snd_pcm_t *pa_alsa_open_by_template( +        char **template,          const char *dev_id, -        const pa_sample_spec *ss, -        void (*cb)(const pa_alsa_profile_info *sink, const pa_alsa_profile_info *source, void *userdata), -        void *userdata) { - -    const pa_alsa_profile_info *i; - -    pa_assert(dev_id); -    pa_assert(ss); -    pa_assert(cb); - -    /* We try each combination of playback/capture. We also try to -     * open only for capture resp. only for sink. Don't get confused -     * by the trailing entry in device_table we use for this! */ - -    for (i = device_table; i < device_table + PA_ELEMENTSOF(device_table); i++) { -        const pa_alsa_profile_info *j; -        snd_pcm_t *pcm_i = NULL; - -        if (i->alsa_name) { -            char *id; -            pa_sample_spec try_ss; -            pa_channel_map try_map; - -            pa_log_debug("Checking for playback on %s (%s)", i->name, i->alsa_name); -            id = pa_sprintf_malloc("%s:%s", i->alsa_name, dev_id); - -            try_ss = *ss; -            try_ss.channels = i->map.channels; -            try_map = i->map; - -            pcm_i = pa_alsa_open_by_device_string( -                    id, NULL, -                    &try_ss, &try_map, -                    SND_PCM_STREAM_PLAYBACK, -                    NULL, NULL, 0, NULL, NULL, -                    TRUE); - -            pa_xfree(id); - -            if (!pcm_i) -                continue; -        } - -        for (j = device_table; j < device_table + PA_ELEMENTSOF(device_table); j++) { -            snd_pcm_t *pcm_j = NULL; - -            if (j->alsa_name) { -                char *jd; -                pa_sample_spec try_ss; -                pa_channel_map try_map; - -                pa_log_debug("Checking for capture on %s (%s)", j->name, j->alsa_name); -                jd = pa_sprintf_malloc("%s:%s", j->alsa_name, dev_id); - -                try_ss = *ss; -                try_ss.channels = j->map.channels; -                try_map = j->map; - -                pcm_j = pa_alsa_open_by_device_string( -                        jd, NULL, -                        &try_ss, &try_map, -                        SND_PCM_STREAM_CAPTURE, -                        NULL, NULL, 0, NULL, NULL, -                        TRUE); - -                pa_xfree(jd); - -                if (!pcm_j) -                    continue; -            } - -            if (pcm_j) -                snd_pcm_close(pcm_j); - -            if (i->alsa_name || j->alsa_name) -                cb(i->alsa_name ? i : NULL, -                   j->alsa_name ? j : NULL, userdata); -        } - -        if (pcm_i) -            snd_pcm_close(pcm_i); -    } - -    return TRUE; -} - -int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { -    int err; - -    pa_assert(mixer); -    pa_assert(dev); - -    if ((err = snd_mixer_attach(mixer, dev)) < 0) { -        pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); -        return -1; -    } - -    if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { -        pa_log_warn("Unable to register mixer: %s", snd_strerror(err)); -        return -1; -    } - -    if ((err = snd_mixer_load(mixer)) < 0) { -        pa_log_warn("Unable to load mixer: %s", snd_strerror(err)); -        return -1; -    } - -    pa_log_info("Successfully attached to mixer '%s'", dev); - -    return 0; -} - -static pa_bool_t elem_has_volume(snd_mixer_elem_t *elem, pa_bool_t playback) { -    pa_assert(elem); - -    if (playback && snd_mixer_selem_has_playback_volume(elem)) -        return TRUE; - -    if (!playback && snd_mixer_selem_has_capture_volume(elem)) -        return TRUE; - -    return FALSE; -} - -static pa_bool_t elem_has_switch(snd_mixer_elem_t *elem, pa_bool_t playback) { -    pa_assert(elem); - -    if (playback && snd_mixer_selem_has_playback_switch(elem)) -        return TRUE; - -    if (!playback && snd_mixer_selem_has_capture_switch(elem)) -        return TRUE; - -    return FALSE; -} - -snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback) { -    snd_mixer_elem_t *elem = NULL, *fallback_elem = NULL; -    snd_mixer_selem_id_t *sid = NULL; - -    snd_mixer_selem_id_alloca(&sid); - -    pa_assert(mixer); -    pa_assert(name); - -    snd_mixer_selem_id_set_name(sid, name); -    snd_mixer_selem_id_set_index(sid, 0); - -    if ((elem = snd_mixer_find_selem(mixer, sid))) { - -        if (elem_has_volume(elem, playback) && -            elem_has_switch(elem, playback)) -            goto success; - -        if (!elem_has_volume(elem, playback) && -            !elem_has_switch(elem, playback)) -            elem = NULL; -    } - -    pa_log_info("Cannot find mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); - -    if (fallback) { -        snd_mixer_selem_id_set_name(sid, fallback); -        snd_mixer_selem_id_set_index(sid, 0); - -        if ((fallback_elem = snd_mixer_find_selem(mixer, sid))) { - -            if (elem_has_volume(fallback_elem, playback) && -                elem_has_switch(fallback_elem, playback)) { -                elem = fallback_elem; -                goto success; -            } - -            if (!elem_has_volume(fallback_elem, playback) && -                !elem_has_switch(fallback_elem, playback)) -                fallback_elem = NULL; -        } - -        pa_log_warn("Cannot find fallback mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); -    } - -    if (elem && fallback_elem) { - -        /* Hmm, so we have both elements, but neither has both mute -         * and volume. Let's prefer the one with the volume */ - -        if (elem_has_volume(elem, playback)) -            goto success; - -        if (elem_has_volume(fallback_elem, playback)) { -            elem = fallback_elem; -            goto success; -        } -    } - -    if (!elem && fallback_elem) -        elem = fallback_elem; - -success: - -    if (elem) -        pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); - -    return elem; -} - -int pa_alsa_find_mixer_and_elem( -        snd_pcm_t *pcm, -        snd_mixer_t **_m, -        snd_mixer_elem_t **_e) { - -    int err; -    snd_mixer_t *m; -    snd_mixer_elem_t *e; -    pa_bool_t found = FALSE; -    const char *dev; - -    pa_assert(pcm); -    pa_assert(_m); -    pa_assert(_e); - -    if ((err = snd_mixer_open(&m, 0)) < 0) { -        pa_log("Error opening mixer: %s", snd_strerror(err)); -        return -1; -    } - -    /* First, try by name */ -    if ((dev = snd_pcm_name(pcm))) -        if (pa_alsa_prepare_mixer(m, dev) >= 0) -            found = TRUE; - -    /* Then, try by card index */ -    if (!found) { -        snd_pcm_info_t* info; -        snd_pcm_info_alloca(&info); - -        if (snd_pcm_info(pcm, info) >= 0) { -            char *md; -            int card_idx; - -            if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { - -                md = pa_sprintf_malloc("hw:%i", card_idx); - -                if (!dev || !pa_streq(dev, md)) -                    if (pa_alsa_prepare_mixer(m, md) >= 0) -                        found = TRUE; - -                pa_xfree(md); -            } -        } -    } - -    if (!found) { -        snd_mixer_close(m); -        return -1; -    } - -    switch (snd_pcm_stream(pcm)) { - -        case SND_PCM_STREAM_PLAYBACK: -            e = pa_alsa_find_elem(m, "Master", "PCM", TRUE); -            break; - -        case SND_PCM_STREAM_CAPTURE: -            e = pa_alsa_find_elem(m, "Capture", "Mic", FALSE); -            break; - -        default: -            pa_assert_not_reached(); -    } - -    if (!e) { -        snd_mixer_close(m); -        return -1; -    } - -    pa_assert(e && m); - -    *_m = m; -    *_e = e; - -    return 0; -} - -static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { -    [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ - -    [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, -    [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, -    [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, - -    [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, -    [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, -    [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, - -    [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, - -    [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, - -    [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, -    [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, - -    [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX9] =  SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, - -    [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, - -    [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, - -    [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, -    [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN -}; - - -int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { -    unsigned i; -    pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; -    pa_bool_t mono_used = FALSE; - -    pa_assert(elem); -    pa_assert(channel_map); -    pa_assert(mixer_map); - -    memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); - -    if (channel_map->channels > 1 && -        ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || -         (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { -        pa_log_info("ALSA device lacks independant volume controls for each channel."); -        return -1; -    } - -    for (i = 0; i < channel_map->channels; i++) { -        snd_mixer_selem_channel_id_t id; -        pa_bool_t is_mono; - -        is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; -        id = alsa_channel_ids[channel_map->map[i]]; - -        if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { -            pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer.", pa_channel_position_to_string(channel_map->map[i])); -            return -1; -        } - -        if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { -            pa_log_info("Channel map has duplicate channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); -            return -1; -        } - -        if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || -            (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { - -            pa_log_info("ALSA device lacks separate volumes control for channel '%s'", pa_channel_position_to_string(channel_map->map[i])); -            return -1; -        } +        char **dev, +        pa_sample_spec *ss, +        pa_channel_map* map, +        int mode, +        uint32_t *nfrags, +        snd_pcm_uframes_t *period_size, +        snd_pcm_uframes_t tsched_size, +        pa_bool_t *use_mmap, +        pa_bool_t *use_tsched, +        pa_bool_t require_exact_channel_number) { -        if (is_mono) { -            mixer_map[i] = SND_MIXER_SCHN_MONO; -            mono_used = TRUE; -        } else { -            mixer_map[i] = id; -            alsa_channel_used[id] = TRUE; -        } +    snd_pcm_t *pcm_handle; +    char **i; + +    for (i = template; *i; i++) { +        char *d; + +        d = pa_replace(*i, "%f", dev_id); + +        pcm_handle = pa_alsa_open_by_device_string( +                d, +                dev, +                ss, +                map, +                mode, +                nfrags, +                period_size, +                tsched_size, +                use_mmap, +                use_tsched, +                require_exact_channel_number); + +        pa_xfree(d); + +        if (pcm_handle) +            return pcm_handle;      } -    pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels); - -    return 0; +    return NULL;  } -void pa_alsa_dump(snd_pcm_t *pcm) { +void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm) {      int err;      snd_output_t *out; @@ -1299,11 +675,11 @@ void pa_alsa_dump(snd_pcm_t *pcm) {      pa_assert_se(snd_output_buffer_open(&out) == 0);      if ((err = snd_pcm_dump(pcm, out)) < 0) -        pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); +        pa_logl(level, "snd_pcm_dump(): %s", pa_alsa_strerror(err));      else {          char *s = NULL;          snd_output_buffer_string(out, &s); -        pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); +        pa_logl(level, "snd_pcm_dump():\n%s", pa_strnull(s));      }      pa_assert_se(snd_output_close(out) == 0); @@ -1313,24 +689,33 @@ void pa_alsa_dump_status(snd_pcm_t *pcm) {      int err;      snd_output_t *out;      snd_pcm_status_t *status; +    char *s = NULL;      pa_assert(pcm);      snd_pcm_status_alloca(&status); -    pa_assert_se(snd_output_buffer_open(&out) == 0); +    if ((err = snd_output_buffer_open(&out)) < 0) { +        pa_log_debug("snd_output_buffer_open() failed: %s", pa_cstrerror(err)); +        return; +    } -    pa_assert_se(snd_pcm_status(pcm, status) == 0); +    if ((err = snd_pcm_status(pcm, status)) < 0) { +        pa_log_debug("snd_pcm_status() failed: %s", pa_cstrerror(err)); +        goto finish; +    } -    if ((err = snd_pcm_status_dump(status, out)) < 0) -        pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); -    else { -        char *s = NULL; -        snd_output_buffer_string(out, &s); -        pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); +    if ((err = snd_pcm_status_dump(status, out)) < 0) { +        pa_log_debug("snd_pcm_dump(): %s", pa_alsa_strerror(err)); +        goto finish;      } -    pa_assert_se(snd_output_close(out) == 0); +    snd_output_buffer_string(out, &s); +    pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + +finish: + +    snd_output_close(out);  }  static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) { @@ -1350,38 +735,43 @@ static void alsa_error_handler(const char *file, int line, const char *function,  static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0); -void pa_alsa_redirect_errors_inc(void) { +void pa_alsa_refcnt_inc(void) {      /* This is not really thread safe, but we do our best */      if (pa_atomic_inc(&n_error_handler_installed) == 0)          snd_lib_error_set_handler(alsa_error_handler);  } -void pa_alsa_redirect_errors_dec(void) { +void pa_alsa_refcnt_dec(void) {      int r;      pa_assert_se((r = pa_atomic_dec(&n_error_handler_installed)) >= 1); -    if (r == 1) +    if (r == 1) {          snd_lib_error_set_handler(NULL); +        snd_config_update_free_global(); +    }  }  pa_bool_t pa_alsa_init_description(pa_proplist *p) { -    const char *s; +    const char *d, *k;      pa_assert(p);      if (pa_device_init_description(p))          return TRUE; -    if ((s = pa_proplist_gets(p, "alsa.card_name"))) { -        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, s); -        return TRUE; -    } +    if (!(d = pa_proplist_gets(p, "alsa.card_name"))) +        d = pa_proplist_gets(p, "alsa.name"); -    if ((s = pa_proplist_gets(p, "alsa.name"))) { -        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, s); -        return TRUE; -    } +    if (!d) +        return FALSE; + +    k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION); + +    if (d && k) +        pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, _("%s %s"), d, k); +    else if (d) +        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d);      return FALSE;  } @@ -1410,7 +800,7 @@ void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card) {      }  #ifdef HAVE_UDEV -    pa_udev_get_info(c, p, card); +    pa_udev_get_info(card, p);  #endif  #ifdef HAVE_HAL @@ -1447,16 +837,14 @@ void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *      pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa"); -    class = snd_pcm_info_get_class(pcm_info); -    if (class <= SND_PCM_CLASS_LAST) { +    if ((class = snd_pcm_info_get_class(pcm_info)) <= SND_PCM_CLASS_LAST) {          if (class_table[class])              pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, class_table[class]);          if (alsa_class_table[class])              pa_proplist_sets(p, "alsa.class", alsa_class_table[class]);      } -    subclass = snd_pcm_info_get_subclass(pcm_info); -    if (subclass <= SND_PCM_SUBCLASS_LAST) +    if ((subclass = snd_pcm_info_get_subclass(pcm_info)) <= SND_PCM_SUBCLASS_LAST)          if (alsa_subclass_table[subclass])              pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]); @@ -1485,7 +873,7 @@ void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm) {      snd_pcm_info_alloca(&info);      if ((err = snd_pcm_hw_params_current(pcm, hwparams)) < 0) -        pa_log_warn("Error fetching hardware parameter info: %s", snd_strerror(err)); +        pa_log_warn("Error fetching hardware parameter info: %s", pa_alsa_strerror(err));      else {          if ((bits = snd_pcm_hw_params_get_sbits(hwparams)) >= 0) @@ -1493,11 +881,41 @@ void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm) {      }      if ((err = snd_pcm_info(pcm, info)) < 0) -        pa_log_warn("Error fetching PCM info: %s", snd_strerror(err)); +        pa_log_warn("Error fetching PCM info: %s", pa_alsa_strerror(err));      else          pa_alsa_init_proplist_pcm_info(c, p, info);  } +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { +    int err; +    snd_ctl_t *ctl; +    snd_ctl_card_info_t *info; +    const char *t; + +    pa_assert(p); + +    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); +        return; +    } + +    if ((err = snd_ctl_card_info(ctl, info)) < 0) { +        pa_log_warn("Control device %s card info: %s", name, snd_strerror(err)); +        snd_ctl_close(ctl); +        return; +    } + +    if ((t = snd_ctl_card_info_get_mixername(info)) && *t) +        pa_proplist_sets(p, "alsa.mixer_name", t); + +    if ((t = snd_ctl_card_info_get_components(info)) && *t) +        pa_proplist_sets(p, "alsa.components", t); + +    snd_ctl_close(ctl); +} +  int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {      snd_pcm_state_t state;      int err; @@ -1526,14 +944,14 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {          case SND_PCM_STATE_XRUN:              if ((err = snd_pcm_recover(pcm, -EPIPE, 1)) != 0) { -                pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); +                pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", pa_alsa_strerror(err));                  return -1;              }              break;          case SND_PCM_STATE_SUSPENDED:              if ((err = snd_pcm_recover(pcm, -ESTRPIPE, 1)) != 0) { -                pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); +                pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", pa_alsa_strerror(err));                  return -1;              }              break; @@ -1543,7 +961,7 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {              snd_pcm_drop(pcm);              if ((err = snd_pcm_prepare(pcm)) < 0) { -                pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); +                pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", pa_alsa_strerror(err));                  return -1;              }              break; @@ -1560,7 +978,7 @@ pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) {      pa_assert(pcm);      if ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) { -        pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); +        pa_log("snd_pcm_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));          return NULL;      } @@ -1568,7 +986,7 @@ pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) {      pollfd = pa_rtpoll_item_get_pollfd(item, NULL);      if ((err = snd_pcm_poll_descriptors(pcm, pollfd, (unsigned) n)) < 0) { -        pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); +        pa_log("snd_pcm_poll_descriptors() failed: %s", pa_alsa_strerror(err));          pa_rtpoll_item_free(item);          return NULL;      } @@ -1605,6 +1023,7 @@ snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa                     (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),                     pa_strnull(dn));              pa_xfree(dn); +            pa_alsa_dump(PA_LOG_ERROR, pcm);          } PA_ONCE_END;          /* Mhmm, let's try not to fail completely */ @@ -1646,6 +1065,7 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_si                     (unsigned long) (pa_bytes_to_usec(abs_k, ss) / PA_USEC_PER_MSEC),                     pa_strnull(dn));              pa_xfree(dn); +            pa_alsa_dump(PA_LOG_ERROR, pcm);          } PA_ONCE_END;          /* Mhmm, let's try not to fail completely */ @@ -1691,6 +1111,7 @@ int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas                     (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),                     pa_strnull(dn));              pa_xfree(dn); +            pa_alsa_dump(PA_LOG_ERROR, pcm);          } PA_ONCE_END;      return r; @@ -1716,10 +1137,11 @@ char *pa_alsa_get_driver_name(int card) {  char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm) {      int card; -      snd_pcm_info_t* info;      snd_pcm_info_alloca(&info); +    pa_assert(pcm); +      if (snd_pcm_info(pcm, info) < 0)          return NULL; @@ -1749,3 +1171,54 @@ char *pa_alsa_get_reserve_name(const char *device) {      return pa_sprintf_malloc("Audio%i", i);  } + +pa_bool_t pa_alsa_pcm_is_hw(snd_pcm_t *pcm) { +    snd_pcm_info_t* info; +    snd_pcm_info_alloca(&info); + +    pa_assert(pcm); + +    if (snd_pcm_info(pcm, info) < 0) +        return FALSE; + +    return snd_pcm_info_get_card(info) >= 0; +} + +pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm) { +    snd_pcm_info_t* info; +    snd_pcm_info_alloca(&info); + +    pa_assert(pcm); + +    if (snd_pcm_info(pcm, info) < 0) +        return FALSE; + +    return snd_pcm_info_get_class(info) == SND_PCM_CLASS_MODEM; +} + +PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree); + +const char* pa_alsa_strerror(int errnum) { +    const char *original = NULL; +    char *translated, *t; +    char errbuf[128]; + +    if ((t = PA_STATIC_TLS_GET(cstrerror))) +        pa_xfree(t); + +    original = snd_strerror(errnum); + +    if (!original) { +        pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %i", errnum); +        original = errbuf; +    } + +    if (!(translated = pa_locale_to_utf8(original))) { +        pa_log_warn("Unable to convert error string to locale, filtering."); +        translated = pa_utf8_filter(original); +    } + +    PA_STATIC_TLS_SET(cstrerror, translated); + +    return translated; +} diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index fe0f71e0..830a922e 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -30,99 +30,97 @@  #include <pulse/mainloop-api.h>  #include <pulse/channelmap.h>  #include <pulse/proplist.h> +#include <pulse/volume.h> +#include <pulsecore/llist.h>  #include <pulsecore/rtpoll.h>  #include <pulsecore/core.h> +#include <pulsecore/log.h> -typedef struct pa_alsa_fdlist pa_alsa_fdlist; - -struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); -void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); -int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); +#include "alsa-mixer.h"  int pa_alsa_set_hw_params(          snd_pcm_t *pcm_handle, -        pa_sample_spec *ss, -        uint32_t *periods, -        snd_pcm_uframes_t *period_size, +        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 tsched_size, -        pa_bool_t *use_mmap, -        pa_bool_t *use_tsched, +        pa_bool_t *use_mmap,               /* modified at return */ +        pa_bool_t *use_tsched,             /* modified at return */          pa_bool_t require_exact_channel_number); -int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); - -int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); -snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback); -int pa_alsa_find_mixer_and_elem(snd_pcm_t *pcm, snd_mixer_t **_m, snd_mixer_elem_t **_e); +int pa_alsa_set_sw_params( +        snd_pcm_t *pcm, +        snd_pcm_uframes_t avail_min); -typedef struct pa_alsa_profile_info { -    pa_channel_map map; -    const char *alsa_name; -    const char *description; /* internationalized */ -    const char *name; -    unsigned priority; -} pa_alsa_profile_info; - -/* Picks a working profile based on the specified ss/map */ +/* Picks a working mapping from the profile set based on the specified ss/map */  snd_pcm_t *pa_alsa_open_by_device_id_auto(          const char *dev_id, -        char **dev, -        pa_sample_spec *ss, -        pa_channel_map* map, +        char **dev,                       /* modified at return */ +        pa_sample_spec *ss,               /* modified at return */ +        pa_channel_map* map,              /* modified at return */          int mode, -        uint32_t *nfrags, -        snd_pcm_uframes_t *period_size, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_size,   /* modified at return */          snd_pcm_uframes_t tsched_size, -        pa_bool_t *use_mmap, -        pa_bool_t *use_tsched, -        const pa_alsa_profile_info **profile); +        pa_bool_t *use_mmap,              /* modified at return */ +        pa_bool_t *use_tsched,            /* modified at return */ +        pa_alsa_profile_set *ps, +        pa_alsa_mapping **mapping);       /* modified at return */ -/* Uses the specified profile */ -snd_pcm_t *pa_alsa_open_by_device_id_profile( +/* Uses the specified mapping */ +snd_pcm_t *pa_alsa_open_by_device_id_mapping(          const char *dev_id, -        char **dev, -        pa_sample_spec *ss, -        pa_channel_map* map, +        char **dev,                       /* modified at return */ +        pa_sample_spec *ss,               /* modified at return */ +        pa_channel_map* map,              /* modified at return */          int mode, -        uint32_t *nfrags, -        snd_pcm_uframes_t *period_size, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_size,   /* modified at return */          snd_pcm_uframes_t tsched_size, -        pa_bool_t *use_mmap, -        pa_bool_t *use_tsched, -        const pa_alsa_profile_info *profile); +        pa_bool_t *use_mmap,              /* modified at return */ +        pa_bool_t *use_tsched,            /* modified at return */ +        pa_alsa_mapping *mapping);  /* Opens the explicit ALSA device */  snd_pcm_t *pa_alsa_open_by_device_string( -        const char *device, -        char **dev, -        pa_sample_spec *ss, -        pa_channel_map* map, +        const char *dir, +        char **dev,                       /* modified at return */ +        pa_sample_spec *ss,               /* modified at return */ +        pa_channel_map* map,              /* modified at return */          int mode, -        uint32_t *nfrags, -        snd_pcm_uframes_t *period_size, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_size,   /* modified at return */          snd_pcm_uframes_t tsched_size, -        pa_bool_t *use_mmap, -        pa_bool_t *use_tsched, +        pa_bool_t *use_mmap,              /* modified at return */ +        pa_bool_t *use_tsched,            /* modified at return */          pa_bool_t require_exact_channel_number); -int pa_alsa_probe_profiles( +/* Opens the explicit ALSA device with a fallback list */ +snd_pcm_t *pa_alsa_open_by_template( +        char **template,          const char *dev_id, -        const pa_sample_spec *ss, -        void (*cb)(const pa_alsa_profile_info *sink, const pa_alsa_profile_info *source, void *userdata), -        void *userdata); - -int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); +        char **dev,                       /* modified at return */ +        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 tsched_size, +        pa_bool_t *use_mmap,              /* modified at return */ +        pa_bool_t *use_tsched,            /* modified at return */ +        pa_bool_t require_exact_channel_number); -void pa_alsa_dump(snd_pcm_t *pcm); +void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm);  void pa_alsa_dump_status(snd_pcm_t *pcm); -void pa_alsa_redirect_errors_inc(void); -void pa_alsa_redirect_errors_dec(void); +void pa_alsa_refcnt_inc(void); +void pa_alsa_refcnt_dec(void);  void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info);  void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card);  void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm); +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name);  pa_bool_t pa_alsa_init_description(pa_proplist *p);  int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); @@ -134,9 +132,13 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_si  int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss);  char *pa_alsa_get_driver_name(int card); -  char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm);  char *pa_alsa_get_reserve_name(const char *device); +pa_bool_t pa_alsa_pcm_is_hw(snd_pcm_t *pcm); +pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm); + +const char* pa_alsa_strerror(int errnum); +  #endif diff --git a/src/modules/alsa/mixer/paths/analog-input-aux.conf b/src/modules/alsa/mixer/paths/analog-input-aux.conf new file mode 100644 index 00000000..db78eb48 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-aux.conf @@ -0,0 +1,62 @@ +# 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. + +; For devices where an 'Aux' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 +name = analog-input + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-fm.conf b/src/modules/alsa/mixer/paths/analog-input-fm.conf new file mode 100644 index 00000000..baf674aa --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-fm.conf @@ -0,0 +1,62 @@ +# 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. + +; For devices where an 'FM' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 70 +name = analog-input-radio + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf new file mode 100644 index 00000000..4be5722d --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf @@ -0,0 +1,61 @@ +# 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. + +; For devices where a 'Line' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Line] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-mic-line.conf b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf new file mode 100644 index 00000000..f7f30854 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf @@ -0,0 +1,63 @@ +# 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. + +; For devices where a 'Mic/Line' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 +name = analog-input + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf b/src/modules/alsa/mixer/paths/analog-input-mic.conf new file mode 100644 index 00000000..2a36f2f3 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf @@ -0,0 +1,63 @@ +# 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. + +; For devices where a 'Mic' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 100 +name = analog-input-microphone + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf.common b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common new file mode 100644 index 00000000..b35e7af8 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common @@ -0,0 +1,63 @@ +# 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. + +; Common element for all microphone inputs +; +; See analog-output.conf.common for an explanation on the directives + +;;; 'Mic Select' + +[Element Mic Select] +enumeration = select + +[Option Mic Select:Mic1] +name = input-microphone +priority = 20 + +[Option Mic Select:Mic2] +name = input-microphone +priority = 19 + +;;; Various Boosts + +[Element Mic Boost (+20dB)] +switch = select +volume = merge + +[Option Mic Boost (+20dB):on] +name = input-boost-on + +[Option Mic Boost (+20dB):off] +name = input-boost-off + +[Element Mic Boost] +switch = select +volume = merge + +[Option Mic Boost:on] +name = input-boost-on + +[Option Mic Boost:off] +name = input-boost-off + +[Element Front Mic Boost] +switch = select + +[Option Front Mic Boost:on] +name = input-boost-on + +[Option Front Mic Boost:off] +name = input-boost-off diff --git a/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf new file mode 100644 index 00000000..8531ec70 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf @@ -0,0 +1,62 @@ +# 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. + +; For devices where a 'TV Tuner' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 70 +name = analog-input-video + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-video.conf b/src/modules/alsa/mixer/paths/analog-input-video.conf new file mode 100644 index 00000000..74c76f07 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-video.conf @@ -0,0 +1,61 @@ +# 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. + +; For devices where a 'Video' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 70 + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input.conf b/src/modules/alsa/mixer/paths/analog-input.conf new file mode 100644 index 00000000..5055f90a --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input.conf @@ -0,0 +1,54 @@ +# 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. + +; A fallback for devices that lack seperate Mic/Line/Aux/Video/TV +; Tuner/FM elements +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 100 + +[Element Capture] +required = volume +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +required-absent = any + +[Element Line] +required-absent = any + +[Element Aux] +required-absent = any + +[Element Video] +required-absent = any + +[Element Mic/Line] +required-absent = any + +[Element TV Tuner] +required-absent = any + +[Element FM] +required-absent = any + +.include analog-input.conf.common +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input.conf.common b/src/modules/alsa/mixer/paths/analog-input.conf.common new file mode 100644 index 00000000..6728a6ae --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input.conf.common @@ -0,0 +1,257 @@ +# 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. + +; Mixer path for PulseAudio's ALSA backend, common elements for all +; input paths. If multiple options by the same id are discovered they +; will be suffixed with a number to distuingish them, in the same +; order they appear here. +; +; Source selection should use the following names: +; +;       input                       -- If we don't know the exact kind of input +;       input-microphone +;       input-microphone-internal +;       input-microphone-external +;       input-linein +;       input-video +;       input-radio +;       input-docking-microphone +;       input-docking-linein +;       input-docking +; +;  We explicitly don't want to wrap the following sources: +; +;       CD +;       Synth/MIDI +;       Phone +;       Mix +;       Digital/SPDIF +;       Master +;       PC Speaker +; +; See analog-output.conf.common for an explanation on the directives + +;;; 'Input Source Select' + +[Element Input Source Select] +enumeration = select + +[Option Input Source Select:Input1] +name = input +priority = 10 + +[Option Input Source Select:Input2] +name = input +priority = 5 + +;;; 'Input Source' + +[Element Input Source] +enumeration = select + +[Option Input Source:Mic] +name = input-microphone +priority = 20 + +[Option Input Source:Microphone] +name = input-microphone +priority = 20 + +[Option Input Source:Front Mic] +name = input-microphone +priority = 19 + +[Option Input Source:Front Microphone] +name = input-microphone +priority = 19 + +[Option Input Source:Line] +name = input-linein +priority = 18 + +[Option Input Source:Line-In] +name = input-linein +priority = 18 + +[Option Input Source:Line In] +name = input-linein +priority = 18 + +;;; ' Capture Source' + +[Element Capture Source] +enumeration = select + +[Option Capture Source:TV Tuner] +name = input-video + +[Option Capture Source:FM] +name = input-radio + +[Option Capture Source:Mic/Line] +name = input + +[Option Capture Source:Line/Mic] +name = input + +[Option Capture Source:Mic] +name = input-microphone + +[Option Capture Source:Microphone] +name = input-microphone + +[Option Capture Source:Int Mic] +name = input-microphone-internal + +[Option Capture Source:Int DMic] +name = input-microphone-internal + +[Option Capture Source:Internal Mic] +name = input-microphone-internal + +[Option Capture Source:iMic] +name = input-microphone-internal + +[Option Capture Source:i-Mic] +name = input-microphone-internal + +[Option Capture Source:Internal Microphone] +name = input-microphone-internal + +[Option Capture Source:Front Mic] +name = input-microphone + +[Option Capture Source:Front Microphone] +name = input-microphone + +[Option Capture Source:Rear Mic] +name = input-microphone + +[Option Capture Source:Mic1] +name = input-microphone + +[Option Capture Source:Mic2] +name = input-microphone + +[Option Capture Source:D-Mic] +name = input-microphone + +[Option Capture Source:IntMic] +name = input-microphone-internal + +[Option Capture Source:ExtMic] +name = input-microphone-external + +[Option Capture Source:Ext Mic] +name = input-microphone-external + +[Option Capture Source:E-Mic] +name = input-microphone-external + +[Option Capture Source:e-Mic] +name = input-microphone-external + +[Option Capture Source:LineIn] +name = input-linein + +[Option Capture Source:Analog] +name = input + +[Option Capture Source:Line] +name = input-linein + +[Option Capture Source:Line-In] +name = input-linein + +[Option Capture Source:Line In] +name = input-linein + +[Option Capture Source:Video] +name = input-video + +[Option Capture Source:Aux] +name = input + +[Option Capture Source:Aux0] +name = input + +[Option Capture Source:Aux1] +name = input + +[Option Capture Source:Aux2] +name = input + +[Option Capture Source:Aux3] +name = input + +[Option Capture Source:AUX IN] +name = input + +[Option Capture Source:Aux In] +name = input + +[Option Capture Source:AOUT] +name = input + +[Option Capture Source:AUX] +name = input + +[Option Capture Source:Cam Mic] +name = input-microphone + +[Option Capture Source:Digital Mic] +name = input-microphone + +[Option Capture Source:Digital Mic 1] +name = input-microphone + +[Option Capture Source:Digital Mic 2] +name = input-microphone + +[Option Capture Source:Analog Inputs] +name = input + +[Option Capture Source:Unknown1] +name = input + +[Option Capture Source:Unknown2] +name = input + +[Option Capture Source:Docking-Station] +name = input-docking + +[Option Capture Source:Dock Mic] +name = input-docking-microphone + +;;; Various Boosts + +[Element Capture Boost] +switch = select + +[Option Capture Boost:on] +name = input-boost-on + +[Option Capture Boost:off] +name = input-boost-off + +[Element Auto Gain Control] +switch = select + +[Option Auto Gain Control:on] +name = input-agc-on + +[Option Auto Gain Control:off] +name = input-agc-off diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf new file mode 100644 index 00000000..c018e0eb --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf @@ -0,0 +1,71 @@ +# 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 'Headphone' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 + +[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 + +[Element Headphone] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Sourround] +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-lfe-on-mono.conf b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf new file mode 100644 index 00000000..7a267890 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf @@ -0,0 +1,72 @@ +# 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. + +; Intended for usage in laptops that have a seperate LFE speaker +; connected to the Master mono connector +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 40 + +[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-no-lfe +override-map.2 = all-left,all-right + +[Element Master Mono] +required = any +switch = mute +volume = merge +override-map.1 = lfe +override-map.2 = lfe,lfe + +[Element Headphone] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Sourround] +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-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf new file mode 100644 index 00000000..f6cb9f8a --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf @@ -0,0 +1,69 @@ +# 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. + +; Intended for usage on boards that have a seperate Mono output plug. +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 50 + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = off +volume = off + +[Element Master Mono] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Headphone] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Sourround] +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.conf b/src/modules/alsa/mixer/paths/analog-output.conf new file mode 100644 index 00000000..ea108aaf --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output.conf @@ -0,0 +1,80 @@ +# 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. + +; Intended for the 'default' output +; +; 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 + +[Element Headphone] +switch = off +volume = off + +[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.common b/src/modules/alsa/mixer/paths/analog-output.conf.common new file mode 100644 index 00000000..cc1185f4 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output.conf.common @@ -0,0 +1,111 @@ +# 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. + +; Common part of all paths + +; So here's generally how mixer paths are used by PA: PA goes through +; a mixer path file from top to bottom and checks if a mixer element +; described therein exists. If so it is added to the list of mixer +; elements PA will control, keeping the order it read them in. If a +; mixer element described here has set the required= or +; required-absent= directives a path might not be accepted as valid +; and is ignored in its entirety (see below). However usually if a +; element listed here is missing this one element is ignored but not +; the entire path. +; +; When a device shall be muted/unmuted *all* elements listed in a path +; file with "switch = mute" will be toggled. +; +; When a device shall change its volume, PA will got through the list +; of all elements with "volume = merge" and set the volume on the +; first element. If that element does not support dB volumes, this is +; where the story ends. If it does support dB volumes, PA divides the +; requested volume by the volume that was set on this element, and +; then go on to the next element with "volume = merge" and then set +; that there, and so on.  That way the first volume element in the +; path will be the one that does the 'biggest' part of the overall +; volume adjustment, with the remaining elements usually being set to +; some value next to 0dB. This logic makes sure we get the full range +; over all volume sliders and a very high granularity of volumes +; already in hardware. +; +; All switches and enumerations set to "select" are exposed via the +; "port" functionality of sinks/sources. Basically every possible +; switch setting and every possible enumeration setting will be +; combined and made into a "port". So make sure you don't list too +; many switches/enums for exposing, because the number of ports might +; rise exponentially. +; +; Only one path can be selected at a time. All paths that are valid +; for an audio device will be exposed as "port" for the sink/source. + + +; [General] +; priority = ...                         # Priority for this path +; description = ... +; +; [Option ...:...]                       # For each option of an enumeration or switch element +;                                        # that shall be exposed as a sink/source port. Needs to +;                                        # be named after the Element, followed by a colon, followed +;                                        # by the option name, resp. on/off if the element is a switch. +; name = ...                             # Logical name to use in the path identifier +; priority = ...                         # Priority if this is made into a device port +; +; [Element ...]                          # For each element that we shall control +; required = ignore | switch | volume | enumeration | any     # If set, require this element to be of this kind and available, +;                                                             # otherwise don't consider this path valid for the card +; required-absent = ignore | switch | volume                  # If set, require this element to not be of this kind and not +;                                                             # available, otherwise don't consider this path valid for the card +; +; switch = ignore | mute | off | on | select                  # What to do with this switch: ignore it, make it follow mute status, +;                                                             # always set it to off, always to on, or make it selectable as port. +;                                                             # If set to 'select' you need to define an Option section for on +;                                                             # and off +; volume = ignore | merge | off | zero   # What to do with this volume: ignore it, merge it into the device +;                                        # volume slider, always set it to the lowest value possible, or always +;                                        # set it to 0 dB (for whatever that means) +; enumeration = ignore | select          # What to do with this enumeration, ignore it or make it selectable +;                                        # via device ports. If set to 'select' you need to define an Option section +;                                        # for each of the items you want to expose +; direction = playback | capture         # Is this relevant only for playback or capture? If not set this will implicitly be +;                                        # set the direction of the PCM device is opened as. Generally this doesn't need to be set +;                                        # unless you have a broken driver that has playback controls marked for capture or vice +;                                        # versa +; direction-try-other = no | yes         # If the element does not supported what is requested, try the other direction, too? +; +; override-map.1 = ...                   # Override the channel mask of the mixer control if the control only exposes a single channel +; override-map.2 = ...                   # Override the channel masks of the mixer control if the control only exposes two channels +;                                        # Override maps should list for each element channel which high-level channels it controls via a +;                                        # channel mask. A channel mask may either be the name of a single channel, or the words "all-left", +;                                        # "all-right", "all-center", "all-front", "all-rear", and "all" to encode a specific subset of +;                                        # channels in a mask + +[Element PCM] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element External Amplifier] +switch = select + +[Option External Amplifier:on] +name = output-amplifier-on +priority = 0 + +[Option External Amplifier:off] +name = output-amplifier-off +priority = 10 diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules new file mode 100644 index 00000000..ea1a2fed --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules @@ -0,0 +1,26 @@ +# do not edit this file, it will be overwritten on update + +# 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. + +SUBSYSTEM!="sound", GOTO="pulseaudio_end" +ACTION!="change", GOTO="pulseaudio_end" +KERNEL!="card*", GOTO="pulseaudio_end" + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf" + +LABEL="pulseaudio_end" diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf new file mode 100644 index 00000000..ac41a8d3 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/default.conf @@ -0,0 +1,144 @@ +# 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. + +; Default profile definitions for the ALSA backend of PulseAudio. This +; is used as fallback for all cards that have no special mapping +; assigned. (and should be good enough for the vast majority of +; cards).  Use the udev property PULSE_PROFILE_SET to assign a +; different profile set than this one to a device.  So what is this +; about? Simply, what we do here is map ALSA devices to how they are +; exposed in PA. We say which ALSA device string to use to open a +; device, which channel mapping to use then, and which mixer path to +; use. This is encoded in a 'mapping'. Multiple of these mappings can +; be bound together in a 'profile' which is then directly exposed in +; the UI as a card profile. Each mapping assigned to a profile will +; result in one sink/source to be created if the profile is selected +; for the card. + +; [General] +; auto-profiles = no | yes                  # Instead of defining all profiles manually, autogenerate +;                                           # them by combining every input mapping with every output mapping. +; +; [Mapping id] +; device-strings = ...                      # ALSA device string. %f will be replaced by the card identifier. +; channel-map = ...                         # Channel mapping to use for this device +; description = ... +; paths-input = ...                         # A list of mixer paths to use. Every path in this list will be probed. +;                                           # If multiple are found to be working they will be available as device ports +; paths-output = ... +; element-input = ...                       # Instead of configuring a full mixer path simply configure a single +;                                           # mixer element for volume/mute handling +; element-output = ... +; priority = ... +; direction = any | input | output          # Only useful for? +; +; [Profile id] +; input-mappings = ...                      # Lists mappings for sources on this profile, those mapping must be +;                                           # defined in this file too +; output-mappings = ...                     # Lists mappings for sinks on this profile, those mappings must be +;                                           # defined in this file too +; description = ... +; priority = ...                            # Numeric value to deduce priority for this profile +; skip-probe = no | yes                     # Skip probing for availability? If this is yes then this profile +;                                           # will be assumed as working without probing. Makes initialization +;                                           # a bit faster but only works if the card is really known well. + +[General] +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-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-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 +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 +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 +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 +priority = 8 +direction = output + +[Mapping analog-surround-71] +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 +priority = 7 +direction = output + +[Mapping iec958-stereo] +device-strings = iec958:%f +channel-map = left,right +priority = 5 + +[Mapping iec958-surround-40] +device-strings = iec958:%f +channel-map = front-left,front-right,rear-left,rear-right +priority = 1 + +[Mapping iec958-ac3-surround-40] +device-strings = a52:%f +channel-map = front-left,front-right,rear-left,rear-right +priority = 2 +direction = output + +[Mapping iec958-ac3-surround-51] +device-strings = a52:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 3 +direction = output + +[Mapping hdmi-stereo] +device-strings = hdmi:%f +channel-map = left,right +priority = 4 +direction = output + +; An example for defining multiple-sink profiles +#[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo] +#description = Foobar +#output-mappings = analog-stereo iec958-stereo +#input-mappings = analog-stereo diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf new file mode 100644 index 00000000..2b835308 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf @@ -0,0 +1,91 @@ +# 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. + +; Native Instruments Audio 4 DJ +; +; This card has two stereo pairs of input and two stereo pairs of +; output, named channels A and B. Channel B has an additional +; Headphone connector. +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-b-output] +description = Analog Stereo Channel B (Headphones) +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-b-input] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channels A, B (Headphones) +output-mappings = analog-stereo-a analog-stereo-b-output +input-mappings = analog-stereo-a analog-stereo-b-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-a] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-a +input-mappings = analog-stereo-a +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Duplex Channel B (Headphones) +output-mappings = analog-stereo-b-output +input-mappings = analog-stereo-b-input +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-a] +description = Analog Stereo Output Channel A +output-mappings = analog-stereo-a +priority = 5 +skip-probe = yes + +[Profile output:analog-stereo-b] +description = Analog Stereo Output Channel B (Headphones) +output-mappings = analog-stereo-b-output +priority = 6 +skip-probe = yes + +[Profile input:analog-stereo-a] +description = Analog Stereo Input Channel A +input-mappings = analog-stereo-a +priority = 2 +skip-probe = yes + +[Profile input:analog-stereo-b] +description = Analog Stereo Input Channel B +input-mappings = analog-stereo-b-input +priority = 1 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf new file mode 100644 index 00000000..3fe3cc56 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf @@ -0,0 +1,162 @@ +# 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. + +; Native Instruments Audio 8 DJ +; +; This card has four stereo pairs of input and four stereo pairs of +; output, named channels A to D. Channel C has an additional Mic/Line +; connector, channel D an additional Headphone connector. +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-b] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right + +# Since we want to set a different description for channel C's/D's input +# and output we define two seperate mappings for them +[Mapping analog-stereo-c-output] +description = Analog Stereo Channel C +device-strings = hw:%f,0,2 +channel-map = left,right +direction = output + +[Mapping analog-stereo-c-input] +description = Analog Stereo Channel C (Line/Mic) +device-strings = hw:%f,0,2 +channel-map = left,right +direction = input + +[Mapping analog-stereo-d-output] +description = Analog Stereo Channel D (Headphones) +device-strings = hw:%f,0,3 +channel-map = left,right +direction = output + +[Mapping analog-stereo-d-input] +description = Analog Stereo Channel D +device-strings = hw:%f,0,3 +channel-map = left,right +direction = input + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channels A, B, C (Line/Mic), D (Headphones) +output-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-output analog-stereo-d-output +input-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-input analog-stereo-d-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-d+input:analog-stereo-c] +description = Analog Stereo Channel D (Headphones) Output, Channel C (Line/Mic) Input +output-mappings = analog-stereo-d-output +input-mappings = analog-stereo-c-input +priority = 90 +skip-probe = yes + +[Profile output:analog-stereo-c-d+input:analog-stereo-c-d] +description = Analog Stereo Duplex Channels C (Line/Mic), D (Line/Mic) +output-mappings = analog-stereo-c-output analog-stereo-d-output +input-mappings = analog-stereo-c-input analog-stereo-d-input +priority = 80 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-a] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-a +input-mappings = analog-stereo-a +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Duplex Channel B +output-mappings = analog-stereo-b +input-mappings = analog-stereo-b +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-c+input:analog-stereo-c] +description = Analog Stereo Duplex Channel C (Line/Mic) +output-mappings = analog-stereo-c-output +input-mappings = analog-stereo-c-input +priority = 60 +skip-probe = yes + +[Profile output:analog-stereo-d+input:analog-stereo-d] +description = Analog Stereo Duplex Channel D (Headphones) +output-mappings = analog-stereo-d-output +input-mappings = analog-stereo-d-input +priority = 70 +skip-probe = yes + +[Profile output:analog-stereo-a] +description = Analog Stereo Output Channel A +output-mappings = analog-stereo-a +priority = 6 +skip-probe = yes + +[Profile output:analog-stereo-b] +description = Analog Stereo Output Channel B +output-mappings = analog-stereo-b +priority = 5 +skip-probe = yes + +[Profile output:analog-stereo-c] +description = Analog Stereo Output Channel C +output-mappings = analog-stereo-c-output +priority = 7 +skip-probe = yes + +[Profile output:analog-stereo-d] +description = Analog Stereo Output Channel D (Headphones) +output-mappings = analog-stereo-d-output +priority = 8 +skip-probe = yes + +[Profile input:analog-stereo-a] +description = Analog Stereo Input Channel A +input-mappings = analog-stereo-a +priority = 2 +skip-probe = yes + +[Profile input:analog-stereo-b] +description = Analog Stereo Input Channel B +input-mappings = analog-stereo-b +priority = 1 +skip-probe = yes + +[Profile input:analog-stereo-c] +description = Analog Stereo Input Channel C (Line/Mic) +input-mappings = analog-stereo-c-input +priority = 4 +skip-probe = yes + +[Profile input:analog-stereo-d] +description = Analog Stereo Input Channel D +input-mappings = analog-stereo-d-input +priority = 3 +skip-probe = yes diff --git a/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 b/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 new file mode 100644 index 00000000..082c9a1b --- /dev/null +++ b/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 @@ -0,0 +1,150 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 29 [94%] [-3.00dB] [on] +  Front Right: Playback 29 [94%] [-3.00dB] [on] +Simple mixer control 'Master Mono',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 23 [74%] [0.00dB] [on] +  Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'Surround',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-46.50dB] [off] +  Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Surround Jack Mode',0 +  Capabilities: enum +  Items: 'Shared' 'Independent' +  Item0: 'Shared' +Simple mixer control 'Center',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'LFE',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Line',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'CD',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-34.50dB] [off] +  Front Left: Capture [on] +  Front Right: Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Mic Select',0 +  Capabilities: enum +  Items: 'Mic1' 'Mic2' +  Item0: 'Mic1' +Simple mixer control 'Video',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Phone',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 31 [100%] [12.00dB] [off] +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined cswitch cswitch-joined +  Playback channels: Mono +  Capture channels: Mono +  Mono: Playback [off] Capture [off] +Simple mixer control 'IEC958 Playback AC97-SPSA',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 3 +  Mono: 0 [0%] +Simple mixer control 'IEC958 Playback Source',0 +  Capabilities: enum +  Items: 'PCM' 'Analog In' 'IEC958 In' +  Item0: 'PCM' +Simple mixer control 'PC Speaker',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 15 +  Mono: Playback 0 [0%] [-45.00dB] [on] +Simple mixer control 'Aux',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [on] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [on] Capture [off] +Simple mixer control 'Mono Output Select',0 +  Capabilities: enum +  Items: 'Mix' 'Mic' +  Item0: 'Mix' +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch cswitch-joined +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 15 +  Front Left: Capture 12 [80%] [18.00dB] [on] +  Front Right: Capture 12 [80%] [18.00dB] [on] +Simple mixer control 'Mix',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Channel Mode',0 +  Capabilities: enum +  Items: '2ch' '4ch' '6ch' +  Item0: '2ch' +Simple mixer control 'Duplicate Front',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'External Amplifier',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x b/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x new file mode 100644 index 00000000..b8f61fab --- /dev/null +++ b/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x @@ -0,0 +1,24 @@ +Simple mixer control 'FM',0 +  Capabilities: cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Mono +  Mono: Capture [off] +Simple mixer control 'Mic/Line',0 +  Capabilities: cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Mono +  Mono: Capture [off] +Simple mixer control 'Capture',0 +  Capabilities: cvolume cvolume-joined +  Capture channels: Mono +  Limits: Capture 0 - 15 +  Mono: Capture 13 [87%] +Simple mixer control 'Capture Boost',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] +Simple mixer control 'TV Tuner',0 +  Capabilities: cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Mono +  Mono: Capture [on] diff --git a/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 b/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 new file mode 100644 index 00000000..a500a817 --- /dev/null +++ b/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 @@ -0,0 +1,135 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 63 +  Mono: +  Front Left: Playback 63 [100%] [0.00dB] [on] +  Front Right: Playback 63 [100%] [0.00dB] [on] +Simple mixer control 'Master Mono',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Headphone',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-46.50dB] [off] +  Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control '3D Control - Center',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 15 +  Mono: 0 [0%] +Simple mixer control '3D Control - Depth',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 15 +  Mono: 0 [0%] +Simple mixer control '3D Control - Switch',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 23 [74%] [0.00dB] [on] +  Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'Line',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on] +Simple mixer control 'CD',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 23 [74%] [0.00dB] [on] +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Mic Boost (+20dB)',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Mic Select',0 +  Capabilities: enum +  Items: 'Mic1' 'Mic2' +  Item0: 'Mic1' +Simple mixer control 'Video',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Phone',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-34.50dB] [off] +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'PC Speaker',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 15 +  Mono: Playback 0 [0%] [-45.00dB] [off] +Simple mixer control 'Aux',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mono Output Select',0 +  Capabilities: enum +  Items: 'Mix' 'Mic' +  Item0: 'Mic' +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch cswitch-joined +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 15 +  Front Left: Capture 15 [100%] [22.50dB] [on] +  Front Right: Capture 15 [100%] [22.50dB] [on] +Simple mixer control 'Mix',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'External Amplifier',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] diff --git a/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI b/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI new file mode 100644 index 00000000..244f24a8 --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI @@ -0,0 +1,4 @@ +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 b/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 new file mode 100644 index 00000000..165522fa --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 @@ -0,0 +1,62 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 63 +  Mono: +  Front Left: Playback 63 [100%] [3.00dB] [on] +  Front Right: Playback 63 [100%] [3.00dB] [on] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 23 [74%] [0.00dB] [on] +  Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'CD',0 +  Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Mono +  Limits: Playback 0 - 31 +  Mono: Capture [off] +  Front Left: Playback 0 [0%] [-34.50dB] [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Mono +  Limits: Playback 0 - 31 +  Mono: Capture [on] +  Front Left: Playback 0 [0%] [-34.50dB] [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic Boost',0 +  Capabilities: volume +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: 0 - 3 +  Front Left: 0 [0%] +  Front Right: 0 [0%] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Default PCM',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Playback Source',0 +  Capabilities: enum +  Items: 'PCM' 'ADC' +  Item0: 'PCM' +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 15 +  Front Left: Capture 0 [0%] [0.00dB] [on] +  Front Right: Capture 0 [0%] [0.00dB] [on] +Simple mixer control 'Mix',0 +  Capabilities: cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Mono +  Mono: Capture [off] diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Conexant CX20551 (Waikiki) b/src/modules/alsa/mixer/samples/HDA Intel--Conexant CX20551 (Waikiki) new file mode 100644 index 00000000..16f7c49b --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA Intel--Conexant CX20551 (Waikiki) @@ -0,0 +1,42 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 30 +  Mono: +  Front Left: Playback 17 [57%] [-21.00dB] [on] +  Front Right: Playback 17 [57%] [-21.00dB] [on] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 255 +  Mono: +  Front Left: Playback 230 [90%] [-5.00dB] [on] +  Front Right: Playback 230 [90%] [-5.00dB] [on] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 30 +  Mono: +  Front Left: Playback 19 [63%] [-6.00dB] [on] +  Front Right: Playback 19 [63%] [-6.00dB] [on] +Simple mixer control 'Mic Boost',0 +  Capabilities: volume +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: 0 - 3 +  Front Left: 3 [100%] +  Front Right: 3 [100%] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] +Simple mixer control 'IEC958 Default PCM',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 14 +  Front Left: Capture 0 [0%] [0.00dB] [off] +  Front Right: Capture 0 [0%] [0.00dB] [off]
\ No newline at end of file diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A b/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A new file mode 100644 index 00000000..28a2e73c --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A @@ -0,0 +1,113 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 64 +  Mono: Playback 64 [100%] [0.00dB] [on] +Simple mixer control 'Headphone',0 +  Capabilities: pswitch +  Playback channels: Front Left - Front Right +  Mono: +  Front Left: Playback [on] +  Front Right: Playback [on] +Simple mixer control 'PCM',0 +  Capabilities: pvolume +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 255 +  Mono: +  Front Left: Playback 255 [100%] [0.00dB] +  Front Right: Playback 255 [100%] [0.00dB] +Simple mixer control 'Front',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 64 +  Mono: +  Front Left: Playback 44 [69%] [-20.00dB] [on] +  Front Right: Playback 44 [69%] [-20.00dB] [on] +Simple mixer control 'Front Mic',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-34.50dB] [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Front Mic Boost',0 +  Capabilities: volume +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: 0 - 3 +  Front Left: 0 [0%] +  Front Right: 0 [0%] +Simple mixer control 'Surround',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 64 +  Mono: +  Front Left: Playback 0 [0%] [-64.00dB] [on] +  Front Right: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'Center',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 64 +  Mono: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'LFE',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 64 +  Mono: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'Side',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 64 +  Mono: +  Front Left: Playback 0 [0%] [-64.00dB] [on] +  Front Right: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'Line',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-34.50dB] [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-34.50dB] [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic Boost',0 +  Capabilities: volume +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: 0 - 3 +  Front Left: 0 [0%] +  Front Right: 0 [0%] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined cswitch cswitch-joined +  Playback channels: Mono +  Capture channels: Mono +  Mono: Playback [on] Capture [on] +Simple mixer control 'IEC958 Default PCM',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 46 +  Front Left: Capture 23 [50%] [7.00dB] [on] +  Front Right: Capture 23 [50%] [7.00dB] [on] +Simple mixer control 'Capture',1 +  Capabilities: cvolume cswitch +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 46 +  Front Left: Capture 0 [0%] [-16.00dB] [off] +  Front Right: Capture 0 [0%] [-16.00dB] [off] +Simple mixer control 'Input Source',0 +  Capabilities: cenum +  Items: 'Mic' 'Front Mic' 'Line' +  Item0: 'Mic' +Simple mixer control 'Input Source',1 +  Capabilities: cenum +  Items: 'Mic' 'Front Mic' 'Line' +  Item0: 'Mic' diff --git a/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A b/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A new file mode 100644 index 00000000..3ddd8af6 --- /dev/null +++ b/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A @@ -0,0 +1,128 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 63 +  Mono: +  Front Left: Playback 44 [70%] [-28.50dB] [on] +  Front Right: Playback 60 [95%] [-4.50dB] [on] +Simple mixer control 'Master Mono',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 17 [55%] [-21.00dB] [on] +Simple mixer control '3D Control - Center',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 15 +  Mono: 0 [0%] +Simple mixer control '3D Control - Depth',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 15 +  Mono: 0 [0%] +Simple mixer control '3D Control - Switch',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 9 [29%] [-21.00dB] [on] +  Front Right: Playback 9 [29%] [-21.00dB] [on] +Simple mixer control 'PCM Out Path & Mute',0 +  Capabilities: enum +  Items: 'pre 3D' 'post 3D' +  Item0: 'pre 3D' +Simple mixer control 'Line',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'CD',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 9 [29%] [-21.00dB] [on] Capture [off] +  Front Right: Playback 9 [29%] [-21.00dB] [on] Capture [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-34.50dB] [off] +  Front Left: Capture [on] +  Front Right: Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Mic Select',0 +  Capabilities: enum +  Items: 'Mic1' 'Mic2' +  Item0: 'Mic1' +Simple mixer control 'Video',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Phone',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-34.50dB] [off] +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'PC Speaker',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 15 +  Mono: Playback 8 [53%] [-21.00dB] [on] +Simple mixer control 'Aux',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mono Output Select',0 +  Capabilities: enum +  Items: 'Mix' 'Mic' +  Item0: 'Mix' +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch cswitch-joined +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 15 +  Front Left: Capture 13 [87%] [19.50dB] [on] +  Front Right: Capture 13 [87%] [19.50dB] [on] +Simple mixer control 'Mix',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'External Amplifier',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer b/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer new file mode 100644 index 00000000..38cf6778 --- /dev/null +++ b/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer @@ -0,0 +1,27 @@ +Simple mixer control 'Bass',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 48 +  Mono: 22 [46%] +Simple mixer control 'Bass Boost',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Treble',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 48 +  Mono: 25 [52%] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 44 +  Mono: +  Front Left: Playback 10 [23%] [-31.00dB] [on] +  Front Right: Playback 10 [23%] [-31.00dB] [on] +Simple mixer control 'Auto Gain Control',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] diff --git a/src/modules/alsa/mixer/samples/USB Audio--USB Mixer b/src/modules/alsa/mixer/samples/USB Audio--USB Mixer new file mode 100644 index 00000000..9cb4fa7f --- /dev/null +++ b/src/modules/alsa/mixer/samples/USB Audio--USB Mixer @@ -0,0 +1,37 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 255 +  Mono: Playback 105 [41%] [-28.97dB] [on] +Simple mixer control 'Line',0 +  Capabilities: pvolume cvolume pswitch pswitch-joined cswitch cswitch-joined +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 255 Capture 0 - 128 +  Front Left: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off] +  Front Right: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pvolume-joined cvolume cvolume-joined pswitch pswitch-joined cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Mono +  Limits: Playback 0 - 255 Capture 0 - 128 +  Mono: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [on] +Simple mixer control 'Mic Capture',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 In',0 +  Capabilities: cswitch cswitch-joined +  Capture channels: Mono +  Mono: Capture [off] +Simple mixer control 'Input 1',0 +  Capabilities: cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Mono +  Mono: Capture [off] +Simple mixer control 'Input 2',0 +  Capabilities: cswitch cswitch-joined cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Mono +  Mono: Capture [off] diff --git a/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer b/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer new file mode 100644 index 00000000..783f826f --- /dev/null +++ b/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer @@ -0,0 +1,5 @@ +Simple mixer control 'Mic',0 +  Capabilities: cvolume cvolume-joined cswitch cswitch-joined +  Capture channels: Mono +  Limits: Capture 0 - 3072 +  Mono: Capture 1536 [50%] [23.00dB] [on] diff --git a/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 b/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 new file mode 100644 index 00000000..15e7b5a6 --- /dev/null +++ b/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 @@ -0,0 +1,211 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 31 [100%] [0.00dB] [on] +  Front Right: Playback 31 [100%] [0.00dB] [on] +Simple mixer control 'Master Mono',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Master Surround',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-46.50dB] [off] +  Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Headphone Jack Sense',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 23 [74%] [0.00dB] [on] +  Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'Surround',0 +  Capabilities: pvolume pswitch +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-46.50dB] [off] +  Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Surround Jack Mode',0 +  Capabilities: enum +  Items: 'Shared' 'Independent' +  Item0: 'Shared' +Simple mixer control 'Center',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 31 [100%] [0.00dB] [off] +Simple mixer control 'LFE',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Line',0 +  Capabilities: pvolume pswitch cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Line Jack Sense',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'CD',0 +  Capabilities: pvolume pswitch cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-34.50dB] [off] +  Front Left: Capture [on] +  Front Right: Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Mic Select',0 +  Capabilities: enum +  Items: 'Mic1' 'Mic2' +  Item0: 'Mic1' +Simple mixer control 'Video',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Phone',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Mono +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-34.50dB] [off] +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Output',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Playback AC97-SPSA',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 3 +  Mono: 3 [100%] +Simple mixer control 'IEC958 Playback Source',0 +  Capabilities: enum +  Items: 'AC-Link' 'A/D Converter' +  Item0: 'AC-Link' +Simple mixer control 'Aux',0 +  Capabilities: pvolume pswitch cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 15 +  Front Left: Capture 0 [0%] [0.00dB] [on] +  Front Right: Capture 0 [0%] [0.00dB] [on] +Simple mixer control 'Mix',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Channel Mode',0 +  Capabilities: enum +  Items: '2ch' '4ch' '6ch' +  Item0: '2ch' +Simple mixer control 'Downmix',0 +  Capabilities: enum +  Items: 'Off' '6 -> 4' '6 -> 2' +  Item0: 'Off' +Simple mixer control 'Exchange Front/Surround',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'External Amplifier',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] +Simple mixer control 'High Pass Filter Enable',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Input Source Select',0 +  Capabilities: enum +  Items: 'Input1' 'Input2' +  Item0: 'Input1' +Simple mixer control 'Input Source Select',1 +  Capabilities: enum +  Items: 'Input1' 'Input2' +  Item0: 'Input1' +Simple mixer control 'Spread Front to Surround and Center/LFE',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'VIA DXS',0 +  Capabilities: pvolume +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 31 [100%] [-48.00dB] +  Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'VIA DXS',1 +  Capabilities: pvolume +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 31 [100%] [-48.00dB] +  Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'VIA DXS',2 +  Capabilities: pvolume +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 31 [100%] [-48.00dB] +  Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'VIA DXS',3 +  Capabilities: pvolume +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 31 [100%] [-48.00dB] +  Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'V_REFOUT Enable',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ b/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ new file mode 100644 index 00000000..d4f3db62 --- /dev/null +++ b/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ @@ -0,0 +1,160 @@ +Simple mixer control 'Master',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 0 [0%] [-46.50dB] [off] +  Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'PCM',0 +  Capabilities: pvolume pswitch pswitch-joined +  Playback channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Mono: +  Front Left: Playback 31 [100%] [-48.00dB] [off] +  Front Right: Playback 31 [100%] [-48.00dB] [off] +Simple mixer control 'Surround',0 +  Capabilities: pswitch +  Playback channels: Front Left - Front Right +  Mono: +  Front Left: Playback [off] +  Front Right: Playback [off] +Simple mixer control 'Surround Jack Mode',0 +  Capabilities: enum +  Items: 'Shared' 'Independent' +  Item0: 'Shared' +Simple mixer control 'Center',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 31 [100%] [0.00dB] [off] +Simple mixer control 'LFE',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 31 +  Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Line',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'CD',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'Mic Select',0 +  Capabilities: enum +  Items: 'Mic1' 'Mic2' +  Item0: 'Mic1' +Simple mixer control 'Video',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Phone',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'IEC958',0 +  Capabilities: pswitch pswitch-joined cswitch cswitch-joined +  Playback channels: Mono +  Capture channels: Mono +  Mono: Playback [off] Capture [off] +Simple mixer control 'IEC958 Capture Monitor',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Capture Valid',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Output',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [off] +Simple mixer control 'IEC958 Playback AC97-SPSA',0 +  Capabilities: volume volume-joined +  Playback channels: Mono +  Capture channels: Mono +  Limits: 0 - 3 +  Mono: 3 [100%] +Simple mixer control 'IEC958 Playback Source',0 +  Capabilities: enum +  Items: 'AC-Link' 'ADC' 'SPDIF-In' +  Item0: 'AC-Link' +Simple mixer control 'PC Speaker',0 +  Capabilities: pvolume pvolume-joined pswitch pswitch-joined +  Playback channels: Mono +  Limits: Playback 0 - 15 +  Mono: Playback 0 [0%] [-45.00dB] [off] +Simple mixer control 'Aux',0 +  Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Playback channels: Front Left - Front Right +  Capture channels: Front Left - Front Right +  Limits: Playback 0 - 31 +  Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] +  Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mono Output Select',0 +  Capabilities: enum +  Items: 'Mix' 'Mic' +  Item0: 'Mix' +Simple mixer control 'Capture',0 +  Capabilities: cvolume cswitch cswitch-joined +  Capture channels: Front Left - Front Right +  Limits: Capture 0 - 15 +  Front Left: Capture 0 [0%] [0.00dB] [on] +  Front Right: Capture 0 [0%] [0.00dB] [on] +Simple mixer control 'Mix',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 +  Capabilities: cswitch cswitch-exclusive +  Capture exclusive group: 0 +  Capture channels: Front Left - Front Right +  Front Left: Capture [off] +  Front Right: Capture [off] +Simple mixer control 'Channel Mode',0 +  Capabilities: enum +  Items: '2ch' '4ch' '6ch' +  Item0: '2ch' +Simple mixer control 'DAC Clock Source',0 +  Capabilities: enum +  Items: 'AC-Link' 'SPDIF-In' 'Both' +  Item0: 'AC-Link' +Simple mixer control 'External Amplifier',0 +  Capabilities: pswitch pswitch-joined +  Playback channels: Mono +  Mono: Playback [on] +Simple mixer control 'Input Source Select',0 +  Capabilities: enum +  Items: 'Input1' 'Input2' +  Item0: 'Input1' +Simple mixer control 'Input Source Select',1 +  Capabilities: enum +  Items: 'Input1' 'Input2' +  Item0: 'Input1' diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index d5e2cdc2..55f6a6e2 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -32,6 +32,10 @@  #include <modules/reserve-wrap.h> +#ifdef HAVE_UDEV +#include <modules/udev-util.h> +#endif +  #include "alsa-util.h"  #include "alsa-sink.h"  #include "alsa-source.h" @@ -43,9 +47,12 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "name=<name for the card/sink/source, to be prefixed> " -        "card_name=<name for card> " -        "sink_name=<name for sink> " -        "source_name=<name for source> " +        "card_name=<name for the card> " +        "card_properties=<properties for the card> " +        "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> " +        "source_name=<name for the source> " +        "source_properties=<properties for the source> "          "device_id=<ALSA card index> "          "format=<sample format> "          "rate=<sample rate> " @@ -61,8 +68,11 @@ PA_MODULE_USAGE(  static const char* const valid_modargs[] = {      "name",      "card_name", +    "card_properties",      "sink_name", +    "sink_properties",      "source_name", +    "source_properties",      "device_id",      "format",      "rate", @@ -86,81 +96,53 @@ struct userdata {      char *device_id;      pa_card *card; -    pa_sink *sink; -    pa_source *source;      pa_modargs *modargs; -    pa_hashmap *profiles; +    pa_alsa_profile_set *profile_set;  };  struct profile_data { -    const pa_alsa_profile_info *sink_profile, *source_profile; +    pa_alsa_profile *profile;  }; -static void enumerate_cb( -        const pa_alsa_profile_info *sink, -        const pa_alsa_profile_info *source, -        void *userdata) { - -    struct userdata *u = userdata; -    char *t, *n; -    pa_card_profile *p; -    struct profile_data *d; -    unsigned bonus = 0; - -    if (sink && source) { -        n = pa_sprintf_malloc("output-%s+input-%s", sink->name, source->name); -        t = pa_sprintf_malloc(_("Output %s + Input %s"), sink->description, _(source->description)); -    } else if (sink) { -        n = pa_sprintf_malloc("output-%s", sink->name); -        t = pa_sprintf_malloc(_("Output %s"), _(sink->description)); -    } else { -        pa_assert(source); -        n = pa_sprintf_malloc("input-%s", source->name); -        t = pa_sprintf_malloc(_("Input %s"), _(source->description)); -    } - -    if (sink) { -        if (pa_channel_map_equal(&sink->map, &u->core->default_channel_map)) -            bonus += 50000; -        else if (sink->map.channels == u->core->default_channel_map.channels) -            bonus += 40000; -    } +static void add_profiles(struct userdata *u, pa_hashmap *h) { +    pa_alsa_profile *ap; +    void *state; -    if (source) { -        if (pa_channel_map_equal(&source->map, &u->core->default_channel_map)) -            bonus += 30000; -        else if (source->map.channels == u->core->default_channel_map.channels) -            bonus += 20000; -    } - -    pa_log_info("Found output profile '%s'", t); +    pa_assert(u); +    pa_assert(h); -    p = pa_card_profile_new(n, t, sizeof(struct profile_data)); +    PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) { +        struct profile_data *d; +        pa_card_profile *cp; +        pa_alsa_mapping *m; +        uint32_t idx; -    pa_xfree(t); -    pa_xfree(n); +        cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); +        cp->priority = ap->priority; -    p->priority = -        (sink ? sink->priority : 0) * 100 + -        (source ? source->priority : 0) + -        bonus; +        if (ap->output_mappings) { +            cp->n_sinks = pa_idxset_size(ap->output_mappings); -    p->n_sinks = !!sink; -    p->n_sources = !!source; +            PA_IDXSET_FOREACH(m, ap->output_mappings, idx) +                if (m->channel_map.channels > cp->max_sink_channels) +                    cp->max_sink_channels = m->channel_map.channels; +        } -    if (sink) -        p->max_sink_channels = sink->map.channels; -    if (source) -        p->max_source_channels = source->map.channels; +        if (ap->input_mappings) { +            cp->n_sources = pa_idxset_size(ap->input_mappings); -    d = PA_CARD_PROFILE_DATA(p); +            PA_IDXSET_FOREACH(m, ap->input_mappings, idx) +                if (m->channel_map.channels > cp->max_source_channels) +                    cp->max_source_channels = m->channel_map.channels; +        } -    d->sink_profile = sink; -    d->source_profile = source; +        d = PA_CARD_PROFILE_DATA(cp); +        d->profile = ap; -    pa_hashmap_put(u->profiles, p->name, p); +        pa_hashmap_put(h, cp->name, cp); +    }  }  static void add_disabled_profile(pa_hashmap *profiles) { @@ -170,7 +152,7 @@ static void add_disabled_profile(pa_hashmap *profiles) {      p = pa_card_profile_new("off", _("Off"), sizeof(struct profile_data));      d = PA_CARD_PROFILE_DATA(p); -    d->sink_profile = d->source_profile = NULL; +    d->profile = NULL;      pa_hashmap_put(profiles, p->name, p);  } @@ -178,6 +160,9 @@ static void add_disabled_profile(pa_hashmap *profiles) {  static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      struct userdata *u;      struct profile_data *nd, *od; +    uint32_t idx; +    pa_alsa_mapping *am; +    pa_queue *sink_inputs = NULL, *source_outputs = NULL;      pa_assert(c);      pa_assert(new_profile); @@ -186,67 +171,85 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      nd = PA_CARD_PROFILE_DATA(new_profile);      od = PA_CARD_PROFILE_DATA(c->active_profile); -    if (od->sink_profile != nd->sink_profile) { -        pa_queue *inputs = NULL; +    if (od->profile && od->profile->output_mappings) +        PA_IDXSET_FOREACH(am, od->profile->output_mappings, idx) { +            if (!am->sink) +                continue; -        if (u->sink) { -            if (nd->sink_profile) -                inputs = pa_sink_move_all_start(u->sink); +            if (nd->profile && +                nd->profile->output_mappings && +                pa_idxset_get_by_data(nd->profile->output_mappings, am, NULL)) +                continue; -            pa_alsa_sink_free(u->sink); -            u->sink = NULL; +            sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs); +            pa_alsa_sink_free(am->sink); +            am->sink = NULL;          } -        if (nd->sink_profile) { -            u->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, nd->sink_profile); +    if (od->profile && od->profile->input_mappings) +        PA_IDXSET_FOREACH(am, od->profile->input_mappings, idx) { +            if (!am->source) +                continue; -            if (inputs) { -                if (u->sink) -                    pa_sink_move_all_finish(u->sink, inputs, FALSE); -                else -                    pa_sink_move_all_fail(inputs); -            } +            if (nd->profile && +                nd->profile->input_mappings && +                pa_idxset_get_by_data(nd->profile->input_mappings, am, NULL)) +                continue; + +            source_outputs = pa_source_move_all_start(am->source, source_outputs); +            pa_alsa_source_free(am->source); +            am->source = NULL;          } -    } -    if (od->source_profile != nd->source_profile) { -        pa_queue *outputs = NULL; +    if (nd->profile && nd->profile->output_mappings) +        PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) { -        if (u->source) { -            if (nd->source_profile) -                outputs = pa_source_move_all_start(u->source); +            if (!am->sink) +                am->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, am); -            pa_alsa_source_free(u->source); -            u->source = NULL; +            if (sink_inputs && am->sink) { +                pa_sink_move_all_finish(am->sink, sink_inputs, FALSE); +                sink_inputs = NULL; +            }          } -        if (nd->source_profile) { -            u->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, nd->source_profile); +    if (nd->profile && nd->profile->input_mappings) +        PA_IDXSET_FOREACH(am, nd->profile->input_mappings, idx) { + +            if (!am->source) +                am->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, am); -            if (outputs) { -                if (u->source) -                    pa_source_move_all_finish(u->source, outputs, FALSE); -                else -                    pa_source_move_all_fail(outputs); +            if (source_outputs && am->source) { +                pa_source_move_all_finish(am->source, source_outputs, FALSE); +                source_outputs = NULL;              }          } -    } + +    if (sink_inputs) +        pa_sink_move_all_fail(sink_inputs); + +    if (source_outputs) +        pa_source_move_all_fail(source_outputs);      return 0;  }  static void init_profile(struct userdata *u) { +    uint32_t idx; +    pa_alsa_mapping *am;      struct profile_data *d;      pa_assert(u);      d = PA_CARD_PROFILE_DATA(u->card->active_profile); -    if (d->sink_profile) -        u->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, d->sink_profile); +    if (d->profile && d->profile->output_mappings) +        PA_IDXSET_FOREACH(am, d->profile->output_mappings, idx) +            am->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, am); -    if (d->source_profile) -        u->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, d->source_profile); +    if (d->profile && d->profile->input_mappings) +        PA_IDXSET_FOREACH(am, d->profile->input_mappings, idx) +            am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am);  }  static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) { @@ -280,12 +283,11 @@ int pa__init(pa_module *m) {      pa_modargs *ma;      int alsa_card_index;      struct userdata *u; -    char rname[32];      pa_reserve_wrapper *reserve = NULL;      const char *description; +    char *fn = NULL; -    pa_alsa_redirect_errors_inc(); -    snd_config_update_free_global(); +    pa_alsa_refcnt_inc();      pa_assert(m); @@ -294,30 +296,47 @@ int pa__init(pa_module *m) {          goto fail;      } -    m->userdata = u = pa_xnew(struct userdata, 1); +    m->userdata = u = pa_xnew0(struct userdata, 1);      u->core = m->core;      u->module = m;      u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID)); -    u->card = NULL; -    u->sink = NULL; -    u->source = NULL;      u->modargs = ma;      if ((alsa_card_index = snd_card_get_index(u->device_id)) < 0) { -        pa_log("Card '%s' doesn't exist: %s", u->device_id, snd_strerror(alsa_card_index)); +        pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(alsa_card_index));          goto fail;      } -    pa_snprintf(rname, sizeof(rname), "Audio%i", alsa_card_index); +    if (!pa_in_system_mode()) { +        char *rname; -    if (!pa_in_system_mode()) -        if (!(reserve = pa_reserve_wrapper_get(m->core, rname))) -            goto fail; +        if ((rname = pa_alsa_get_reserve_name(u->device_id))) { +            reserve = pa_reserve_wrapper_get(m->core, rname); +            pa_xfree(rname); + +            if (!reserve) +                goto fail; +        } +    } + +#ifdef HAVE_UDEV +    fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET"); +#endif + +    u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); +    pa_xfree(fn); + +    if (!u->profile_set) +        goto fail; + +    pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec);      pa_card_new_data_init(&data);      data.driver = __FILE__;      data.module = m; +      pa_alsa_init_proplist_card(m->core, data.proplist, alsa_card_index); +      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id);      pa_alsa_init_description(data.proplist);      set_card_name(&data, ma, u->device_id); @@ -326,11 +345,8 @@ int pa__init(pa_module *m) {          if ((description = pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION)))              pa_reserve_wrapper_set_application_device_name(reserve, description); -    u->profiles = data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); -    if (pa_alsa_probe_profiles(u->device_id, &m->core->default_sample_spec, enumerate_cb, u) < 0) { -        pa_card_new_data_done(&data); -        goto fail; -    } +    data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); +    add_profiles(u, data.profiles);      if (pa_hashmap_isempty(data.profiles)) {          pa_log("Failed to find a working profile."); @@ -340,6 +356,12 @@ int pa__init(pa_module *m) {      add_disabled_profile(data.profiles); +    if (pa_modargs_get_proplist(ma, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_card_new_data_done(&data); +        goto fail; +    } +      u->card = pa_card_new(m->core, &data);      pa_card_new_data_done(&data); @@ -351,7 +373,8 @@ int pa__init(pa_module *m) {      init_profile(u); -    pa_reserve_wrapper_unref(reserve); +    if (reserve) +        pa_reserve_wrapper_unref(reserve);      return 0; @@ -366,13 +389,22 @@ fail:  int pa__get_n_used(pa_module *m) {      struct userdata *u; +    int n = 0; +    uint32_t idx; +    pa_sink *sink; +    pa_source *source;      pa_assert(m);      pa_assert_se(u = m->userdata); +    pa_assert(u->card); + +    PA_IDXSET_FOREACH(sink, u->card->sinks, idx) +        n += pa_sink_linked_by(sink); -    return -        (u->sink ? pa_sink_linked_by(u->sink) : 0) + -        (u->source ? pa_source_linked_by(u->source) : 0); +    PA_IDXSET_FOREACH(source, u->card->sources, idx) +        n += pa_source_linked_by(source); + +    return n;  }  void pa__done(pa_module*m) { @@ -383,11 +415,19 @@ void pa__done(pa_module*m) {      if (!(u = m->userdata))          goto finish; -    if (u->sink) -        pa_alsa_sink_free(u->sink); +    if (u->card && u->card->sinks) { +        pa_sink *s; + +        while ((s = pa_idxset_steal_first(u->card->sinks, NULL))) +            pa_alsa_sink_free(s); +    } + +    if (u->card && u->card->sources) { +        pa_source *s; -    if (u->source) -        pa_alsa_source_free(u->source); +        while ((s = pa_idxset_steal_first(u->card->sources, NULL))) +            pa_alsa_source_free(s); +    }      if (u->card)          pa_card_free(u->card); @@ -395,10 +435,12 @@ void pa__done(pa_module*m) {      if (u->modargs)          pa_modargs_free(u->modargs); +    if (u->profile_set) +        pa_alsa_profile_set_free(u->profile_set); +      pa_xfree(u->device_id);      pa_xfree(u);  finish: -    snd_config_update_free_global(); -    pa_alsa_redirect_errors_dec(); +    pa_alsa_refcnt_dec();  } diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c index c728a446..3aa89b2a 100644 --- a/src/modules/alsa/module-alsa-sink.c +++ b/src/modules/alsa/module-alsa-sink.c @@ -40,6 +40,7 @@ PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "name=<name of the sink, to be prefixed> "          "sink_name=<name for the sink> " +        "sink_properities=<properties for the sink> "          "device=<ALSA device> "          "device_id=<ALSA card index> "          "format=<sample format> " @@ -52,11 +53,13 @@ PA_MODULE_USAGE(          "tsched=<enable system timer based scheduling mode?> "          "tsched_buffer_size=<buffer size when using timer based scheduling> "          "tsched_buffer_watermark=<lower fill watermark> " -        "ignore_dB=<ignore dB information from the device?>"); +        "ignore_dB=<ignore dB information from the device?> " +        "control=<name of mixer control>");  static const char* const valid_modargs[] = {      "name",      "sink_name", +    "sink_properties",      "device",      "device_id",      "format", @@ -70,6 +73,7 @@ static const char* const valid_modargs[] = {      "tsched_buffer_size",      "tsched_buffer_watermark",      "ignore_dB", +    "control",      NULL  }; @@ -78,8 +82,7 @@ int pa__init(pa_module*m) {      pa_assert(m); -    pa_alsa_redirect_errors_inc(); -    snd_config_update_free_global(); +    pa_alsa_refcnt_inc();      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {          pa_log("Failed to parse module arguments"); @@ -120,6 +123,5 @@ void pa__done(pa_module*m) {      if ((sink = m->userdata))          pa_alsa_sink_free(sink); -    snd_config_update_free_global(); -    pa_alsa_redirect_errors_dec(); +    pa_alsa_refcnt_dec();  } diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c index 6188019f..23da4185 100644 --- a/src/modules/alsa/module-alsa-source.c +++ b/src/modules/alsa/module-alsa-source.c @@ -37,6 +37,7 @@  #include <pulse/timeval.h>  #include <pulsecore/core-error.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core.h>  #include <pulsecore/module.h>  #include <pulsecore/memchunk.h> @@ -51,7 +52,6 @@  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h>  #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h>  #include "alsa-util.h"  #include "alsa-source.h" @@ -64,6 +64,7 @@ PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "name=<name for the source, to be prefixed> "          "source_name=<name for the source> " +        "source_properties=<properties for the source> "          "device=<ALSA device> "          "device_id=<ALSA card index> "          "format=<sample format> " @@ -76,11 +77,13 @@ PA_MODULE_USAGE(          "tsched=<enable system timer based scheduling mode?> "          "tsched_buffer_size=<buffer size when using timer based scheduling> "          "tsched_buffer_watermark=<upper fill watermark> " -        "ignore_dB=<ignore dB information from the device?>"); +        "ignore_dB=<ignore dB information from the device?> " +        "control=<name of mixer control>");  static const char* const valid_modargs[] = {      "name",      "source_name", +    "source_properties",      "device",      "device_id",      "format", @@ -94,6 +97,7 @@ static const char* const valid_modargs[] = {      "tsched_buffer_size",      "tsched_buffer_watermark",      "ignore_dB", +    "control",      NULL  }; @@ -102,8 +106,7 @@ int pa__init(pa_module*m) {      pa_assert(m); -    pa_alsa_redirect_errors_inc(); -    snd_config_update_free_global(); +    pa_alsa_refcnt_inc();      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {          pa_log("Failed to parse module arguments"); @@ -144,6 +147,5 @@ void pa__done(pa_module*m) {      if ((source = m->userdata))          pa_alsa_source_free(source); -    snd_config_update_free_global(); -    pa_alsa_redirect_errors_dec(); +    pa_alsa_refcnt_dec();  } diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index ccc8bee3..66e1c31e 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -24,32 +24,38 @@  #endif  #include <pulsecore/core-util.h> -#include <modules/dbus-util.h> +#include <pulsecore/shared.h> +#include <pulsecore/dbus-shared.h>  #include "bluetooth-util.h" -enum mode { -    MODE_FIND, -    MODE_GET, -    MODE_DISCOVER -}; -  struct pa_bluetooth_discovery { -    DBusConnection *connection; +    PA_REFCNT_DECLARE; + +    pa_core *core; +    pa_dbus_connection *connection;      PA_LLIST_HEAD(pa_dbus_pending, pending); +    pa_hashmap *devices; +    pa_hook hook; +}; -    enum mode mode; +static void get_properties_reply(DBusPendingCall *pending, void *userdata); +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func); -    /* If mode == MODE_FIND look for a specific device by its address. -       If mode == MODE_GET look for a specific device by its path. */ -    const char *looking_for; -    pa_bluetooth_device *found_device; +static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) { +    pa_assert(value); -    /* If looking_for is NULL we do long-time discovery */ -    pa_hashmap *devices; -    pa_bluetooth_device_callback_t callback; -    struct userdata *userdata; -}; +    if (pa_streq(value, "disconnected")) +        return PA_BT_AUDIO_STATE_DISCONNECTED; +    else if (pa_streq(value, "connecting")) +        return PA_BT_AUDIO_STATE_CONNECTING; +    else if (pa_streq(value, "connected")) +        return PA_BT_AUDIO_STATE_CONNECTED; +    else if (pa_streq(value, "playing")) +        return PA_BT_AUDIO_STATE_PLAYING; + +    return PA_BT_AUDIO_STATE_INVALID; +}  static pa_bluetooth_uuid *uuid_new(const char *uuid) {      pa_bluetooth_uuid *u; @@ -73,9 +79,9 @@ static pa_bluetooth_device* device_new(const char *path) {      d = pa_xnew(pa_bluetooth_device, 1); -    d->device_info_valid = d->audio_sink_info_valid = d->headset_info_valid = 0; +    d->dead = FALSE; -    d->data = NULL; +    d->device_info_valid = 0;      d->name = NULL;      d->path = pa_xstrdup(path); @@ -87,14 +93,14 @@ static pa_bluetooth_device* device_new(const char *path) {      d->class = -1;      d->trusted = -1; -    d->audio_sink_connected = -1; - -    d->headset_connected = -1; +    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;      return d;  } -void pa_bluetooth_device_free(pa_bluetooth_device *d) { +static void device_free(pa_bluetooth_device *d) {      pa_bluetooth_uuid *u;      pa_assert(d); @@ -111,24 +117,14 @@ void pa_bluetooth_device_free(pa_bluetooth_device *d) {      pa_xfree(d);  } -static pa_bool_t device_is_loaded(pa_bluetooth_device *d) { -    pa_assert(d); - -    /* FIXME: e83621724d7939b97b4f01f0d7e965d61ef8e55e, f1daa282f030e4e2381341e0f65faca47c4b891b is borked, probably needs to be reversed */ - -    return d->device_info_valid && (d->audio_sink_info_valid || d->headset_info_valid); -} -  static pa_bool_t device_is_audio(pa_bluetooth_device *d) {      pa_assert(d); -    pa_assert(d->device_info_valid); -    pa_assert(d->audio_sink_info_valid || d->headset_info_valid); - -    /* FIXME: e83621724d7939b97b4f01f0d7e965d61ef8e55e, f1daa282f030e4e2381341e0f65faca47c4b891b is borked, probably needs to be reversed */ - -    return d->device_info_valid > 0 && -        (d->audio_sink_info_valid > 0 || d->headset_info_valid > 0); +    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);  }  static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) { @@ -224,11 +220,25 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device                  while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {                      pa_bluetooth_uuid *node;                      const char *value; +                    DBusMessage *m;                      dbus_message_iter_get_basic(&ai, &value);                      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")); +                        send_and_add_to_pending(y, d, m, get_properties_reply); +                    } 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); +                    } +                      if (!dbus_message_iter_next(&ai))                          break;                  } @@ -241,12 +251,12 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device      return 0;  } -static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusMessageIter *i) { +static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {      const char *key;      DBusMessageIter variant_i;      pa_assert(u); -    pa_assert(connected); +    pa_assert(state);      pa_assert(i);      if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { @@ -268,19 +278,18 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusM      dbus_message_iter_recurse(i, &variant_i); -/*     pa_log_debug("Parsing property org.bluez.{AudioSink|Headset}.%s", key); */ +/*     pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */      switch (dbus_message_iter_get_arg_type(&variant_i)) { -        case DBUS_TYPE_BOOLEAN: { +        case DBUS_TYPE_STRING: { -            dbus_bool_t value; +            const char *value;              dbus_message_iter_get_basic(&variant_i, &value); -            if (pa_streq(key, "Connected")) -                *connected = !!value; - -/*             pa_log_debug("Value %s", pa_yes_no(value)); */ +            if (pa_streq(key, "State")) +                *state = pa_bt_audio_state_from_string(value); +/*             pa_log_debug("Value %s", value); */              break;          } @@ -289,21 +298,26 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusM      return 0;  } -static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t good) { +static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {      pa_assert(y);      pa_assert(d); -    if (y->mode != MODE_DISCOVER) +    if (!device_is_audio(d))          return; -    if (!device_is_loaded(d)) -        return; +    d->dead = dead; +    pa_hook_fire(&y->hook, d); +} -    if (!device_is_audio(d)) -        return; +static void remove_all_devices(pa_bluetooth_discovery *y) { +    pa_bluetooth_device *d; -    y->callback(y->userdata, d, good); +    pa_assert(y); +    while ((d = pa_hashmap_steal_first(y->devices))) { +        run_callback(y, d, TRUE); +        device_free(d); +    }  }  static void get_properties_reply(DBusPendingCall *pending, void *userdata) { @@ -328,10 +342,12 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {      if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))          d->device_info_valid = valid; -    else if (dbus_message_is_method_call(p->message, "org.bluez.Headset", "GetProperties")) -        d->headset_info_valid = valid; -    else if (dbus_message_is_method_call(p->message, "org.bluez.AudioSink", "GetProperties")) -        d->audio_sink_info_valid = valid; + +    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { +        pa_log_debug("Bluetooth daemon is apparently not available."); +        remove_all_devices(y); +        goto finish2; +    }      if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { @@ -363,12 +379,16 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {                  if (parse_device_property(y, d, &dict_i) < 0)                      goto finish; +            } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) { +                if (parse_audio_property(y, &d->audio_state, &dict_i) < 0) +                    goto finish; +              } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) { -                if (parse_audio_property(y, &d->headset_connected, &dict_i) < 0) +                if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)                      goto finish;              }  else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) { -                if (parse_audio_property(y, &d->audio_sink_connected, &dict_i) < 0) +                if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)                      goto finish;              }          } @@ -378,8 +398,9 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {      }  finish: -    run_callback(y, d, TRUE); +    run_callback(y, d, FALSE); +finish2:      dbus_message_unref(r);      PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); @@ -393,9 +414,9 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bl      pa_assert(y);      pa_assert(m); -    pa_assert_se(dbus_connection_send_with_reply(y->connection, m, &call, -1)); +    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1)); -    p = pa_dbus_pending_new(m, call, y, d); +    p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, d);      PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);      dbus_pending_call_set_notify(call, func, p, NULL); @@ -409,24 +430,18 @@ static void found_device(pa_bluetooth_discovery *y, const char* path) {      pa_assert(y);      pa_assert(path); +    if (pa_hashmap_get(y->devices, path)) +        return; +      d = device_new(path); -    if (y->mode == MODE_DISCOVER) { -        pa_assert(y->devices); -        pa_hashmap_put(y->devices, d->path, d); -    } else { -        pa_assert(!y->found_device); -        y->found_device = d; -    } +    pa_hashmap_put(y->devices, d->path, d);      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); -    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Headset", "GetProperties")); -    send_and_add_to_pending(y, d, m, get_properties_reply); - -    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.AudioSink", "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 */  }  static void list_devices_reply(DBusPendingCall *pending, void *userdata) { @@ -445,9 +460,15 @@ static void list_devices_reply(DBusPendingCall *pending, void *userdata) {      pa_assert_se(y = p->context_data);      pa_assert_se(r = dbus_pending_call_steal_reply(pending)); +    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { +        pa_log_debug("Bluetooth daemon is apparently not available."); +        remove_all_devices(y); +        goto finish; +    } +      if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {          pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r)); -        goto end; +        goto finish;      }      if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) { @@ -460,7 +481,7 @@ static void list_devices_reply(DBusPendingCall *pending, void *userdata) {              found_device(y, paths[i]);      } -end: +finish:      if (paths)          dbus_free_string_array (paths); @@ -470,57 +491,11 @@ end:      pa_dbus_pending_free(p);  } -static void find_device_reply(DBusPendingCall *pending, void *userdata) { -    DBusError e; -    DBusMessage *r; -    char *path = NULL; -    pa_dbus_pending *p; -    pa_bluetooth_discovery *y; - -    pa_assert(pending); - -    dbus_error_init(&e); - -    pa_assert_se(p = userdata); -    pa_assert_se(y = p->context_data); -    pa_assert_se(r = dbus_pending_call_steal_reply(pending)); - -    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { -        pa_log("Error from FindDevice reply: %s", dbus_message_get_error_name(r)); -        goto end; -    } - -    if (!dbus_message_get_args(r, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { -        pa_log("org.bluez.Adapter.FindDevice returned an error: '%s'\n", e.message); -        dbus_error_free(&e); -    } else -        found_device(y, path); - -end: -    dbus_message_unref(r); - -    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); -    pa_dbus_pending_free(p); -} -  static void found_adapter(pa_bluetooth_discovery *y, const char *path) {      DBusMessage *m; -    if (y->mode == MODE_FIND) { -        pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "FindDevice")); - -        pa_assert_se(dbus_message_append_args(m, -                                              DBUS_TYPE_STRING, &y->looking_for, -                                              DBUS_TYPE_INVALID)); - -        send_and_add_to_pending(y, NULL, m, find_device_reply); - -    } else { -        pa_assert(y->mode == MODE_DISCOVER); - -        pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices")); -        send_and_add_to_pending(y, NULL, m, list_devices_reply); -    } +    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices")); +    send_and_add_to_pending(y, NULL, m, list_devices_reply);  }  static void list_adapters_reply(DBusPendingCall *pending, void *userdata) { @@ -539,9 +514,15 @@ static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {      pa_assert_se(y = p->context_data);      pa_assert_se(r = dbus_pending_call_steal_reply(pending)); +    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { +        pa_log_debug("Bluetooth daemon is apparently not available."); +        remove_all_devices(y); +        goto finish; +    } +      if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {          pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r)); -        goto end; +        goto finish;      }      if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) { @@ -554,7 +535,7 @@ static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {              found_adapter(y, paths[i]);      } -end: +finish:      if (paths)          dbus_free_string_array (paths); @@ -600,11 +581,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us          pa_log_debug("Device %s removed", path);          if ((d = pa_hashmap_remove(y->devices, path))) { - -            pa_assert_se(y->mode == MODE_DISCOVER); -            run_callback(y, d, FALSE); - -            pa_bluetooth_device_free(d); +            run_callback(y, d, TRUE); +            device_free(d);          }          return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -635,7 +613,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us          found_adapter(y, path);          return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -    } else if (dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") || +    } 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.Device", "PropertyChanged")) { @@ -653,19 +632,46 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us                  if (parse_device_property(y, d, &arg_i) < 0)                      goto fail; +            } else if (dbus_message_has_interface(m, "org.bluez.Audio")) { +                if (parse_audio_property(y, &d->audio_state, &arg_i) < 0) +                    goto fail; +              } else if (dbus_message_has_interface(m, "org.bluez.Headset")) { -                if (parse_audio_property(y, &d->headset_connected, &arg_i) < 0) +                if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)                      goto fail; -		d->headset_info_valid = 1;              }  else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) { -                if (parse_audio_property(y, &d->audio_sink_connected, &arg_i) < 0) +                if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)                      goto fail; -		d->audio_sink_info_valid = 1;              } -            pa_assert_se(y->mode == MODE_DISCOVER); -            run_callback(y, d, TRUE); +            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; + +        if (!dbus_message_get_args(m, &err, +                                   DBUS_TYPE_STRING, &name, +                                   DBUS_TYPE_STRING, &old_owner, +                                   DBUS_TYPE_STRING, &new_owner, +                                   DBUS_TYPE_INVALID)) { +            pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); +            goto fail; +        } + +        if (pa_streq(name, "org.bluez")) { +            if (old_owner && *old_owner) { +                pa_log_debug("Bluetooth daemon disappeared."); +                remove_all_devices(y); +            } + +            if (new_owner && *new_owner) { +                pa_log_debug("Bluetooth daemon appeared."); +                list_adapters(y); +            }          }          return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -677,86 +683,87 @@ fail:      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;  } -pa_bluetooth_device* pa_bluetooth_find_device(DBusConnection *c, const char* address) { -    pa_bluetooth_discovery y; - -    memset(&y, 0, sizeof(y)); -    y.mode = MODE_FIND; -    y.looking_for = address; -    y.connection = c; -    PA_LLIST_HEAD_INIT(pa_dbus_pending, y.pending); +const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) { +    pa_bluetooth_device *d; +    void *state = NULL; -    list_adapters(&y); +    pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0); +    pa_assert(address); -    pa_dbus_sync_pending_list(&y.pending); -    pa_assert(!y.pending); +    if (!pa_hook_is_firing(&y->hook)) +        pa_bluetooth_discovery_sync(y); -    if (y.found_device) { -        pa_assert(device_is_loaded(y.found_device)); +    while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) +        if (pa_streq(d->address, address)) +            return d; -        if (!device_is_audio(y.found_device)) { -            pa_bluetooth_device_free(y.found_device); -            return NULL; -        } -    } - -    return y.found_device; +    return NULL;  } -pa_bluetooth_device* pa_bluetooth_get_device(DBusConnection *c, const char* path) { -    pa_bluetooth_discovery y; +const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) { +    pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0); +    pa_assert(path); + +    if (!pa_hook_is_firing(&y->hook)) +        pa_bluetooth_discovery_sync(y); -    memset(&y, 0, sizeof(y)); -    y.mode = MODE_GET; -    y.connection = c; -    PA_LLIST_HEAD_INIT(pa_dbus_pending, y.pending); +    return pa_hashmap_get(y->devices, path); +} -    found_device(&y, path); +static int setup_dbus(pa_bluetooth_discovery *y) { +    DBusError err; -    pa_dbus_sync_pending_list(&y.pending); -    pa_assert(!y.pending); +    dbus_error_init(&err); -    if (y.found_device) { -        pa_assert(device_is_loaded(y.found_device)); +    y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err); -        if (!device_is_audio(y.found_device)) { -            pa_bluetooth_device_free(y.found_device); -            return NULL; -        } +    if (dbus_error_is_set(&err) || !y->connection) { +        pa_log("Failed to get D-Bus connection: %s", err.message); +        dbus_error_free(&err); +        return -1;      } -    return y.found_device; +    return 0;  } -pa_bluetooth_discovery* pa_bluetooth_discovery_new(DBusConnection *c, pa_bluetooth_device_callback_t cb, struct userdata *u) { +pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {      DBusError err;      pa_bluetooth_discovery *y;      pa_assert(c); -    pa_assert(cb);      dbus_error_init(&err); +    if ((y = pa_shared_get(c, "bluetooth-discovery"))) +        return pa_bluetooth_discovery_ref(y); +      y = pa_xnew0(pa_bluetooth_discovery, 1); -    y->mode = MODE_DISCOVER; -    y->connection = c; -    y->callback = cb; -    y->userdata = u; +    PA_REFCNT_INIT(y); +    y->core = c;      y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);      PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending); +    pa_hook_init(&y->hook, y); +    pa_shared_set(c, "bluetooth-discovery", y); + +    if (setup_dbus(y) < 0) +        goto fail;      /* dynamic detection of bluetooth audio devices */ -    if (!dbus_connection_add_filter(c, filter_cb, y, NULL)) { +    if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {          pa_log_error("Failed to add filter function");          goto fail;      }      if (pa_dbus_add_matches( -                c, &err, +                pa_dbus_connection_get(y->connection), &err, +                "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",                  "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.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) {          pa_log("Failed to add D-Bus matches: %s", err.message); @@ -768,46 +775,77 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_new(DBusConnection *c, pa_bluetoo      return y;  fail: + +    if (y) +        pa_bluetooth_discovery_unref(y); +      dbus_error_free(&err); +      return NULL;  } -void pa_bluetooth_discovery_free(pa_bluetooth_discovery *y) { -    pa_bluetooth_device *d; +pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) { +    pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0); + +    PA_REFCNT_INC(y); + +    return y; +} +void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {      pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0); + +    if (PA_REFCNT_DEC(y) > 0) +        return;      pa_dbus_free_pending_list(&y->pending);      if (y->devices) { -        while ((d = pa_hashmap_steal_first(y->devices))) { -            run_callback(y, d, FALSE); -            pa_bluetooth_device_free(d); -        } - +        remove_all_devices(y);          pa_hashmap_free(y->devices, NULL, NULL);      }      if (y->connection) { -        pa_dbus_remove_matches(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.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.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); -        dbus_connection_remove_filter(y->connection, filter_cb, y); +        dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); + +        pa_dbus_connection_unref(y->connection);      } + +    pa_hook_done(&y->hook); + +    if (y->core) +        pa_shared_remove(y->core, "bluetooth-discovery"); + +    pa_xfree(y);  }  void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {      pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0);      pa_dbus_sync_pending_list(&y->pending);  } +pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) { +    pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0); + +    return &y->hook; +} +  const char*pa_bluetooth_get_form_factor(uint32_t class) {      unsigned i;      const char *r; @@ -867,3 +905,16 @@ char *pa_bluetooth_cleanup_name(const char *name) {      return t;  } + +pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) { +    pa_assert(uuid); + +    while (uuids) { +        if (strcasecmp(uuids->uuid, uuid) == 0) +            return TRUE; + +        uuids = uuids->next; +    } + +    return FALSE; +} diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h index 0364c972..265caf40 100644 --- a/src/modules/bluetooth/bluetooth-util.h +++ b/src/modules/bluetooth/bluetooth-util.h @@ -28,6 +28,20 @@  #include <pulsecore/macro.h>  #include <pulsecore/core-util.h> +/* UUID copied from bluez/audio/device.h */ +#define GENERIC_AUDIO_UUID      "00001203-0000-1000-8000-00805F9B34FB" + +#define HSP_HS_UUID             "00001108-0000-1000-8000-00805F9B34FB" +#define HSP_AG_UUID             "00001112-0000-1000-8000-00805F9B34FB" + +#define HFP_HS_UUID             "0000111E-0000-1000-8000-00805F9B34FB" +#define HFP_AG_UUID             "0000111F-0000-1000-8000-00805F9B34FB" + +#define ADVANCED_AUDIO_UUID     "0000110D-0000-1000-8000-00805F9B34FB" + +#define A2DP_SOURCE_UUID        "0000110A-0000-1000-8000-00805F9B34FB" +#define A2DP_SINK_UUID          "0000110B-0000-1000-8000-00805F9B34FB" +  typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;  typedef struct pa_bluetooth_device pa_bluetooth_device;  typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; @@ -39,12 +53,20 @@ 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 */ +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_t; +  struct pa_bluetooth_device { -    void *data; /* arbitrary information for the one owning the discovery object */ +    pa_bool_t dead;      int device_info_valid;      /* 0: no results yet; 1: good results; -1: bad results ... */ -    int audio_sink_info_valid;  /* ... same here ... */ -    int headset_info_valid;     /* ... and here */      /* Device information */      char *name; @@ -57,25 +79,31 @@ struct pa_bluetooth_device {      int class;      int trusted; -    /* AudioSink information */ -    int audio_sink_connected; +    /* Audio state */ +    pa_bt_audio_state_t audio_state; -    /* Headset information */ -    int headset_connected; -}; +    /* AudioSink state */ +    pa_bt_audio_state_t audio_sink_state; -void pa_bluetooth_device_free(pa_bluetooth_device *d); +    /* Headset state */ +    pa_bt_audio_state_t headset_state; +}; -pa_bluetooth_device* pa_bluetooth_get_device(DBusConnection *c, const char* path); -pa_bluetooth_device* pa_bluetooth_find_device(DBusConnection *c, const char* address); +pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core); +pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y); +void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *d); -typedef void (*pa_bluetooth_device_callback_t)(struct userdata *u, pa_bluetooth_device *d, pa_bool_t good); -pa_bluetooth_discovery* pa_bluetooth_discovery_new(DBusConnection *c, pa_bluetooth_device_callback_t cb, struct userdata *u); -void pa_bluetooth_discovery_free(pa_bluetooth_discovery *d);  void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *d); -const char*pa_bluetooth_get_form_factor(uint32_t class); +const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path); +const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address); + +pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *d); + +const char* pa_bluetooth_get_form_factor(uint32_t class);  char *pa_bluetooth_cleanup_name(const char *name); +pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid); +  #endif diff --git a/src/modules/bluetooth/ipc.c b/src/modules/bluetooth/ipc.c index f14c92c4..dcecad8a 100644 --- a/src/modules/bluetooth/ipc.c +++ b/src/modules/bluetooth/ipc.c @@ -35,6 +35,7 @@ static const char *strtypes[] = {  /* This table contains the string representation for messages names */  static const char *strnames[] = {  	"BT_GET_CAPABILITIES", +	"BT_OPEN",  	"BT_SET_CONFIGURATION",  	"BT_NEW_STREAM",  	"BT_START_STREAM", diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h index f030acfa..2e170f50 100644 --- a/src/modules/bluetooth/ipc.h +++ b/src/modules/bluetooth/ipc.h @@ -71,7 +71,7 @@ extern "C" {  #include <sys/un.h>  #include <errno.h> -#define BT_SUGGESTED_BUFFER_SIZE   128 +#define BT_SUGGESTED_BUFFER_SIZE   512  #define BT_IPC_SOCKET_NAME "\0/org/bluez/audio"  /* Generic message header definition, except for RESPONSE messages */ @@ -94,10 +94,12 @@ typedef struct {  /* Messages names */  #define BT_GET_CAPABILITIES		0 -#define BT_SET_CONFIGURATION		1 -#define BT_NEW_STREAM			2 -#define BT_START_STREAM			3 -#define BT_STOP_STREAM			4 +#define BT_OPEN				1 +#define BT_SET_CONFIGURATION		2 +#define BT_NEW_STREAM			3 +#define BT_START_STREAM			4 +#define BT_STOP_STREAM			5 +#define BT_CLOSE			6  #define BT_CONTROL			7  #define BT_CAPABILITIES_TRANSPORT_A2DP	0 @@ -112,19 +114,31 @@ typedef struct {  struct bt_get_capabilities_req {  	bt_audio_msg_header_t	h; -	char			device[18];	/* Address of the remote Device */ +	char			source[18];	/* Address of the local Device */ +	char			destination[18];/* Address of the remote Device */ +	char			object[128];	/* DBus object path */  	uint8_t			transport;	/* Requested transport */  	uint8_t			flags;		/* Requested flags */ +	uint8_t			seid;		/* Requested capability configuration */  } __attribute__ ((packed));  /** - * SBC Codec parameters as per A2DP profile 1.0 § 4.3 + * SBC Codec parameters as per A2DP profile 1.0 § 4.3   */ -#define BT_A2DP_CODEC_SBC			0x00 -#define BT_A2DP_CODEC_MPEG12			0x01 -#define BT_A2DP_CODEC_MPEG24			0x02 -#define BT_A2DP_CODEC_ATRAC			0x03 +/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */ +#define BT_A2DP_SEID_RANGE			(1 << 6) - 1 + +#define BT_A2DP_SBC_SOURCE			0x00 +#define BT_A2DP_SBC_SINK			0x01 +#define BT_A2DP_MPEG12_SOURCE			0x02 +#define BT_A2DP_MPEG12_SINK			0x03 +#define BT_A2DP_MPEG24_SOURCE			0x04 +#define BT_A2DP_MPEG24_SINK			0x05 +#define BT_A2DP_ATRAC_SOURCE			0x06 +#define BT_A2DP_ATRAC_SINK			0x07 +#define BT_A2DP_UNKNOWN_SOURCE			0x08 +#define BT_A2DP_UNKNOWN_SINK			0x09  #define BT_SBC_SAMPLING_FREQ_16000		(1 << 3)  #define BT_SBC_SAMPLING_FREQ_32000		(1 << 2) @@ -163,10 +177,16 @@ struct bt_get_capabilities_req {  #define BT_PCM_FLAG_NREC			0x01  #define BT_PCM_FLAG_PCM_ROUTING			0x02 +#define BT_WRITE_LOCK				(1 << 1) +#define BT_READ_LOCK				1 +  typedef struct { +	uint8_t seid;  	uint8_t transport;  	uint8_t type;  	uint8_t length; +	uint8_t configured; +	uint8_t lock;  	uint8_t data[0];  } __attribute__ ((packed)) codec_capabilities_t; @@ -199,20 +219,35 @@ typedef struct {  struct bt_get_capabilities_rsp {  	bt_audio_msg_header_t	h; +	char			source[18];	/* Address of the local Device */ +	char			destination[18];/* Address of the remote Device */ +	char			object[128];	/* DBus object path */  	uint8_t			data[0];	/* First codec_capabilities_t */  } __attribute__ ((packed)); +struct bt_open_req { +	bt_audio_msg_header_t	h; +	char			source[18];	/* Address of the local Device */ +	char			destination[18];/* Address of the remote Device */ +	char			object[128];	/* DBus object path */ +	uint8_t			seid;		/* Requested capability configuration to lock */ +	uint8_t			lock;		/* Requested lock */ +} __attribute__ ((packed)); + +struct bt_open_rsp { +	bt_audio_msg_header_t	h; +	char			source[18];	/* Address of the local Device */ +	char			destination[18];/* Address of the remote Device */ +	char			object[128];	/* DBus object path */ +} __attribute__ ((packed)); +  struct bt_set_configuration_req {  	bt_audio_msg_header_t	h; -	char			device[18];	/* Address of the remote Device */ -	uint8_t			access_mode;	/* Requested access mode */  	codec_capabilities_t	codec;		/* Requested codec */  } __attribute__ ((packed));  struct bt_set_configuration_rsp {  	bt_audio_msg_header_t	h; -	uint8_t			transport;	/* Granted transport */ -	uint8_t			access_mode;	/* Granted access mode */  	uint16_t		link_mtu;	/* Max length that transport supports */  } __attribute__ ((packed)); @@ -241,6 +276,14 @@ struct bt_stop_stream_rsp {  	bt_audio_msg_header_t	h;  } __attribute__ ((packed)); +struct bt_close_req { +	bt_audio_msg_header_t	h; +} __attribute__ ((packed)); + +struct bt_close_rsp { +	bt_audio_msg_header_t	h; +} __attribute__ ((packed)); +  struct bt_suspend_stream_ind {  	bt_audio_msg_header_t	h;  } __attribute__ ((packed)); diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 93673cb9..609efd17 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -30,13 +30,15 @@  #include <linux/sockios.h>  #include <arpa/inet.h> -#include <pulse/xmalloc.h> -#include <pulse/timeval.h> -#include <pulse/sample.h>  #include <pulse/i18n.h> +#include <pulse/rtclock.h> +#include <pulse/sample.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h>  #include <pulsecore/module.h>  #include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/core-error.h>  #include <pulsecore/socket-util.h> @@ -44,10 +46,8 @@  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h>  #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h>  #include <pulsecore/namereg.h> - -#include <modules/dbus-util.h> +#include <pulsecore/dbus-shared.h>  #include "module-bluetooth-device-symdef.h"  #include "ipc.h" @@ -65,32 +65,43 @@ PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "name=<name for the card/sink/source, to be prefixed> "          "card_name=<name for the card> " +        "card_properties=<properties for the card> "          "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> "          "source_name=<name for the source> " +        "source_properties=<properties for the source> "          "address=<address of the device> "          "profile=<a2dp|hsp> "          "rate=<sample rate> "          "channels=<number of channels> " -        "path=<device object path> " +        "path=<device object path>"); + +/* +#ifdef NOKIA          "sco_sink=<SCO over PCM sink name> " -        "sco_source=<SCO over PCM source name>"); +        "sco_source=<SCO over PCM source name>" +#endif +*/  /* TODO: not close fd when entering suspend mode in a2dp */ -/* TODO: BT_PCM_FLAG_NREC */ -  static const char* const valid_modargs[] = {      "name",      "card_name", +    "card_properties",      "sink_name", +    "sink_properties",      "source_name", +    "source_properties",      "address",      "profile",      "rate",      "channels",      "path", +#ifdef NOKIA      "sco_sink",      "sco_source", +#endif      NULL  }; @@ -98,7 +109,7 @@ struct a2dp_info {      sbc_capabilities_t sbc_capabilities;      sbc_t sbc;                           /* Codec data */      pa_bool_t sbc_initialized;           /* Keep track if the encoder is initialized */ -    size_t codesize;                     /* SBC codesize */ +    size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */      void* buffer;                        /* Codec transfer buffer */      size_t buffer_size;                  /* Size of the buffer */ @@ -108,8 +119,10 @@ struct a2dp_info {  struct hsp_info {      pcm_capabilities_t pcm_capabilities; +#ifdef NOKIA      pa_sink *sco_sink;      pa_source *sco_source; +#endif      pa_hook_slot *sink_state_changed_slot;      pa_hook_slot *source_state_changed_slot;  }; @@ -124,6 +137,12 @@ struct userdata {      pa_core *core;      pa_module *module; +    char *address; +    char *path; +    pa_bluetooth_discovery *discovery; + +    pa_dbus_connection *connection; +      pa_card *card;      pa_sink *sink;      pa_source *source; @@ -149,19 +168,24 @@ struct userdata {      struct a2dp_info a2dp;      struct hsp_info hsp; -    pa_dbus_connection *connection;      enum profile profile;      pa_modargs *modargs; -    pa_bluetooth_device *device; - -    int stream_write_type, stream_read_type; +    int stream_write_type;      int service_write_type, service_read_type;  }; +#define FIXED_LATENCY_PLAYBACK_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) + +#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC) + +#ifdef NOKIA  #define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) +#endif  static int init_bt(struct userdata *u);  static int init_profile(struct userdata *u); @@ -264,7 +288,8 @@ static ssize_t service_expect(struct userdata*u, bt_audio_msg_header_t *rsp, siz      return 0;  } -static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *rsp) { +/* Run from main thread */ +static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capabilities_rsp *rsp) {      uint16_t bytes_left;      const codec_capabilities_t *codec; @@ -295,12 +320,15 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *          pa_assert(codec->type == BT_HFP_CODEC_PCM); +        if (codec->configured && seid == 0) +            return codec->seid; +          memcpy(&u->hsp.pcm_capabilities, codec, sizeof(u->hsp.pcm_capabilities));      } else if (u->profile == PROFILE_A2DP) {          while (bytes_left > 0) { -            if (codec->type == BT_A2DP_CODEC_SBC) +            if ((codec->type == BT_A2DP_SBC_SINK) && !codec->lock)                  break;              bytes_left -= codec->length; @@ -310,7 +338,10 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *          if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities))              return -1; -        pa_assert(codec->type == BT_A2DP_CODEC_SBC); +        pa_assert(codec->type == BT_A2DP_SBC_SINK); + +        if (codec->configured && seid == 0) +            return codec->seid;          memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));      } @@ -318,13 +349,15 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *      return 0;  } -static int get_caps(struct userdata *u) { +/* Run from main thread */ +static int get_caps(struct userdata *u, uint8_t seid) {      union {          struct bt_get_capabilities_req getcaps_req;          struct bt_get_capabilities_rsp getcaps_rsp;          bt_audio_error_t error;          uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];      } msg; +    int ret;      pa_assert(u); @@ -332,8 +365,9 @@ static int get_caps(struct userdata *u) {      msg.getcaps_req.h.type = BT_REQUEST;      msg.getcaps_req.h.name = BT_GET_CAPABILITIES;      msg.getcaps_req.h.length = sizeof(msg.getcaps_req); +    msg.getcaps_req.seid = seid; -    pa_strlcpy(msg.getcaps_req.device, u->device->address, sizeof(msg.getcaps_req.device)); +    pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object));      if (u->profile == PROFILE_A2DP)          msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;      else { @@ -348,9 +382,14 @@ static int get_caps(struct userdata *u) {      if (service_expect(u, &msg.getcaps_rsp.h, sizeof(msg), BT_GET_CAPABILITIES, 0) < 0)          return -1; -    return parse_caps(u, &msg.getcaps_rsp); +    ret = parse_caps(u, seid, &msg.getcaps_rsp); +    if (ret <= 0) +        return ret; + +    return get_caps(u, ret);  } +/* Run from main thread */  static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {      switch (freq) { @@ -396,6 +435,7 @@ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {      }  } +/* Run from main thread */  static int setup_a2dp(struct userdata *u) {      sbc_capabilities_t *cap;      int i; @@ -424,8 +464,8 @@ static int setup_a2dp(struct userdata *u) {              break;          } -    if ((unsigned) i >= PA_ELEMENTSOF(freq_table)) { -        for (; i >= 0; i--) { +    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { +        for (--i; i >= 0; i--) {              if (cap->frequency & freq_table[i].cap) {                  u->sample_spec.rate = freq_table[i].rate;                  cap->frequency = freq_table[i].cap; @@ -439,6 +479,11 @@ static int setup_a2dp(struct userdata *u) {          }      } +    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); + +    if (cap->capability.configured) +        return 0; +      if (u->sample_spec.channels <= 1) {          if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {              cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; @@ -498,6 +543,7 @@ static int setup_a2dp(struct userdata *u) {      return 0;  } +/* Run from main thread */  static void setup_sbc(struct a2dp_info *a2dp) {      sbc_capabilities_t *active_capabilities; @@ -585,17 +631,36 @@ static void setup_sbc(struct a2dp_info *a2dp) {      }      a2dp->sbc.bitpool = active_capabilities->max_bitpool; -    a2dp->codesize = (uint16_t) sbc_get_codesize(&a2dp->sbc); +    a2dp->codesize = sbc_get_codesize(&a2dp->sbc); +    a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);  } +/* Run from main thread */  static int set_conf(struct userdata *u) {      union { +        struct bt_open_req open_req; +        struct bt_open_rsp open_rsp;          struct bt_set_configuration_req setconf_req;          struct bt_set_configuration_rsp setconf_rsp;          bt_audio_error_t error;          uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];      } msg; +    memset(&msg, 0, sizeof(msg)); +    msg.open_req.h.type = BT_REQUEST; +    msg.open_req.h.name = BT_OPEN; +    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; + +    if (service_send(u, &msg.open_req.h) < 0) +        return -1; + +    if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0) +        return -1; +      if (u->profile == PROFILE_A2DP ) {          u->sample_spec.format = PA_SAMPLE_S16LE; @@ -614,15 +679,14 @@ static int set_conf(struct userdata *u) {      msg.setconf_req.h.name = BT_SET_CONFIGURATION;      msg.setconf_req.h.length = sizeof(msg.setconf_req); -    pa_strlcpy(msg.setconf_req.device, u->device->address, sizeof(msg.setconf_req.device)); -    msg.setconf_req.access_mode = u->profile == PROFILE_A2DP ? BT_CAPABILITIES_ACCESS_MODE_WRITE : BT_CAPABILITIES_ACCESS_MODE_READWRITE; - -    msg.setconf_req.codec.transport = u->profile == PROFILE_A2DP ? BT_CAPABILITIES_TRANSPORT_A2DP : BT_CAPABILITIES_TRANSPORT_SCO; -      if (u->profile == PROFILE_A2DP) {          memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities)); -        msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec); +    } else { +        msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; +        msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1; +        msg.setconf_req.codec.length = sizeof(pcm_capabilities_t);      } +    msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec);      if (service_send(u, &msg.setconf_req.h) < 0)          return -1; @@ -630,24 +694,17 @@ static int set_conf(struct userdata *u) {      if (service_expect(u, &msg.setconf_rsp.h, sizeof(msg), BT_SET_CONFIGURATION, sizeof(msg.setconf_rsp)) < 0)          return -1; -    if ((u->profile == PROFILE_A2DP && msg.setconf_rsp.transport != BT_CAPABILITIES_TRANSPORT_A2DP) || -        (u->profile == PROFILE_HSP && msg.setconf_rsp.transport != BT_CAPABILITIES_TRANSPORT_SCO)) { -        pa_log("Transport doesn't match what we requested."); -        return -1; -    } - -    if ((u->profile == PROFILE_A2DP && msg.setconf_rsp.access_mode != BT_CAPABILITIES_ACCESS_MODE_WRITE) || -        (u->profile == PROFILE_HSP && msg.setconf_rsp.access_mode != BT_CAPABILITIES_ACCESS_MODE_READWRITE)) { -        pa_log("Access mode doesn't match what we requested."); -        return -1; -    } -      u->link_mtu = msg.setconf_rsp.link_mtu;      /* setup SBC encoder now we agree on parameters */      if (u->profile == PROFILE_A2DP) {          setup_sbc(&u->a2dp); -        u->block_size = u->a2dp.codesize; + +        u->block_size = +            ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) +            / u->a2dp.frame_length +            * u->a2dp.codesize); +          pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",                      u->a2dp.sbc.allocation, u->a2dp.sbc.subbands, u->a2dp.sbc.blocks, u->a2dp.sbc.bitpool);      } else @@ -667,6 +724,7 @@ static int start_stream_fd(struct userdata *u) {          uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];      } msg;      struct pollfd *pollfd; +    int one;      pa_assert(u);      pa_assert(u->rtpoll); @@ -695,13 +753,29 @@ static int start_stream_fd(struct userdata *u) {      pa_make_fd_nonblock(u->stream_fd);      pa_make_socket_low_delay(u->stream_fd); +    one = 1; +    if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) +        pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno)); + +    pa_log_debug("Stream properly set up, we're ready to roll!"); +      u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);      pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);      pollfd->fd = u->stream_fd;      pollfd->events = pollfd->revents = 0; -    u->read_index = 0; -    u->write_index = 0; +    u->read_index = u->write_index = 0; +    u->started_at = 0; + +    if (u->source) +        u->read_smoother = pa_smoother_new( +                PA_USEC_PER_SEC, +                PA_USEC_PER_SEC*2, +                TRUE, +                TRUE, +                10, +                pa_rtclock_now(), +                TRUE);      return 0;  } @@ -737,9 +811,15 @@ static int stop_stream_fd(struct userdata *u) {      pa_close(u->stream_fd);      u->stream_fd = -1; +    if (u->read_smoother) { +        pa_smoother_free(u->read_smoother); +        u->read_smoother = NULL; +    } +      return r;  } +/* Run from 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;      pa_bool_t failed = FALSE; @@ -747,7 +827,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse      pa_assert(u->sink == PA_SINK(o)); -    pa_log_debug("got message: %d", code);      switch (code) {          case PA_SINK_MESSAGE_SET_STATE: @@ -775,8 +854,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse                      if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)                          if (start_stream_fd(u) < 0)                              failed = TRUE; - -                    u->started_at = pa_rtclock_usec();                      break;                  case PA_SINK_UNLINKED: @@ -787,7 +864,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse              break;          case PA_SINK_MESSAGE_GET_LATENCY: { -            *((pa_usec_t*) data) = 0; + +            if (u->read_smoother) { +                pa_usec_t wi, ri; + +                ri = pa_smoother_get(u->read_smoother, pa_rtclock_now()); +                wi = pa_bytes_to_usec(u->write_index + u->block_size, &u->sample_spec); + +                *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; +            } else { +                pa_usec_t ri, wi; + +                ri = pa_rtclock_now() - u->started_at; +                wi = pa_bytes_to_usec(u->write_index, &u->sample_spec); + +                *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; +            } + +            *((pa_usec_t*) data) += u->sink->fixed_latency;              return 0;          }      } @@ -797,6 +891,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse      return (r < 0 || !failed) ? r : -1;  } +/* Run from IO thread */  static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {      struct userdata *u = PA_SOURCE(o)->userdata;      pa_bool_t failed = FALSE; @@ -804,7 +899,6 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off      pa_assert(u->source == PA_SOURCE(o)); -    pa_log_debug("got message: %d", code);      switch (code) {          case PA_SOURCE_MESSAGE_SET_STATE: @@ -818,7 +912,8 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off                      if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)                          stop_stream_fd(u); -                    pa_smoother_pause(u->read_smoother, pa_rtclock_usec()); +                    if (u->read_smoother) +                        pa_smoother_pause(u->read_smoother, pa_rtclock_now());                      break;                  case PA_SOURCE_IDLE: @@ -831,7 +926,8 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off                          if (start_stream_fd(u) < 0)                              failed = TRUE; -                    pa_smoother_resume(u->read_smoother, pa_rtclock_usec()); +                    /* We don't resume the smoother here. Instead we +                     * wait until the first packet arrives */                      break;                  case PA_SOURCE_UNLINKED: @@ -842,7 +938,12 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off              break;          case PA_SOURCE_MESSAGE_GET_LATENCY: { -            *((pa_usec_t*) data) = 0; +            pa_usec_t wi, ri; + +            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;              return 0;          } @@ -853,54 +954,71 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off      return (r < 0 || !failed) ? r : -1;  } +/* Run from IO thread */  static int hsp_process_render(struct userdata *u) {      int ret = 0; -    pa_memchunk memchunk;      pa_assert(u);      pa_assert(u->profile == PROFILE_HSP);      pa_assert(u->sink); -    pa_sink_render_full(u->sink, u->block_size, &memchunk); +    /* First, render some data */ +    if (!u->write_memchunk.memblock) +        pa_sink_render_full(u->sink, u->block_size, &u->write_memchunk); + +    pa_assert(u->write_memchunk.length == u->block_size);      for (;;) {          ssize_t l;          const void *p; -        p = (const uint8_t*) pa_memblock_acquire(memchunk.memblock) + memchunk.index; -        l = pa_write(u->stream_fd, p, memchunk.length, &u->stream_write_type); -        pa_memblock_release(memchunk.memblock); +        /* Now write that data to the socket. The socket is of type +         * SEQPACKET, and we generated the data of the MTU size, so this +         * should just work. */ -        pa_log_debug("Memblock written to socket: %lli bytes", (long long) l); +        p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index; +        l = pa_write(u->stream_fd, p, u->write_memchunk.length, &u->stream_write_type); +        pa_memblock_release(u->write_memchunk.memblock);          pa_assert(l != 0);          if (l < 0) { -            if (errno == EINTR || errno == EAGAIN) /*** FIXME: EAGAIN handling borked ***/ + +            if (errno == EINTR) +                /* Retry right away if we got interrupted */                  continue; -            else { -                pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno)); -                ret = -1; + +            else if (errno == EAGAIN) +                /* Hmm, apparently the socket was not writable, give up for now */                  break; -            } -        } else { -            pa_assert((size_t) l <= memchunk.length); -            memchunk.index += (size_t) l; -            memchunk.length -= (size_t) l; +            pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno)); +            ret = -1; +            break; +        } -            u->write_index += (uint64_t) l; +        pa_assert((size_t) l <= u->write_memchunk.length); -            if (memchunk.length <= 0) -                break; +        if ((size_t) l != u->write_memchunk.length) { +            pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.", +                        (unsigned long long) l, +                        (unsigned long long) u->write_memchunk.length); +            ret = -1; +            break;          } -    } -    pa_memblock_unref(memchunk.memblock); +        u->write_index += (uint64_t) u->write_memchunk.length; +        pa_memblock_unref(u->write_memchunk.memblock); +        pa_memchunk_reset(&u->write_memchunk); + +        ret = 1; +        break; +    }      return ret;  } +/* Run from IO thread */  static int hsp_process_push(struct userdata *u) {      int ret = 0;      pa_memchunk memchunk; @@ -908,6 +1026,7 @@ static int hsp_process_push(struct userdata *u) {      pa_assert(u);      pa_assert(u->profile == PROFILE_HSP);      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; @@ -915,26 +1034,69 @@ static int hsp_process_push(struct userdata *u) {      for (;;) {          ssize_t l;          void *p; +        struct msghdr m; +        struct cmsghdr *cm; +        uint8_t aux[1024]; +        struct iovec iov; +        pa_bool_t found_tstamp = FALSE; +        pa_usec_t tstamp; + +        memset(&m, 0, sizeof(m)); +        memset(&aux, 0, sizeof(aux)); +        memset(&iov, 0, sizeof(iov)); + +        m.msg_iov = &iov; +        m.msg_iovlen = 1; +        m.msg_control = aux; +        m.msg_controllen = sizeof(aux);          p = pa_memblock_acquire(memchunk.memblock); -        l = pa_read(u->stream_fd, p, pa_memblock_get_length(memchunk.memblock), &u->stream_read_type); +        iov.iov_base = p; +        iov.iov_len = pa_memblock_get_length(memchunk.memblock); +        l = recvmsg(u->stream_fd, &m, 0);          pa_memblock_release(memchunk.memblock);          if (l <= 0) { -            if (l < 0 && (errno == EINTR || errno == EAGAIN)) /*** FIXME: EAGAIN handling borked ***/ + +            if (l < 0 && errno == EINTR) +                /* Retry right away if we got interrupted */                  continue; -            else { -                pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF"); -                ret = -1; + +            else if (l < 0 && errno == EAGAIN) +                /* Hmm, apparently the socket was not readable, give up for now. */                  break; -            } -        } else { -            memchunk.length = (size_t) l; -            u->read_index += (uint64_t) l; -            pa_source_post(u->source, &memchunk); +            pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF"); +            ret = -1;              break;          } + +        pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock)); + +        memchunk.length = (size_t) l; +        u->read_index += (uint64_t) l; + +        for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) +            if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) { +                struct timeval *tv = (struct timeval*) CMSG_DATA(cm); +                pa_rtclock_from_wallclock(tv); +                tstamp = pa_timeval_load(tv); +                found_tstamp = TRUE; +                break; +            } + +        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); + +        pa_source_post(u->source, &memchunk); + +        ret = 1; +        break;      }      pa_memblock_unref(memchunk.memblock); @@ -942,132 +1104,156 @@ static int hsp_process_push(struct userdata *u) {      return ret;  } +/* Run from IO thread */ +static void a2dp_prepare_buffer(struct userdata *u) { +    pa_assert(u); + +    if (u->a2dp.buffer_size >= u->link_mtu) +        return; + +    u->a2dp.buffer_size = 2 * u->link_mtu; +    pa_xfree(u->a2dp.buffer); +    u->a2dp.buffer = pa_xmalloc(u->a2dp.buffer_size); +} + +/* Run from IO thread */  static int a2dp_process_render(struct userdata *u) { -    size_t frame_size;      struct a2dp_info *a2dp;      struct rtp_header *header;      struct rtp_payload *payload; -    size_t left; +    size_t nbytes;      void *d;      const void *p; +    size_t to_write, to_encode;      unsigned frame_count; -    size_t written; -    uint64_t writing_at; +    int ret = 0;      pa_assert(u);      pa_assert(u->profile == PROFILE_A2DP);      pa_assert(u->sink); -    a2dp = &u->a2dp; +    /* First, render some data */ +    if (!u->write_memchunk.memblock) +        pa_sink_render_full(u->sink, u->block_size, &u->write_memchunk); -    if (a2dp->buffer_size < u->link_mtu) { -        a2dp->buffer_size = 2*u->link_mtu; -        pa_xfree(a2dp->buffer); -        a2dp->buffer = pa_xmalloc(a2dp->buffer_size); -    } +    pa_assert(u->write_memchunk.length == u->block_size); -    header = (struct rtp_header*) a2dp->buffer; +    a2dp_prepare_buffer(u); + +    a2dp = &u->a2dp; +    header = a2dp->buffer;      payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); -    d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload); -    left = a2dp->buffer_size - sizeof(*header) - sizeof(*payload); -    frame_size = sbc_get_frame_length(&a2dp->sbc);      frame_count = 0; -    writing_at = u->write_index; +    /* Try to create a packet of the full MTU */ -    do { -        ssize_t encoded; +    p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index; +    to_encode = u->write_memchunk.length; -        if (!u->write_memchunk.memblock) -            pa_sink_render_full(u->sink, u->block_size, &u->write_memchunk); +    d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload); +    to_write = a2dp->buffer_size - sizeof(*header) - sizeof(*payload); + +    while (PA_LIKELY(to_encode > 0 && to_write > 0)) { +        size_t written; +        ssize_t encoded; -        p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index;          encoded = sbc_encode(&a2dp->sbc, -                             p, u->write_memchunk.length, -                             d, left, +                             p, to_encode, +                             d, to_write,                               &written); -        PA_ONCE_BEGIN { -            pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&a2dp->sbc))); -        } PA_ONCE_END; - -        pa_memblock_release(u->write_memchunk.memblock); - -        if (encoded <= 0) { -            pa_log_error("SBC encoding error (%d)", encoded); +        if (PA_UNLIKELY(encoded <= 0)) { +            pa_log_error("SBC encoding error (%li)", (long) encoded); +            pa_memblock_release(u->write_memchunk.memblock);              return -1;          } -        pa_assert((size_t) encoded <= u->write_memchunk.length); -        pa_assert((size_t) encoded == sbc_get_codesize(&a2dp->sbc)); +/*         pa_log_debug("SBC: encoded: %lu; written: %lu", (unsigned long) encoded, (unsigned long) written); */ +/*         pa_log_debug("SBC: codesize: %lu; frame_length: %lu", (unsigned long) a2dp->codesize, (unsigned long) a2dp->frame_length); */ -        pa_assert((size_t) written <= left); -        pa_assert((size_t) written == sbc_get_frame_length(&a2dp->sbc)); +        pa_assert_fp((size_t) encoded <= to_encode); +        pa_assert_fp((size_t) encoded == a2dp->codesize); -/*         pa_log_debug("SBC: encoded: %d; written: %d", encoded, written); */ +        pa_assert_fp((size_t) written <= to_write); +        pa_assert_fp((size_t) written == a2dp->frame_length); -        u->write_memchunk.index += encoded; -        u->write_memchunk.length -= encoded; - -        if (u->write_memchunk.length <= 0) { -            pa_memblock_unref(u->write_memchunk.memblock); -            pa_memchunk_reset(&u->write_memchunk); -        } - -        u->write_index += encoded; +        p = (const uint8_t*) p + encoded; +        to_encode -= encoded;          d = (uint8_t*) d + written; -        left -= written; +        to_write -= written;          frame_count++; +    } -    } while (((uint8_t*) d - ((uint8_t*) a2dp->buffer + sbc_get_frame_length(&a2dp->sbc))) < (ptrdiff_t) u->link_mtu); +    pa_memblock_release(u->write_memchunk.memblock); + +    pa_assert(to_encode == 0); + +    PA_ONCE_BEGIN { +        pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&a2dp->sbc))); +    } PA_ONCE_END;      /* write it to the fifo */      memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); -    payload->frame_count = frame_count;      header->v = 2;      header->pt = 1;      header->sequence_number = htons(a2dp->seq_num++); -    header->timestamp = htonl(writing_at / frame_size); +    header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));      header->ssrc = htonl(1); +    payload->frame_count = frame_count; -    p = a2dp->buffer; -    left = (uint8_t*) d - (uint8_t*) a2dp->buffer; +    nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer;      for (;;) {          ssize_t l; -        l = pa_write(u->stream_fd, p, left, &u->stream_write_type); -/*         pa_log_debug("write: requested %lu bytes; written %li bytes; mtu=%li", (unsigned long) left, (long) l, (unsigned long) u->link_mtu); */ +        l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type);          pa_assert(l != 0);          if (l < 0) { -            if (errno == EINTR || errno == EAGAIN) /*** FIXME: EAGAIN handling borked ***/ -                continue; -            else { -                pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno)); -                return -1; -            } -        } else { -            pa_assert((size_t) l <= left); -            d = (uint8_t*) d + l; -            left -= l; +            if (errno == EINTR) +                /* Retry right away if we got interrupted */ +                continue; -            if (left <= 0) +            else if (errno == EAGAIN) +                /* Hmm, apparently the socket was not writable, give up for now */                  break; + +            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno)); +            ret  = -1; +            break; +        } + +        pa_assert((size_t) l <= nbytes); + +        if ((size_t) l != nbytes) { +            pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.", +                        (unsigned long long) l, +                        (unsigned long long) nbytes); +            ret = -1; +            break;          } + +        u->write_index += (uint64_t) u->write_memchunk.length; +        pa_memblock_unref(u->write_memchunk.memblock); +        pa_memchunk_reset(&u->write_memchunk); + +        ret = 1; + +        break;      } -    return 0; +    return ret;  }  static void thread_func(void *userdata) {      struct userdata *u = userdata; -    pa_bool_t do_write = FALSE, writable = FALSE; +    unsigned do_write = 0; +    pa_bool_t writable = FALSE;      pa_assert(u); @@ -1080,9 +1266,6 @@ static void thread_func(void *userdata) {          goto fail;      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll); - -    pa_smoother_set_time_offset(u->read_smoother, pa_rtclock_usec());      for (;;) {          struct pollfd *pollfd; @@ -1093,13 +1276,20 @@ static void thread_func(void *userdata) {          if (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state)) { +            /* We should send two blocks to the device before we expect +             * a response. */ + +            if (u->write_index == 0 && u->read_index <= 0) +                do_write = 2; +              if (pollfd && (pollfd->revents & POLLIN)) { +                int n_read; -                if (hsp_process_push(u) < 0) +                if ((n_read = hsp_process_push(u)) < 0)                      goto fail;                  /* We just read something, so we are supposed to write something, too */ -                do_write = TRUE; +                do_write += n_read;              }          } @@ -1112,44 +1302,70 @@ static void thread_func(void *userdata) {                  if (pollfd->revents & POLLOUT)                      writable = TRUE; -                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && !do_write && writable) { +                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {                      pa_usec_t time_passed; -                    uint64_t should_have_written; +                    pa_usec_t audio_sent;                      /* Hmm, there is no input stream we could synchronize                       * to. So let's do things by time */ -                    time_passed = pa_rtclock_usec() - u->started_at; -                    should_have_written = pa_usec_to_bytes(time_passed, &u->sink->sample_spec); +                    time_passed = pa_rtclock_now() - u->started_at; +                    audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec); + +                    if (audio_sent <= time_passed) { +                        pa_usec_t audio_to_send = time_passed - audio_sent; + +                        /* Never try to catch up for more than 100ms */ +                        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); -                    do_write = u->write_index <= should_have_written ; -/*                 pa_log_debug("Time has come: %s", pa_yes_no(do_write)); */ +                            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; +                        } + +                        do_write = 1; +                    }                  } -                if (writable && do_write) { -                    if (u->write_index == 0) -                        u->started_at = pa_rtclock_usec(); +                if (writable && do_write > 0) { +                    int n_written; + +                    if (u->write_index <= 0) +                        u->started_at = pa_rtclock_now();                      if (u->profile == PROFILE_A2DP) { -                        if (a2dp_process_render(u) < 0) +                        if ((n_written = a2dp_process_render(u)) < 0)                              goto fail;                      } else { -                        if (hsp_process_render(u) < 0) +                        if ((n_written = hsp_process_render(u)) < 0)                              goto fail;                      } -                    do_write = FALSE; +                    if (n_written == 0) +                        pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!"); + +                    do_write -= n_written;                      writable = FALSE;                  } -                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && !do_write) { +                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {                      pa_usec_t time_passed, next_write_at, sleep_for;                      /* Hmm, there is no input stream we could synchronize                       * to. So let's estimate when we need to wake up the latest */ -                    time_passed = pa_rtclock_usec() - u->started_at; -                    next_write_at = pa_bytes_to_usec(u->write_index, &u->sink->sample_spec); +                    time_passed = pa_rtclock_now() - u->started_at; +                    next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);                      sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;  /*                 pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */ @@ -1177,7 +1393,11 @@ static void thread_func(void *userdata) {          pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;          if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) { -            pa_log_error("FD error."); +            pa_log_info("FD error: %s%s%s%s", +                        pollfd->revents & POLLERR ? "POLLERR " :"", +                        pollfd->revents & POLLHUP ? "POLLHUP " :"", +                        pollfd->revents & POLLPRI ? "POLLPRI " :"", +                        pollfd->revents & POLLNVAL ? "POLLNVAL " :"");              goto fail;          }      } @@ -1192,146 +1412,105 @@ finish:      pa_log_debug("IO thread shutting down");  } -/* static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *msg, void *userdata) { */ -/*     DBusMessageIter arg_i; */ -/*     DBusError err; */ -/*     const char *value; */ -/*     struct userdata *u; */ - -/*     pa_assert(bus); */ -/*     pa_assert(msg); */ -/*     pa_assert(userdata); */ -/*     u = userdata; */ - -/*     pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", */ -/*                  dbus_message_get_interface(msg), */ -/*                  dbus_message_get_path(msg), */ -/*                  dbus_message_get_member(msg)); */ - -/*     dbus_error_init(&err); */ - -/*    if (!dbus_message_has_path(msg, u->path)) */ -/*        goto done; */ - -/*     if (dbus_message_is_signal(msg, "org.bluez.Headset", "PropertyChanged") || */ -/*         dbus_message_is_signal(msg, "org.bluez.AudioSink", "PropertyChanged")) { */ - -/*         struct device *d; */ -/*         const char *profile; */ -/*         DBusMessageIter variant_i; */ -/*         dbus_uint16_t gain; */ - -/*         if (!dbus_message_iter_init(msg, &arg_i)) { */ -/*             pa_log("dbus: message has no parameters"); */ -/*             goto done; */ -/*         } */ +/* Run from main thread */ +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) { +    DBusError err; +    struct userdata *u; -/*         if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_STRING) { */ -/*             pa_log("Property name not a string."); */ -/*             goto done; */ -/*         } */ +    pa_assert(bus); +    pa_assert(m); +    pa_assert_se(u = userdata); -/*         dbus_message_iter_get_basic(&arg_i, &value); */ +    dbus_error_init(&err); -/*         if (!dbus_message_iter_next(&arg_i)) { */ -/*             pa_log("Property value missing"); */ -/*             goto done; */ -/*         } */ +    pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", +                 dbus_message_get_interface(m), +                 dbus_message_get_path(m), +                 dbus_message_get_member(m)); -/*         if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_VARIANT) { */ -/*             pa_log("Property value not a variant."); */ -/*             goto done; */ -/*         } */ +   if (!dbus_message_has_path(m, u->path)) +       goto fail; -/*         dbus_message_iter_recurse(&arg_i, &variant_i); */ +    if (dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged") || +        dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) { -/*         if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_UINT16) { */ -/*             dbus_message_iter_get_basic(&variant_i, &gain); */ +        dbus_uint16_t gain; +        pa_cvolume v; -/*             if (pa_streq(value, "SpeakerGain")) { */ -/*                 pa_log("spk gain: %d", gain); */ -/*                 pa_cvolume_set(&u->sink->virtual_volume, 1, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); */ -/*                 pa_subscription_post(u->sink->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, u->sink->index); */ -/*             } else { */ -/*                 pa_log("mic gain: %d", gain); */ -/*                 if (!u->source) */ -/*                     goto done; */ +        if (!dbus_message_get_args(m, &err, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID) || gain > 15) { +            pa_log("Failed to parse org.bluez.Headset.{Speaker|Microphone}GainChanged: %s", err.message); +            goto fail; +        } -/*                 pa_cvolume_set(&u->source->virtual_volume, 1, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); */ -/*                 pa_subscription_post(u->source->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, u->source->index); */ -/*             } */ -/*         } */ -/*     } */ +        if (u->profile == PROFILE_HSP) { +            if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) { -/* done: */ -/*     dbus_error_free(&err); */ -/*     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; */ -/* } */ +                pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); +                pa_sink_volume_changed(u->sink, &v, TRUE); -/* static int sink_get_volume_cb(pa_sink *s) { */ -/*     struct userdata *u = s->userdata; */ -/*     pa_assert(u); */ +            } else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) { -/*     /\* refresh? *\/ */ +                pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); +                pa_source_volume_changed(u->source, &v, TRUE); +            } +        } +    } -/*     return 0; */ -/* } */ +fail: +    dbus_error_free(&err); -/* static int source_get_volume_cb(pa_source *s) { */ -/*     struct userdata *u = s->userdata; */ -/*     pa_assert(u); */ +    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} -/*     /\* refresh? *\/ */ +/* Run from main thread */ +static void sink_set_volume_cb(pa_sink *s) { +    struct userdata *u = s->userdata; +    DBusMessage *m; +    dbus_uint16_t gain; -/*     return 0; */ -/* } */ +    pa_assert(u); -/* static int sink_set_volume_cb(pa_sink *s) { */ -/*     DBusError e; */ -/*     DBusMessage *m, *r; */ -/*     DBusMessageIter it, itvar; */ -/*     dbus_uint16_t vol; */ -/*     const char *spkgain = "SpeakerGain"; */ -/*     struct userdata *u = s->userdata; */ -/*     pa_assert(u); */ +    if (u->profile != PROFILE_HSP) +        return; -/*     dbus_error_init(&e); */ +    gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM; -/*     vol = ((float) pa_cvolume_max(&s->virtual_volume) / PA_VOLUME_NORM) * 15; */ -/*     pa_log_debug("set headset volume: %d", vol); */ +    if (gain > 15) +        gain = 15; -/*     pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetProperty")); */ -/*     dbus_message_iter_init_append(m, &it); */ -/*     dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &spkgain); */ -/*     dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, DBUS_TYPE_UINT16_AS_STRING, &itvar); */ -/*     dbus_message_iter_append_basic(&itvar, DBUS_TYPE_UINT16, &vol); */ -/*     dbus_message_iter_close_container(&it, &itvar); */ +    pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); -/*     r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->conn), m, -1, &e); */ +    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)); +    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->connection), m, NULL)); +    dbus_message_unref(m); +} -/* finish: */ -/*     if (m) */ -/*         dbus_message_unref(m); */ -/*     if (r) */ -/*         dbus_message_unref(r); */ +/* Run from main thread */ +static void source_set_volume_cb(pa_source *s) { +    struct userdata *u = s->userdata; +    DBusMessage *m; +    dbus_uint16_t gain; -/*     dbus_error_free(&e); */ +    pa_assert(u); -/*     return 0; */ -/* } */ +    if (u->profile != PROFILE_HSP) +        return; -/* static int source_set_volume_cb(pa_source *s) { */ -/*     dbus_uint16_t vol; */ -/*     struct userdata *u = s->userdata; */ -/*     pa_assert(u); */ +    gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM; -/*     vol = ((float)pa_cvolume_max(&s->virtual_volume) / PA_VOLUME_NORM) * 15; */ +    if (gain > 15) +        gain = 15; -/*     pa_log_debug("set headset mic volume: %d (not implemented yet)", vol); */ +    pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); -/*     return 0; */ -/* } */ +    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)); +    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->connection), m, NULL)); +    dbus_message_unref(m); +} +/* Run from main thread */  static char *get_name(const char *type, pa_modargs *ma, const char *device_id, pa_bool_t *namereg_fail) {      char *t;      const char *n; @@ -1360,6 +1539,8 @@ static char *get_name(const char *type, pa_modargs *ma, const char *device_id, p      return pa_sprintf_malloc("bluez_%s.%s", type, n);  } +#ifdef NOKIA +  static void sco_over_pcm_state_update(struct userdata *u) {      pa_assert(u);      pa_assert(USE_SCO_OVER_PCM(u)); @@ -1414,8 +1595,12 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct      return PA_HOOK_OK;  } +#endif + +/* Run from main thread */  static int add_sink(struct userdata *u) { +#ifdef NOKIA      if (USE_SCO_OVER_PCM(u)) {          pa_proplist *p; @@ -1428,7 +1613,10 @@ static int add_sink(struct userdata *u) {          if (!u->hsp.sink_state_changed_slot)              u->hsp.sink_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u); -    } else { +    } else +#endif + +    {          pa_sink_new_data data;          pa_bool_t b; @@ -1437,11 +1625,19 @@ static int add_sink(struct userdata *u) {          data.module = u->module;          pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);          pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); +        if (u->profile == PROFILE_HSP) +            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");          data.card = u->card; -        data.name = get_name("sink", u->modargs, u->device->address, &b); +        data.name = get_name("sink", u->modargs, u->address, &b);          data.namereg_fail = b; -        u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); +        if (pa_modargs_get_proplist(u->modargs, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +            pa_log("Invalid properties"); +            pa_sink_new_data_done(&data); +            return -1; +        } + +        u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY | (u->profile == PROFILE_HSP ? PA_SINK_HW_VOLUME_CTRL : 0));          pa_sink_new_data_done(&data);          if (!u->sink) { @@ -1451,28 +1647,36 @@ static int add_sink(struct userdata *u) {          u->sink->userdata = u;          u->sink->parent.process_msg = sink_process_msg; + +        pa_sink_set_max_request(u->sink, u->block_size); +        pa_sink_set_fixed_latency(u->sink, +                                  (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + +                                  pa_bytes_to_usec(u->block_size, &u->sample_spec));      } -/*     u->sink->get_volume = sink_get_volume_cb; */ -/*     u->sink->set_volume = sink_set_volume_cb; */ +    if (u->profile == PROFILE_HSP) { +        u->sink->set_volume = sink_set_volume_cb; +        u->sink->n_volume_steps = 16; +    }      return 0;  } +/* Run from main thread */  static int add_source(struct userdata *u) { -    pa_proplist *p; +#ifdef NOKIA      if (USE_SCO_OVER_PCM(u)) {          u->source = u->hsp.sco_source; -        p = pa_proplist_new(); -        pa_proplist_sets(p, "bluetooth.protocol", "sco"); -        pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p); -        pa_proplist_free(p); +        pa_proplist_sets(u->source->proplist, "bluetooth.protocol", "hsp");          if (!u->hsp.source_state_changed_slot)              u->hsp.source_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u); -    } else { +    } else +#endif + +    {          pa_source_new_data data;          pa_bool_t b; @@ -1480,12 +1684,20 @@ 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" : "sco"); +        pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp"); +        if (u->profile == PROFILE_HSP) +            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");          data.card = u->card; -        data.name = get_name("source", u->modargs, u->device->address, &b); +        data.name = get_name("source", u->modargs, u->address, &b);          data.namereg_fail = b; -        u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); +        if (pa_modargs_get_proplist(u->modargs, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +            pa_log("Invalid properties"); +            pa_source_new_data_done(&data); +            return -1; +        } + +        u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY | (u->profile == PROFILE_HSP ? PA_SOURCE_HW_VOLUME_CTRL : 0));          pa_source_new_data_done(&data);          if (!u->source) { @@ -1495,39 +1707,51 @@ static int add_source(struct userdata *u) {          u->source->userdata = u;          u->source->parent.process_msg = source_process_msg; -    } -/*     u->source->get_volume = source_get_volume_cb; */ -/*     u->source->set_volume = source_set_volume_cb; */ +        pa_source_set_fixed_latency(u->source, +                                    (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + +                                    pa_bytes_to_usec(u->block_size, &u->sample_spec)); +    } -    p = pa_proplist_new(); -    pa_proplist_sets(p, "bluetooth.nrec", pa_yes_no(u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC)); -    pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p); -    pa_proplist_free(p); +    if (u->profile == PROFILE_HSP) { +        pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0"); +        u->source->set_volume = source_set_volume_cb; +        u->source->n_volume_steps = 16; +    }      return 0;  } +/* Run from main thread */  static void shutdown_bt(struct userdata *u) {      pa_assert(u);      if (u->stream_fd >= 0) {          pa_close(u->stream_fd);          u->stream_fd = -1; + +        u->stream_write_type = 0;      }      if (u->service_fd >= 0) {          pa_close(u->service_fd);          u->service_fd = -1; +        u->service_write_type = u->service_write_type = 0; +    } + +    if (u->write_memchunk.memblock) { +        pa_memblock_unref(u->write_memchunk.memblock); +        pa_memchunk_reset(&u->write_memchunk);      }  } +/* Run from main thread */  static int init_bt(struct userdata *u) {      pa_assert(u);      shutdown_bt(u); -    u->stream_write_type = u->stream_read_type = 0; +    u->stream_write_type = 0;      u->service_write_type = u->service_write_type = 0;      if ((u->service_fd = bt_audio_service_open()) < 0) { @@ -1540,10 +1764,11 @@ static int init_bt(struct userdata *u) {      return 0;  } +/* Run from main thread */  static int setup_bt(struct userdata *u) {      pa_assert(u); -    if (get_caps(u) < 0) +    if (get_caps(u, 0) < 0)          return -1;      pa_log_debug("Got device capabilities"); @@ -1553,16 +1778,19 @@ static int setup_bt(struct userdata *u) {      pa_log_debug("Connection to the device configured"); +#ifdef NOKIA      if (USE_SCO_OVER_PCM(u)) {          pa_log_debug("Configured to use SCO over PCM");          return 0;      } +#endif      pa_log_debug("Got the stream socket");      return 0;  } +/* Run from main thread */  static int init_profile(struct userdata *u) {      int r = 0;      pa_assert(u); @@ -1583,6 +1811,7 @@ static int init_profile(struct userdata *u) {      return r;  } +/* Run from main thread */  static void stop_thread(struct userdata *u) {      pa_assert(u); @@ -1623,8 +1852,14 @@ static void stop_thread(struct userdata *u) {          pa_rtpoll_free(u->rtpoll);          u->rtpoll = NULL;      } + +    if (u->read_smoother) { +        pa_smoother_free(u->read_smoother); +        u->read_smoother = NULL; +    }  } +/* Run from main thread */  static int start_thread(struct userdata *u) {      pa_assert(u);      pa_assert(!u->thread); @@ -1634,6 +1869,7 @@ static int start_thread(struct userdata *u) {      u->rtpoll = pa_rtpoll_new();      pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll); +#ifdef NOKIA      if (USE_SCO_OVER_PCM(u)) {          if (start_stream_fd(u) < 0)              return -1; @@ -1643,6 +1879,7 @@ static int start_thread(struct userdata *u) {          /* FIXME: monitor stream_fd error */          return 0;      } +#endif      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log_error("Failed to create IO thread"); @@ -1654,21 +1891,29 @@ static int start_thread(struct userdata *u) {          pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);          pa_sink_set_rtpoll(u->sink, u->rtpoll);          pa_sink_put(u->sink); + +        if (u->sink->set_volume) +            u->sink->set_volume(u->sink);      }      if (u->source) {          pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);          pa_source_set_rtpoll(u->source, u->rtpoll);          pa_source_put(u->source); + +        if (u->source->set_volume) +            u->source->set_volume(u->source);      }      return 0;  } +/* Run from main thread */  static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      struct userdata *u;      enum profile *d;      pa_queue *inputs = NULL, *outputs = NULL; +    const pa_bluetooth_device *device;      pa_assert(c);      pa_assert(new_profile); @@ -1676,26 +1921,44 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      d = PA_CARD_PROFILE_DATA(new_profile); +    if (!(device = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) { +        pa_log_error("Failed to get device object."); +        return -PA_ERR_IO; +    } + +    /* The state signal is sent by bluez, so it is racy to check +       strictly for CONNECTED, we should also accept STREAMING state +       as being good enough. However, if the profile is used +       concurrently (which is unlikely), ipc will fail later on, and +       module will be unloaded. */ +    if (device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) { +        pa_log_warn("HSP is not connected, refused to switch profile"); +        return -PA_ERR_IO; +    } +    else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) { +        pa_log_warn("A2DP is not connected, refused to switch profile"); +        return -PA_ERR_IO; +    } +      if (u->sink) { -        inputs = pa_sink_move_all_start(u->sink); +        inputs = pa_sink_move_all_start(u->sink, NULL); +#ifdef NOKIA          if (!USE_SCO_OVER_PCM(u)) +#endif              pa_sink_unlink(u->sink);      }      if (u->source) { -        outputs = pa_source_move_all_start(u->source); +        outputs = pa_source_move_all_start(u->source, NULL); +#ifdef NOKIA          if (!USE_SCO_OVER_PCM(u)) +#endif              pa_source_unlink(u->source);      }      stop_thread(u);      shutdown_bt(u); -    if (u->write_memchunk.memblock) { -        pa_memblock_unref(u->write_memchunk.memblock); -        pa_memchunk_reset(&u->write_memchunk); -    } -      u->profile = *d;      u->sample_spec = u->requested_sample_spec; @@ -1724,36 +1987,51 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      return 0;  } -static int add_card(struct userdata *u, const char * default_profile) { +/* Run from main thread */ +static int add_card(struct userdata *u, const pa_bluetooth_device *device) {      pa_card_new_data data;      pa_bool_t b;      pa_card_profile *p;      enum profile *d;      const char *ff;      char *n; +    const char *default_profile; + +    pa_assert(u); +    pa_assert(device);      pa_card_new_data_init(&data);      data.driver = __FILE__;      data.module = u->module; -    n = pa_bluetooth_cleanup_name(u->device->name); +    n = pa_bluetooth_cleanup_name(device->name);      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n);      pa_xfree(n); -    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device->address); +    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, device->address);      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "bluez");      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "bluetooth"); -    if ((ff = pa_bluetooth_get_form_factor(u->device->class))) +    if ((ff = pa_bluetooth_get_form_factor(device->class)))          pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, ff); -    pa_proplist_sets(data.proplist, "bluez.path", u->device->path); -    pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) u->device->class); -    pa_proplist_sets(data.proplist, "bluez.name", u->device->name); -    data.name = get_name("card", u->modargs, u->device->address, &b); +    pa_proplist_sets(data.proplist, "bluez.path", device->path); +    pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) device->class); +    pa_proplist_sets(data.proplist, "bluez.name", device->name); +    data.name = get_name("card", u->modargs, device->address, &b);      data.namereg_fail = b; +    if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_card_new_data_done(&data); +        return -1; +    } +      data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); -    if (u->device->audio_sink_info_valid > 0) { +    /* we base hsp/a2dp availability on UUIDs. +       Ideally, it would be based on "Connected" state, but +       we can't afford to wait for this information when +       we are loaded with profile="hsp", for instance */ +    if (pa_bluetooth_uuid_has(device->uuids, A2DP_SINK_UUID)) {          p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile));          p->priority = 10;          p->n_sinks = 1; @@ -1767,7 +2045,8 @@ static int add_card(struct userdata *u, const char * default_profile) {          pa_hashmap_put(data.profiles, p->name, p);      } -    if (u->device->headset_info_valid > 0) { +    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));          p->priority = 20;          p->n_sinks = 1; @@ -1788,7 +2067,7 @@ static int add_card(struct userdata *u, const char * default_profile) {      *d = PROFILE_OFF;      pa_hashmap_put(data.profiles, p->name, p); -    if (default_profile) { +    if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) {          if (pa_hashmap_get(data.profiles, default_profile))              pa_card_new_data_set_profile(&data, default_profile);          else @@ -1807,51 +2086,71 @@ static int add_card(struct userdata *u, const char * default_profile) {      u->card->set_profile = card_set_profile;      d = PA_CARD_PROFILE_DATA(u->card->active_profile); -    u->profile = *d; -    return 0; -} - -static int setup_dbus(struct userdata *u) { -    DBusError error; - -    dbus_error_init(&error); - -    u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &error); -    if (dbus_error_is_set(&error) || (!u->connection)) { -        pa_log("Failed to get D-Bus connection: %s", error.message); -        dbus_error_free(&error); -        return -1; +    if ((device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) || +        (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP)) { +        pa_log_warn("Default profile not connected, selecting off profile"); +        u->card->active_profile = pa_hashmap_get(u->card->profiles, "off"); +        u->card->save_profile = FALSE;      } +    d = PA_CARD_PROFILE_DATA(u->card->active_profile); +    u->profile = *d; +      return 0;  } -static int find_device(struct userdata *u, const char *address, const char *path) { +/* Run from main thread */ +static const pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) { +    const pa_bluetooth_device *d = NULL; +      pa_assert(u);      if (!address && !path) {          pa_log_error("Failed to get device address/path from module arguments."); -        return -1; +        return NULL;      }      if (path) { -        if (!(u->device = pa_bluetooth_get_device(pa_dbus_connection_get(u->connection), path))) { +        if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, path))) {              pa_log_error("%s is not a valid BlueZ audio device.", path); -            return -1; +            return NULL;          } -        if (address && !(pa_streq(u->device->address, address))) { +        if (address && !(pa_streq(d->address, address))) {              pa_log_error("Passed path %s and address %s don't match.", path, address); -            return -1; +            return NULL;          } +      } else { -        if (!(u->device = pa_bluetooth_find_device(pa_dbus_connection_get(u->connection), address))) { +        if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) {              pa_log_error("%s is not known.", address); -            return -1; +            return NULL;          }      } +    if (d) { +        u->address = pa_xstrdup(d->address); +        u->path = pa_xstrdup(d->path); +    } + +    return d; +} + +/* Run from main thread */ +static int setup_dbus(struct userdata *u) { +    DBusError err; + +    dbus_error_init(&err); + +    u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &err); + +    if (dbus_error_is_set(&err) || !u->connection) { +        pa_log("Failed to get D-Bus connection: %s", err.message); +        dbus_error_free(&err); +        return -1; +    } +      return 0;  } @@ -1860,9 +2159,14 @@ int pa__init(pa_module* m) {      uint32_t channels;      struct userdata *u;      const char *address, *path; +    DBusError err; +    char *mike, *speaker; +    const pa_bluetooth_device *device;      pa_assert(m); +    dbus_error_init(&err); +      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {          pa_log_error("Failed to parse module arguments");          goto fail; @@ -1873,10 +2177,10 @@ int pa__init(pa_module* m) {      u->core = m->core;      u->service_fd = -1;      u->stream_fd = -1; -    u->read_smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10);      u->sample_spec = m->core->default_sample_spec;      u->modargs = ma; +#ifdef NOKIA      if (pa_modargs_get_value(ma, "sco_sink", NULL) &&          !(u->hsp.sco_sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_sink", NULL), PA_NAMEREG_SINK))) {          pa_log("SCO sink not found"); @@ -1888,6 +2192,7 @@ int pa__init(pa_module* m) {          pa_log("SCO source not found");          goto fail;      } +#endif      if (pa_modargs_get_value_u32(ma, "rate", &u->sample_spec.rate) < 0 ||          u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) { @@ -1904,65 +2209,54 @@ int pa__init(pa_module* m) {      u->sample_spec.channels = (uint8_t) channels;      u->requested_sample_spec = u->sample_spec; -    if (setup_dbus(u) < 0) -        goto fail; -      address = pa_modargs_get_value(ma, "address", NULL);      path = pa_modargs_get_value(ma, "path", NULL); -    if (find_device(u, address, path) < 0) +    if (setup_dbus(u) < 0) +        goto fail; + +    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))          goto fail; -    pa_assert(u->device); +    if (!(device = find_device(u, address, path))) +        goto fail;      /* Add the card structure. This will also initialize the default profile */ -    if (add_card(u, pa_modargs_get_value(ma, "profile", NULL)) < 0) +    if (add_card(u, device) < 0)          goto fail;      /* Connect to the BT service and query capabilities */      if (init_bt(u) < 0)          goto fail; +    if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) { +        pa_log_error("Failed to add filter function"); +        goto fail; +    } + +    speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path); +    mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path); + +    if (pa_dbus_add_matches( +                pa_dbus_connection_get(u->connection), &err, +                speaker, +                mike, +                NULL) < 0) { + +        pa_xfree(speaker); +        pa_xfree(mike); + +        pa_log("Failed to add D-Bus matches: %s", err.message); +        goto fail; +    } + +    pa_xfree(speaker); +    pa_xfree(mike); +      if (u->profile != PROFILE_OFF)          if (init_profile(u) < 0)              goto fail; -/*     if (u->path) { */ -/*         DBusError err; */ -/*         dbus_error_init(&err); */ -/*         char *t; */ - - -/*         if (!dbus_connection_add_filter(pa_dbus_connection_get(u->conn), filter_cb, u, NULL)) { */ -/*             pa_log_error("Failed to add filter function"); */ -/*             goto fail; */ -/*         } */ - -/*         if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO || */ -/*             u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */ -/*             t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path); */ -/*             dbus_bus_add_match(pa_dbus_connection_get(u->conn), t, &err); */ -/*             pa_xfree(t); */ - -/*             if (dbus_error_is_set(&err)) { */ -/*                 pa_log_error("Unable to subscribe to org.bluez.Headset signals: %s: %s", err.name, err.message); */ -/*                 goto fail; */ -/*             } */ -/*         } */ - -/*         if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP || */ -/*             u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */ -/*             t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path); */ -/*             dbus_bus_add_match(pa_dbus_connection_get(u->conn), t, &err); */ -/*             pa_xfree(t); */ - -/*             if (dbus_error_is_set(&err)) { */ -/*                 pa_log_error("Unable to subscribe to org.bluez.AudioSink signals: %s: %s", err.name, err.message); */ -/*                 goto fail; */ -/*             } */ -/*         } */ -/*     } */ -      if (u->sink || u->source)          if (start_thread(u) < 0)              goto fail; @@ -1970,7 +2264,11 @@ int pa__init(pa_module* m) {      return 0;  fail: +      pa__done(m); + +    dbus_error_free(&err); +      return -1;  } @@ -1992,39 +2290,39 @@ void pa__done(pa_module *m) {      if (!(u = m->userdata))          return; -    if (u->sink && !USE_SCO_OVER_PCM(u)) +    if (u->sink +#ifdef NOKIA +        && !USE_SCO_OVER_PCM(u) +#endif +    )          pa_sink_unlink(u->sink); -    if (u->source && !USE_SCO_OVER_PCM(u)) +    if (u->source +#ifdef NOKIA +        && !USE_SCO_OVER_PCM(u) +#endif +    )          pa_source_unlink(u->source);      stop_thread(u);      if (u->connection) { -/*         DBusError error; */ -/*         char *t; */ - -/*         if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO || */ -/*             u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */ - -/*             t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path); */ -/*             dbus_error_init(&error); */ -/*             dbus_bus_remove_match(pa_dbus_connection_get(u->conn), t, &error); */ -/*             dbus_error_free(&error); */ -/*             pa_xfree(t); */ -/*         } */ - -/*         if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP || */ -/*             u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */ - -/*             t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path); */ -/*             dbus_error_init(&error); */ -/*             dbus_bus_remove_match(pa_dbus_connection_get(u->conn), t, &error); */ -/*             dbus_error_free(&error); */ -/*             pa_xfree(t); */ -/*         } */ - -/*         dbus_connection_remove_filter(pa_dbus_connection_get(u->conn), filter_cb, u); */ + +        if (u->path) { +            char *speaker, *mike; +            speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path); +            mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path); + +            pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), +                                   speaker, +                                   mike, +                                   NULL); + +            pa_xfree(speaker); +            pa_xfree(mike); +        } + +        dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u);          pa_dbus_connection_unref(u->connection);      } @@ -2036,12 +2334,6 @@ void pa__done(pa_module *m) {      shutdown_bt(u); -    if (u->device) -        pa_bluetooth_device_free(u->device); - -    if (u->write_memchunk.memblock) -        pa_memblock_unref(u->write_memchunk.memblock); -      if (u->a2dp.buffer)          pa_xfree(u->a2dp.buffer); @@ -2050,5 +2342,11 @@ void pa__done(pa_module *m) {      if (u->modargs)          pa_modargs_free(u->modargs); +    pa_xfree(u->address); +    pa_xfree(u->path); + +    if (u->discovery) +        pa_bluetooth_discovery_unref(u->discovery); +      pa_xfree(u);  } diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index 4586d8ca..788fee00 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -34,7 +34,7 @@  #include <pulsecore/macro.h>  #include <pulsecore/llist.h>  #include <pulsecore/core-util.h> -#include <modules/dbus-util.h> +#include <pulsecore/dbus-shared.h>  #include "module-bluetooth-discover-symdef.h"  #include "bluetooth-util.h" @@ -42,13 +42,21 @@  PA_MODULE_AUTHOR("Joao Paulo Rechi Vita");  PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers");  PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_USAGE("sco_sink=<name of sink> " -                "sco_source=<name of source>" -                "async=<Asynchronous initialization?>"); +PA_MODULE_USAGE("async=<Asynchronous initialization?>"); +PA_MODULE_LOAD_ONCE(TRUE); + +/* +#ifdef NOKIA +   "sco_sink=<name of sink> " +   "sco_source=<name of source>" +#endif +*/  static const char* const valid_modargs[] = { +#ifdef NOKIA      "sco_sink",      "sco_source", +#endif      "async",      NULL  }; @@ -57,26 +65,45 @@ struct userdata {      pa_module *module;      pa_modargs *modargs;      pa_core *core; -    pa_dbus_connection *connection;      pa_bluetooth_discovery *discovery; +    pa_hook_slot *slot; +    pa_hashmap *hashmap;  }; -static void load_module_for_device(struct userdata *u, pa_bluetooth_device *d, pa_bool_t good) { +struct module_info { +    char *path; +    uint32_t module; +}; + +static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) { +    struct module_info *mi; +      pa_assert(u);      pa_assert(d); -    if (good && -        d->device_connected > 0 && -        (d->audio_sink_connected > 0 || d->headset_connected > 0)) { +    mi = pa_hashmap_get(u->hashmap, d->path); + +    if (!d->dead && +        d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) { -        if (((uint32_t) PA_PTR_TO_UINT(d->data))-1 == PA_INVALID_INDEX) { +        if (!mi) {              pa_module *m = NULL;              char *args;              /* Oh, awesome, a new device has shown up and been connected! */ -            args = pa_sprintf_malloc("address=\"%s\" path=\"%s\" profile=\"%s\"", d->address, d->path, d->headset_connected ? "hsp" : "a2dp"); +            args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path); +#if 0 +            /* This is in case we have to use hsp immediately, without waiting for .Audio.State = Connected */ +            if (d->headset_state >= PA_BT_AUDIO_STATE_CONNECTED && somecondition) { +                char *tmp; +                tmp = pa_sprintf_malloc("%s profile=\"hsp\"", args); +                pa_xfree(args); +                args = tmp; +            } +#endif +#ifdef NOKIA              if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) &&                  pa_modargs_get_value(u->modargs, "sco_source", NULL)) {                  char *tmp; @@ -87,44 +114,38 @@ static void load_module_for_device(struct userdata *u, pa_bluetooth_device *d, p                  pa_xfree(args);                  args = tmp;              } +#endif              pa_log_debug("Loading module-bluetooth-device %s", args);              m = pa_module_load(u->module->core, "module-bluetooth-device", args);              pa_xfree(args); -            if (m) -                d->data = PA_UINT_TO_PTR((uint32_t) (m->index+1)); -            else +            if (m) { +                mi = pa_xnew(struct module_info, 1); +                mi->module = m->index; +                mi->path = pa_xstrdup(d->path); + +                pa_hashmap_put(u->hashmap, mi->path, mi); +            } else                  pa_log_debug("Failed to load module for device %s", d->path);          }      } else { -        if (((uint32_t) PA_PTR_TO_UINT(d->data))-1 != PA_INVALID_INDEX) { +        if (mi) {              /* Hmm, disconnection? Then let's unload our module */              pa_log_debug("Unloading module for %s", d->path); -            pa_module_unload_request_by_index(u->core, (uint32_t) (PA_PTR_TO_UINT(d->data))-1, TRUE); -            d->data = NULL; -        } -    } -} - -static int setup_dbus(struct userdata *u) { -    DBusError err; +            pa_module_unload_request_by_index(u->core, mi->module, TRUE); -    dbus_error_init(&err); - -    u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &err); - -    if (dbus_error_is_set(&err) || !u->connection) { -        pa_log("Failed to get D-Bus connection: %s", err.message); -        dbus_error_free(&err); -        return -1; +            pa_hashmap_remove(u->hashmap, mi->path); +            pa_xfree(mi->path); +            pa_xfree(mi); +        }      } -    return 0; +    return PA_HOOK_OK;  }  int pa__init(pa_module* m) { @@ -149,12 +170,12 @@ int pa__init(pa_module* m) {      u->core = m->core;      u->modargs = ma;      ma = NULL; +    u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); -    if (setup_dbus(u) < 0) +    if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))          goto fail; -    if (!(u->discovery = pa_bluetooth_discovery_new(pa_dbus_connection_get(u->connection), load_module_for_device, u))) -        goto fail; +    u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery), PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u);      if (!async)          pa_bluetooth_discovery_sync(u->discovery); @@ -178,11 +199,22 @@ void pa__done(pa_module* m) {      if (!(u = m->userdata))          return; +    if (u->slot) +        pa_hook_slot_free(u->slot); +      if (u->discovery) -        pa_bluetooth_discovery_free(u->discovery); +        pa_bluetooth_discovery_unref(u->discovery); + +    if (u->hashmap) { +        struct module_info *mi; -    if (u->connection) -        pa_dbus_connection_unref(u->connection); +        while ((mi = pa_hashmap_steal_first(u->hashmap))) { +            pa_xfree(mi->path); +            pa_xfree(mi); +        } + +        pa_hashmap_free(u->hashmap, NULL, NULL); +    }      if (u->modargs)          pa_modargs_free(u->modargs); diff --git a/src/modules/bluetooth/module-bluetooth-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c index c8d7b4d9..c4cfd733 100644 --- a/src/modules/bluetooth/module-bluetooth-proximity.c +++ b/src/modules/bluetooth/module-bluetooth-proximity.c @@ -42,8 +42,8 @@  #include <pulsecore/core-util.h>  #include <pulsecore/core-error.h>  #include <pulsecore/start-child.h> +#include <pulsecore/dbus-shared.h> -#include "../dbus-util.h"  #include "module-bluetooth-proximity-symdef.h"  PA_MODULE_AUTHOR("Lennart Poettering"); @@ -109,7 +109,7 @@ static void update_volume(struct userdata *u) {          }          pa_log_info("Found %u BT devices, unmuting.", u->n_found); -        pa_sink_set_mute(s, FALSE); +        pa_sink_set_mute(s, FALSE, FALSE);      } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {          pa_sink *s; @@ -122,7 +122,7 @@ static void update_volume(struct userdata *u) {          }          pa_log_info("No BT devices found, muting."); -        pa_sink_set_mute(s, TRUE); +        pa_sink_set_mute(s, TRUE, FALSE);      } else          pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown); diff --git a/src/modules/bluetooth/sbc.c b/src/modules/bluetooth/sbc.c index 6fa54796..779be4bd 100644 --- a/src/modules/bluetooth/sbc.c +++ b/src/modules/bluetooth/sbc.c @@ -973,13 +973,13 @@ int sbc_init(sbc_t *sbc, unsigned long flags)  	return 0;  } -int sbc_parse(sbc_t *sbc, void *input, int input_len) +ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len)  {  	return sbc_decode(sbc, input, input_len, NULL, 0, NULL);  } -int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output, -		int output_len, int *written) +ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len, +			void *output, size_t output_len, size_t *written)  {  	struct sbc_priv *priv;  	char *ptr; @@ -1004,7 +1004,7 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,  		sbc->bitpool = priv->frame.bitpool;  		priv->frame.codesize = sbc_get_codesize(sbc); -		priv->frame.length = sbc_get_frame_length(sbc); +		priv->frame.length = framelen;  	}  	if (!output) @@ -1020,7 +1020,7 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,  	ptr = output; -	if (output_len < samples * priv->frame.channels * 2) +	if (output_len < (size_t) (samples * priv->frame.channels * 2))  		samples = output_len / (priv->frame.channels * 2);  	for (i = 0; i < samples; i++) { @@ -1044,10 +1044,8 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,  	return framelen;  } -ssize_t sbc_encode(sbc_t *sbc, -               const void *input, size_t input_len, -               void *output, size_t output_len, -               size_t *written) +ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len, +			void *output, size_t output_len, size_t *written)  {  	struct sbc_priv *priv;  	int framelen, samples; @@ -1138,30 +1136,25 @@ void sbc_finish(sbc_t *sbc)  size_t sbc_get_frame_length(sbc_t *sbc)  {  	size_t ret; -	uint8_t subbands, channels, blocks, joint; +	uint8_t subbands, channels, blocks, joint, bitpool;  	struct sbc_priv *priv;  	priv = sbc->priv; -	if (!priv->init) { -		subbands = sbc->subbands ? 8 : 4; -		blocks = 4 + (sbc->blocks * 4); -		channels = sbc->mode == SBC_MODE_MONO ? 1 : 2; -		joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0; -	} else { -		subbands = priv->frame.subbands; -		blocks = priv->frame.blocks; -		channels = priv->frame.channels; -		joint = priv->frame.joint; -	} +	if (priv->init) +		return priv->frame.length; -	ret = 4 + (4 * subbands * channels) / 8; +	subbands = sbc->subbands ? 8 : 4; +	blocks = 4 + (sbc->blocks * 4); +	channels = sbc->mode == SBC_MODE_MONO ? 1 : 2; +	joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0; +	bitpool = sbc->bitpool; +	ret = 4 + (4 * subbands * channels) / 8;  	/* This term is not always evenly divide so we round it up */  	if (channels == 1) -		ret += ((blocks * channels * sbc->bitpool) + 7) / 8; +		ret += ((blocks * channels * bitpool) + 7) / 8;  	else -		ret += (((joint ? subbands : 0) + blocks * sbc->bitpool) + 7) -			/ 8; +		ret += (((joint ? subbands : 0) + blocks * bitpool) + 7) / 8;  	return ret;  } diff --git a/src/modules/bluetooth/sbc.h b/src/modules/bluetooth/sbc.h index 25a12885..65435884 100644 --- a/src/modules/bluetooth/sbc.h +++ b/src/modules/bluetooth/sbc.h @@ -82,15 +82,15 @@ typedef struct sbc_struct sbc_t;  int sbc_init(sbc_t *sbc, unsigned long flags);  int sbc_reinit(sbc_t *sbc, unsigned long flags); -int sbc_parse(sbc_t *sbc, void *input, int input_len); -int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output, -		int output_len, int *len); + +ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len); + +ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len, +			void *output, size_t output_len, size_t *written);  /* Encodes ONE input block into ONE output block */ -ssize_t sbc_encode(sbc_t *sbc, -               const void *input, size_t input_len, -               void *output, size_t output_len, -               size_t *written); +ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len, +			void *output, size_t output_len, size_t *written);  /* Returns the output block size in bytes */  size_t sbc_get_frame_length(sbc_t *sbc); diff --git a/src/modules/hal-util.c b/src/modules/hal-util.c index 422ae4ec..e2a2d8d7 100644 --- a/src/modules/hal-util.c +++ b/src/modules/hal-util.c @@ -24,10 +24,10 @@  #endif  #include <pulsecore/log.h> +#include <pulsecore/dbus-shared.h>  #include <hal/libhal.h> -#include "dbus-util.h"  #include "hal-util.h"  int pa_hal_get_info(pa_core *core, pa_proplist *p, int card) { diff --git a/src/modules/jack/module-jack-sink.c b/src/modules/jack/module-jack-sink.c new file mode 100644 index 00000000..fc976fa7 --- /dev/null +++ b/src/modules/jack/module-jack-sink.c @@ -0,0 +1,504 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2006 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 <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> + +#include <jack/jack.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.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 "module-jack-sink-symdef.h" + +/* General overview: + * + * Because JACK has a very unflexible event loop management which + * doesn't allow us to add our own event sources to the event thread + * we cannot use the JACK real-time thread for dispatching our PA + * work. Instead, we run an additional RT thread which does most of + * the PA handling, and have the JACK RT thread request data from it + * via pa_asyncmsgq. The cost is an additional context switch which + * should hopefully not be that expensive if RT scheduling is + * enabled. A better fix would only be possible with additional event + * source support in JACK. + */ + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("JACK Sink"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE( +        "sink_name=<name for the sink> " +        "sink_properties=<properties  for the card> " +        "server_name=<jack server name> " +        "client_name=<jack client name> " +        "channels=<number of channels> " +        "channel_map=<channel map> " +        "connect=<connect ports?>"); + +#define DEFAULT_SINK_NAME "jack_out" + +struct userdata { +    pa_core *core; +    pa_module *module; +    pa_sink *sink; + +    unsigned channels; + +    jack_port_t* port[PA_CHANNELS_MAX]; +    jack_client_t *client; + +    void *buffer[PA_CHANNELS_MAX]; + +    pa_thread_mq thread_mq; +    pa_asyncmsgq *jack_msgq; +    pa_rtpoll *rtpoll; +    pa_rtpoll_item *rtpoll_item; + +    pa_thread *thread; + +    jack_nframes_t frames_in_buffer; +    jack_nframes_t saved_frame_time; +    pa_bool_t saved_frame_time_valid; +}; + +static const char* const valid_modargs[] = { +    "sink_name", +    "sink_properties", +    "server_name", +    "client_name", +    "channels", +    "channel_map", +    "connect", +    NULL +}; + +enum { +    SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX, +    SINK_MESSAGE_BUFFER_SIZE, +    SINK_MESSAGE_ON_SHUTDOWN +}; + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) { +    struct userdata *u = PA_SINK(o)->userdata; + +    switch (code) { + +        case SINK_MESSAGE_RENDER: + +            /* Handle the request from the JACK thread */ + +            if (u->sink->thread_info.state == PA_SINK_RUNNING) { +                pa_memchunk chunk; +                size_t nbytes; +                void *p; + +                pa_assert(offset > 0); +                nbytes = (size_t) offset * pa_frame_size(&u->sink->sample_spec); + +                pa_sink_render_full(u->sink, nbytes, &chunk); + +                p = (uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index; +                pa_deinterleave(p, u->buffer, u->channels, sizeof(float), (unsigned) offset); +                pa_memblock_release(chunk.memblock); + +                pa_memblock_unref(chunk.memblock); +            } else { +                unsigned c; +                pa_sample_spec ss; + +                /* Humm, we're not RUNNING, hence let's write some silence */ + +                ss = u->sink->sample_spec; +                ss.channels = 1; + +                for (c = 0; c < u->channels; c++) +                    pa_silence_memory(u->buffer[c], (size_t) offset * pa_sample_size(&ss), &ss); +            } + +            u->frames_in_buffer = (jack_nframes_t) offset; +            u->saved_frame_time = * (jack_nframes_t*) data; +            u->saved_frame_time_valid = TRUE; + +            return 0; + +        case SINK_MESSAGE_BUFFER_SIZE: +            pa_sink_set_max_request_within_thread(u->sink, (size_t) offset * pa_frame_size(&u->sink->sample_spec)); +            return 0; + +        case SINK_MESSAGE_ON_SHUTDOWN: +            pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); +            return 0; + +        case PA_SINK_MESSAGE_GET_LATENCY: { +            jack_nframes_t l, ft, d; +            size_t n; + +            /* This is the "worst-case" latency */ +            l = jack_port_get_total_latency(u->client, u->port[0]) + u->frames_in_buffer; + +            if (u->saved_frame_time_valid) { +                /* Adjust the worst case latency by the time that +                 * passed since we last handed data to JACK */ + +                ft = jack_frame_time(u->client); +                d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; +                l = l > d ? l - d : 0; +            } + +            /* Convert it to usec */ +            n = l * pa_frame_size(&u->sink->sample_spec); +            *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + +            return 0; +        } + +    } + +    return pa_sink_process_msg(o, code, data, offset, memchunk); +} + +static int jack_process(jack_nframes_t nframes, void *arg) { +    struct userdata *u = arg; +    unsigned c; +    jack_nframes_t frame_time; +    pa_assert(u); + +    /* We just forward the request to our other RT thread */ + +    for (c = 0; c < u->channels; c++) +        pa_assert_se(u->buffer[c] = jack_port_get_buffer(u->port[c], nframes)); + +    frame_time = jack_frame_time(u->client); + +    pa_assert_se(pa_asyncmsgq_send(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RENDER, &frame_time, nframes, NULL) == 0); +    return 0; +} + +static void thread_func(void *userdata) { +    struct userdata *u = userdata; + +    pa_assert(u); + +    pa_log_debug("Thread starting up"); + +    if (u->core->realtime_scheduling) +        pa_make_realtime(u->core->realtime_priority); + +    pa_thread_mq_install(&u->thread_mq); + +    for (;;) { +        int ret; + +        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) +            if (u->sink->thread_info.rewind_requested) +                pa_sink_process_rewind(u->sink, 0); + +        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) +            goto fail; + +        if (ret == 0) +            goto finish; +    } + +fail: +    /* If this was no regular exit from the loop we have to continue +     * processing messages until we received PA_MESSAGE_SHUTDOWN */ +    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); +    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: +    pa_log_debug("Thread shutting down"); +} + +static void jack_error_func(const char*t) { +    char *s; + +    s = pa_xstrndup(t, strcspn(t, "\n\r")); +    pa_log_warn("JACK error >%s<", s); +    pa_xfree(s); +} + +static void jack_init(void *arg) { +    struct userdata *u = arg; + +    pa_log_info("JACK thread starting up."); + +    if (u->core->realtime_scheduling) +        pa_make_realtime(u->core->realtime_priority+4); +} + +static void jack_shutdown(void* arg) { +    struct userdata *u = arg; + +    pa_log_info("JACK thread shutting down."); +    pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); +} + +static int jack_buffer_size(jack_nframes_t nframes, void *arg) { +    struct userdata *u = arg; + +    pa_log_info("JACK buffer size changed."); +    pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_BUFFER_SIZE, NULL, nframes, NULL, NULL); +    return 0; +} + +int pa__init(pa_module*m) { +    struct userdata *u = NULL; +    pa_sample_spec ss; +    pa_channel_map map; +    pa_modargs *ma = NULL; +    jack_status_t status; +    const char *server_name, *client_name; +    uint32_t channels = 0; +    pa_bool_t do_connect = TRUE; +    unsigned i; +    const char **ports = NULL, **p; +    pa_sink_new_data data; + +    pa_assert(m); + +    jack_set_error_function(jack_error_func); + +    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, "connect", &do_connect) < 0) { +        pa_log("Failed to parse connect= argument."); +        goto fail; +    } + +    server_name = pa_modargs_get_value(ma, "server_name", NULL); +    client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink"); + +    m->userdata = u = pa_xnew0(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->saved_frame_time_valid = FALSE; +    u->rtpoll = pa_rtpoll_new(); +    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + +    /* The queue linking the JACK thread and our RT thread */ +    u->jack_msgq = pa_asyncmsgq_new(0); + +    /* The msgq from the JACK RT thread should have an even higher +     * priority than the normal message queues, to match the guarantee +     * all other drivers make: supplying the audio device with data is +     * the top priority -- and as long as that is possible we don't do +     * anything else */ +    u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + +    if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { +        pa_log("jack_client_open() failed."); +        goto fail; +    } + +    ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); + +    channels = 0; +    for (p = ports; *p; p++) +        channels++; + +    if (!channels) +        channels = m->core->default_sample_spec.channels; + +    if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || +        channels <= 0 || +        channels > PA_CHANNELS_MAX) { +        pa_log("Failed to parse channels= argument."); +        goto fail; +    } + +    if (channels == m->core->default_channel_map.channels) +        map = m->core->default_channel_map; +    else +        pa_channel_map_init_extend(&map, channels, PA_CHANNEL_MAP_ALSA); + +    if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { +        pa_log("Failed to parse channel_map= argument."); +        goto fail; +    } + +    pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); + +    u->channels = ss.channels = (uint8_t) channels; +    ss.rate = jack_get_sample_rate(u->client); +    ss.format = PA_SAMPLE_FLOAT32NE; + +    pa_assert(pa_sample_spec_valid(&ss)); + +    for (i = 0; i < ss.channels; i++) { +        if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) { +            pa_log("jack_port_register() failed."); +            goto fail; +        } +    } + +    pa_sink_new_data_init(&data); +    data.driver = __FILE__; +    data.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_API, "jack"); +    if (server_name) +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); +    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client)); +    pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } + +    u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); +    pa_sink_new_data_done(&data); + +    if (!u->sink) { +        pa_log("Failed to create sink."); +        goto fail; +    } + +    u->sink->parent.process_msg = sink_process_msg; +    u->sink->userdata = u; + +    pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); +    pa_sink_set_rtpoll(u->sink, u->rtpoll); +    pa_sink_set_max_request(u->sink, jack_get_buffer_size(u->client) * pa_frame_size(&u->sink->sample_spec)); + +    jack_set_process_callback(u->client, jack_process, u); +    jack_on_shutdown(u->client, jack_shutdown, u); +    jack_set_thread_init_callback(u->client, jack_init, u); +    jack_set_buffer_size_callback(u->client, jack_buffer_size, u); + +    if (!(u->thread = pa_thread_new(thread_func, u))) { +        pa_log("Failed to create thread."); +        goto fail; +    } + +    if (jack_activate(u->client)) { +        pa_log("jack_activate() failed"); +        goto fail; +    } + +    if (do_connect) { +        for (i = 0, p = ports; i < ss.channels; i++, p++) { + +            if (!*p) { +                pa_log("Not enough physical output ports, leaving unconnected."); +                break; +            } + +            pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p); + +            if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) { +                pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); +                break; +            } +        } +    } + +    pa_sink_put(u->sink); + +    free(ports); +    pa_modargs_free(ma); + +    return 0; + +fail: +    if (ma) +        pa_modargs_free(ma); + +    free(ports); + +    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; + +    if (u->client) +        jack_client_close(u->client); + +    if (u->sink) +        pa_sink_unlink(u->sink); + +    if (u->thread) { +        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); +        pa_thread_free(u->thread); +    } + +    pa_thread_mq_done(&u->thread_mq); + +    if (u->sink) +        pa_sink_unref(u->sink); + +    if (u->rtpoll_item) +        pa_rtpoll_item_free(u->rtpoll_item); + +    if (u->jack_msgq) +        pa_asyncmsgq_unref(u->jack_msgq); + +    if (u->rtpoll) +        pa_rtpoll_free(u->rtpoll); + +    pa_xfree(u); +} diff --git a/src/modules/jack/module-jack-source.c b/src/modules/jack/module-jack-source.c new file mode 100644 index 00000000..a898e0e5 --- /dev/null +++ b/src/modules/jack/module-jack-source.c @@ -0,0 +1,454 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2006 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 <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> + +#include <jack/jack.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/source.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 "module-jack-source-symdef.h" + +/* See module-jack-sink for a few comments how this module basically + * works */ + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("JACK Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( +        "source_name=<name for the source> " +        "source_properties=<properties for the source> " +        "server_name=<jack server name> " +        "client_name=<jack client name> " +        "channels=<number of channels> " +        "channel_map=<channel map> " +        "connect=<connect ports?>"); + +#define DEFAULT_SOURCE_NAME "jack_in" + +struct userdata { +    pa_core *core; +    pa_module *module; +    pa_source *source; + +    unsigned channels; + +    jack_port_t* port[PA_CHANNELS_MAX]; +    jack_client_t *client; + +    pa_thread_mq thread_mq; +    pa_asyncmsgq *jack_msgq; +    pa_rtpoll *rtpoll; +    pa_rtpoll_item *rtpoll_item; + +    pa_thread *thread; + +    jack_nframes_t saved_frame_time; +    pa_bool_t saved_frame_time_valid; +}; + +static const char* const valid_modargs[] = { +    "source_name", +    "source_properties", +    "server_name", +    "client_name", +    "channels", +    "channel_map", +    "connect", +    NULL +}; + +enum { +    SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX, +    SOURCE_MESSAGE_ON_SHUTDOWN +}; + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { +    struct userdata *u = PA_SOURCE(o)->userdata; + +    switch (code) { + +        case SOURCE_MESSAGE_POST: + +            /* Handle the new block from the JACK thread */ +            pa_assert(chunk); +            pa_assert(chunk->length > 0); + +            if (u->source->thread_info.state == PA_SOURCE_RUNNING) +                pa_source_post(u->source, chunk); + +            u->saved_frame_time = (jack_nframes_t) offset; +            u->saved_frame_time_valid = TRUE; + +            return 0; + +        case SOURCE_MESSAGE_ON_SHUTDOWN: +            pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); +            return 0; + +        case PA_SOURCE_MESSAGE_GET_LATENCY: { +            jack_nframes_t l, ft, d; +            size_t n; + +            /* This is the "worst-case" latency */ +            l = jack_port_get_total_latency(u->client, u->port[0]); + +            if (u->saved_frame_time_valid) { +                /* Adjust the worst case latency by the time that +                 * passed since we last handed data to JACK */ + +                ft = jack_frame_time(u->client); +                d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; +                l += d; +            } + +            /* Convert it to usec */ +            n = l * pa_frame_size(&u->source->sample_spec); +            *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->source->sample_spec); + +            return 0; +        } +    } + +    return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int jack_process(jack_nframes_t nframes, void *arg) { +    unsigned c; +    struct userdata *u = arg; +    const void *buffer[PA_CHANNELS_MAX]; +    void *p; +    jack_nframes_t frame_time; +    pa_memchunk chunk; + +    pa_assert(u); + +    for (c = 0; c < u->channels; c++) +        pa_assert_se(buffer[c] = jack_port_get_buffer(u->port[c], nframes)); + +    /* We interleave the data and pass it on to the other RT thread */ + +    pa_memchunk_reset(&chunk); +    chunk.length = nframes * pa_frame_size(&u->source->sample_spec); +    chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); +    p = pa_memblock_acquire(chunk.memblock); +    pa_interleave(buffer, u->channels, p, sizeof(float), nframes); +    pa_memblock_release(chunk.memblock); + +    frame_time = jack_frame_time(u->client); + +    pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, NULL, frame_time, &chunk, NULL); + +    pa_memblock_unref(chunk.memblock); + +    return 0; +} + +static void thread_func(void *userdata) { +    struct userdata *u = userdata; + +    pa_assert(u); + +    pa_log_debug("Thread starting up"); + +    if (u->core->realtime_scheduling) +        pa_make_realtime(u->core->realtime_priority); + +    pa_thread_mq_install(&u->thread_mq); + +    for (;;) { +        int ret; + +        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) +            goto fail; + +        if (ret == 0) +            goto finish; +    } + +fail: +    /* If this was no regular exit from the loop we have to continue +     * processing messages until we received PA_MESSAGE_SHUTDOWN */ +    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); +    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: +    pa_log_debug("Thread shutting down"); +} + +static void jack_error_func(const char*t) { +    char *s; + +    s = pa_xstrndup(t, strcspn(t, "\n\r")); +    pa_log_warn("JACK error >%s<", s); +    pa_xfree(s); +} + +static void jack_init(void *arg) { +    struct userdata *u = arg; + +    pa_log_info("JACK thread starting up."); + +    if (u->core->realtime_scheduling) +        pa_make_realtime(u->core->realtime_priority+4); +} + +static void jack_shutdown(void* arg) { +    struct userdata *u = arg; + +    pa_log_info("JACK thread shutting down.."); +    pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); +} + +int pa__init(pa_module*m) { +    struct userdata *u = NULL; +    pa_sample_spec ss; +    pa_channel_map map; +    pa_modargs *ma = NULL; +    jack_status_t status; +    const char *server_name, *client_name; +    uint32_t channels = 0; +    pa_bool_t do_connect = TRUE; +    unsigned i; +    const char **ports = NULL, **p; +    pa_source_new_data data; + +    pa_assert(m); + +    jack_set_error_function(jack_error_func); + +    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, "connect", &do_connect) < 0) { +        pa_log("Failed to parse connect= argument."); +        goto fail; +    } + +    server_name = pa_modargs_get_value(ma, "server_name", NULL); +    client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Source"); + +    m->userdata = u = pa_xnew0(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->saved_frame_time_valid = FALSE; +    u->rtpoll = pa_rtpoll_new(); +    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + +    u->jack_msgq = pa_asyncmsgq_new(0); +    u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + +    if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { +        pa_log("jack_client_open() failed."); +        goto fail; +    } + +    ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); + +    channels = 0; +    for (p = ports; *p; p++) +        channels++; + +    if (!channels) +        channels = m->core->default_sample_spec.channels; + +    if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || +        channels <= 0 || +        channels >= PA_CHANNELS_MAX) { +        pa_log("failed to parse channels= argument."); +        goto fail; +    } + +    if (channels == m->core->default_channel_map.channels) +        map = m->core->default_channel_map; +    else +        pa_channel_map_init_extend(&map, channels, PA_CHANNEL_MAP_ALSA); + +    if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { +        pa_log("failed to parse channel_map= argument."); +        goto fail; +    } + +    pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); + +    u->channels = ss.channels = (uint8_t) channels; +    ss.rate = jack_get_sample_rate(u->client); +    ss.format = PA_SAMPLE_FLOAT32NE; + +    pa_assert(pa_sample_spec_valid(&ss)); + +    for (i = 0; i < ss.channels; i++) { +        if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput|JackPortIsTerminal, 0))) { +            pa_log("jack_port_register() failed."); +            goto fail; +        } +    } + +    pa_source_new_data_init(&data); +    data.driver = __FILE__; +    data.module = m; +    pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); +    pa_source_new_data_set_sample_spec(&data, &ss); +    pa_source_new_data_set_channel_map(&data, &map); +    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack"); +    if (server_name) +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); +    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack source (%s)", jack_get_client_name(u->client)); +    pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + +    if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_source_new_data_done(&data); +        goto fail; +    } + +    u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); +    pa_source_new_data_done(&data); + +    if (!u->source) { +        pa_log("Failed to create source."); +        goto fail; +    } + +    u->source->parent.process_msg = source_process_msg; +    u->source->userdata = u; + +    pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); +    pa_source_set_rtpoll(u->source, u->rtpoll); + +    jack_set_process_callback(u->client, jack_process, u); +    jack_on_shutdown(u->client, jack_shutdown, u); +    jack_set_thread_init_callback(u->client, jack_init, u); + +    if (!(u->thread = pa_thread_new(thread_func, u))) { +        pa_log("Failed to create thread."); +        goto fail; +    } + +    if (jack_activate(u->client)) { +        pa_log("jack_activate() failed"); +        goto fail; +    } + +    if (do_connect) { +        for (i = 0, p = ports; i < ss.channels; i++, p++) { + +            if (!*p) { +                pa_log("Not enough physical output ports, leaving unconnected."); +                break; +            } + +            pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p); + +            if (jack_connect(u->client, *p, jack_port_name(u->port[i]))) { +                pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); +                break; +            } +        } + +    } + +    pa_source_put(u->source); + +    free(ports); +    pa_modargs_free(ma); + +    return 0; + +fail: +    if (ma) +        pa_modargs_free(ma); + +    free(ports); + +    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_source_linked_by(u->source); +} + +void pa__done(pa_module*m) { +    struct userdata *u; +    pa_assert(m); + +    if (!(u = m->userdata)) +        return; + +    if (u->client) +        jack_client_close(u->client); + +    if (u->source) +        pa_source_unlink(u->source); + +    if (u->thread) { +        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); +        pa_thread_free(u->thread); +    } + +    pa_thread_mq_done(&u->thread_mq); + +    if (u->source) +        pa_source_unref(u->source); + +    if (u->rtpoll_item) +        pa_rtpoll_item_free(u->rtpoll_item); + +    if (u->jack_msgq) +        pa_asyncmsgq_unref(u->jack_msgq); + +    if (u->rtpoll) +        pa_rtpoll_free(u->rtpoll); + +    pa_xfree(u); +} diff --git a/src/modules/module-augment-properties.c b/src/modules/module-augment-properties.c index c3e5997a..15aa3a1e 100644 --- a/src/modules/module-augment-properties.c +++ b/src/modules/module-augment-properties.c @@ -58,6 +58,7 @@ struct rule {      char *process_name;      char *application_name;      char *icon_name; +    char *role;      pa_proplist *proplist;  }; @@ -72,12 +73,21 @@ static void rule_free(struct rule *r) {      pa_xfree(r->process_name);      pa_xfree(r->application_name);      pa_xfree(r->icon_name); +    pa_xfree(r->role);      if (r->proplist)          pa_proplist_free(r->proplist);      pa_xfree(r);  } -static int parse_properties(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_properties( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { +      struct rule *r = userdata;      pa_proplist *n; @@ -93,11 +103,56 @@ static int parse_properties(const char *filename, unsigned line, const char *sec      return 0;  } -static int check_type(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_categories( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { + +    struct rule *r = userdata; +    const char *state = NULL; +    char *c; + +    while ((c = pa_split(rvalue, ";", &state))) { + +        if (pa_streq(c, "Game")) { +            pa_xfree(r->role); +            r->role = pa_xstrdup("game"); +        } else if (pa_streq(c, "Telephony")) { +            pa_xfree(r->role); +            r->role = pa_xstrdup("phone"); +        } + +        pa_xfree(c); +    } + +    return 0; +} + +static int check_type( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { +      return pa_streq(rvalue, "Application") ? 0 : -1;  } -static int catch_all(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int catch_all( +        const char *filename, +        unsigned line, +        const char *section, +        const char *lvalue, +        const char *rvalue, +        void *data, +        void *userdata) { +      return 0;  } @@ -109,6 +164,7 @@ static void update_rule(struct rule *r) {          { "Icon", pa_config_parse_string,              NULL, "Desktop Entry" },          { "Type", check_type,                          NULL, "Desktop Entry" },          { "X-PulseAudio-Properties", parse_properties, NULL, "Desktop Entry" }, +        { "Categories", parse_categories,              NULL, "Desktop Entry" },          { NULL,  catch_all, NULL, NULL },          { NULL, NULL, NULL, NULL },      }; @@ -131,7 +187,8 @@ static void update_rule(struct rule *r) {      r->mtime = st.st_mtime;      pa_xfree(r->application_name);      pa_xfree(r->icon_name); -    r->application_name = r->icon_name = NULL; +    pa_xfree(r->role); +    r->application_name = r->icon_name = r->role = NULL;      if (r->proplist)          pa_proplist_clear(r->proplist); @@ -151,6 +208,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) {      if (!r->good)          return; +    if (r->proplist) +        pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); +      if (r->icon_name)          if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME))              pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, r->icon_name); @@ -164,8 +224,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) {              pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, r->application_name);      } -    if (r->proplist) -        pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); +    if (r->role) +        if (!pa_proplist_contains(p, PA_PROP_MEDIA_ROLE)) +            pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, r->role);  }  static void make_room(pa_hashmap *cache) { diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index 0afb9353..7dea94f7 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -30,12 +30,12 @@  #include <stdio.h>  #include <stdlib.h>  #include <ctype.h> -#include <gdbm.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> @@ -45,6 +45,7 @@  #include <pulsecore/core-subscribe.h>  #include <pulsecore/card.h>  #include <pulsecore/namereg.h> +#include <pulsecore/database.h>  #include "module-card-restore-symdef.h" @@ -53,7 +54,7 @@ PA_MODULE_DESCRIPTION("Automatically restore profile of cards");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(TRUE); -#define SAVE_INTERVAL 10 +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)  static const char* const valid_modargs[] = {      NULL @@ -65,7 +66,7 @@ struct userdata {      pa_subscription *subscription;      pa_hook_slot *card_new_hook_slot;      pa_time_event *save_time_event; -    GDBM_FILE gdbm_file; +    pa_database *database;  };  #define ENTRY_VERSION 1 @@ -75,43 +76,42 @@ struct entry {      char profile[PA_NAME_MAX];  } PA_GCC_PACKED ; -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +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(tv);      pa_assert(u);      pa_assert(e == u->save_time_event);      u->core->mainloop->time_free(u->save_time_event);      u->save_time_event = NULL; -    gdbm_sync(u->gdbm_file); +    pa_database_sync(u->database);      pa_log_info("Synced.");  }  static struct entry* read_entry(struct userdata *u, const char *name) { -    datum key, data; +    pa_datum key, data;      struct entry *e;      pa_assert(u);      pa_assert(name); -    key.dptr = (char*) name; -    key.dsize = (int) strlen(name); +    key.data = (char*) name; +    key.size = strlen(name); -    data = gdbm_fetch(u->gdbm_file, key); +    pa_zero(data); -    if (!data.dptr) +    if (!pa_database_get(u->database, &key, &data))          goto fail; -    if (data.dsize != sizeof(struct entry)) { -        pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); +    if (data.size != sizeof(struct entry)) { +        pa_log_debug("Database contains entry for card %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.dptr; +    e = (struct entry*) data.data;      if (e->version != ENTRY_VERSION) {          pa_log_debug("Version of database entry for card %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -127,25 +127,21 @@ static struct entry* read_entry(struct userdata *u, const char *name) {  fail: -    pa_xfree(data.dptr); +    pa_datum_free(&data);      return NULL;  }  static void trigger_save(struct userdata *u) { -    struct timeval tv; -      if (u->save_time_event)          return; -    pa_gettimeofday(&tv); -    tv.tv_sec += SAVE_INTERVAL; -    u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); +    u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);  }  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; -    datum key, data; +    pa_datum key, data;      pa_card *card;      pa_assert(c); @@ -155,12 +151,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          t != (PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE))          return; -    memset(&entry, 0, sizeof(entry)); +    pa_zero(entry);      entry.version = ENTRY_VERSION;      if (!(card = pa_idxset_get_by_index(c->cards, idx)))          return; +    if (!card->save_profile) +        return; +      pa_strlcpy(entry.profile, card->active_profile ? card->active_profile->name : "", sizeof(entry.profile));      if ((old = read_entry(u, card->name))) { @@ -173,15 +172,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          pa_xfree(old);      } -    key.dptr = card->name; -    key.dsize = (int) strlen(card->name); +    key.data = card->name; +    key.size = strlen(card->name); -    data.dptr = (void*) &entry; -    data.dsize = sizeof(entry); +    data.data = &entry; +    data.size = sizeof(entry);      pa_log_info("Storing profile for card %s.", card->name); -    gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); +    pa_database_set(u->database, &key, &data, TRUE);      trigger_save(u);  } @@ -194,8 +193,9 @@ static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new      if ((e = read_entry(u, new_data->name)) && e->profile[0]) {          if (!new_data->active_profile) { -            pa_card_new_data_set_profile(new_data, e->profile);              pa_log_info("Restoring profile for card %s.", new_data->name); +            pa_card_new_data_set_profile(new_data, e->profile); +            new_data->save_profile = TRUE;          } else              pa_log_debug("Not restoring profile for card %s, because already set.", new_data->name); @@ -208,10 +208,9 @@ static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new  int pa__init(pa_module*m) {      pa_modargs *ma = NULL;      struct userdata *u; -    char *fname, *fn; +    char *fname;      pa_card *card;      uint32_t idx; -    int gdbm_cache_size;      pa_assert(m); @@ -220,37 +219,23 @@ int pa__init(pa_module*m) {          goto fail;      } -    m->userdata = u = pa_xnew(struct userdata, 1); +    m->userdata = u = pa_xnew0(struct userdata, 1);      u->core = m->core;      u->module = m; -    u->save_time_event = NULL; -    u->gdbm_file = NULL;      u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CARD, subscribe_callback, u);      u->card_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) card_new_hook_callback, u); -    /* We include the host identifier in the file name because gdbm -     * files are CPU dependant, and we don't want things to go wrong -     * if we are on a multiarch system. */ - -    fn = pa_sprintf_malloc("card-database."CANONICAL_HOST".gdbm"); -    fname = pa_state_path(fn, TRUE); -    pa_xfree(fn); - -    if (!fname) +    if (!(fname = pa_state_path("card-database", TRUE)))          goto fail; -    if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { -        pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); +    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;      } -    /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ -    gdbm_cache_size = 10; -    gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); -      pa_log_info("Sucessfully opened database file '%s'.", fname);      pa_xfree(fname); @@ -286,8 +271,8 @@ void pa__done(pa_module*m) {      if (u->save_time_event)          u->core->mainloop->time_free(u->save_time_event); -    if (u->gdbm_file) -        gdbm_close(u->gdbm_file); +    if (u->database) +        pa_database_close(u->database);      pa_xfree(u);  } diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 43ad9680..8443a5a3 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -26,6 +26,7 @@  #include <stdio.h>  #include <errno.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h> @@ -36,6 +37,7 @@  #include <pulsecore/sink-input.h>  #include <pulsecore/memblockq.h>  #include <pulsecore/log.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/modargs.h>  #include <pulsecore/namereg.h> @@ -43,7 +45,6 @@  #include <pulsecore/thread.h>  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h>  #include <pulsecore/core-error.h>  #include <pulsecore/time-smoother.h> @@ -55,12 +56,13 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> "          "slaves=<slave sinks> "          "adjust_time=<seconds> "          "resample_method=<method> "          "format=<sample format> " -        "channels=<number of channels> "          "rate=<sample rate> " +        "channels=<number of channels> "          "channel_map=<channel map>");  #define DEFAULT_SINK_NAME "combined" @@ -69,16 +71,17 @@ PA_MODULE_USAGE(  #define DEFAULT_ADJUST_TIME 10 -#define REQUEST_LATENCY_USEC (PA_USEC_PER_MSEC * 200) +#define BLOCK_USEC (PA_USEC_PER_MSEC * 200)  static const char* const valid_modargs[] = {      "sink_name", +    "sink_properties",      "slaves",      "adjust_time",      "resample_method",      "format", -    "channels",      "rate", +    "channels",      "channel_map",      NULL  }; @@ -116,6 +119,7 @@ struct userdata {      uint32_t adjust_time;      pa_bool_t automatic; +    pa_bool_t auto_desc;      pa_hook_slot *sink_put_slot, *sink_unlink_slot, *sink_state_changed_slot; @@ -222,9 +226,8 @@ static void adjust_rates(struct userdata *u) {      pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, NULL, (int64_t) avg_total_latency, NULL);  } -static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {      struct userdata *u = userdata; -    struct timeval n;      pa_assert(u);      pa_assert(a); @@ -232,9 +235,7 @@ static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct time      adjust_rates(u); -    pa_gettimeofday(&n); -    n.tv_sec += (time_t) u->adjust_time; -    u->sink->core->mainloop->time_restart(e, &n); +    pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC);  }  static void process_render_null(struct userdata *u, pa_usec_t now) { @@ -278,9 +279,8 @@ static void thread_func(void *userdata) {          pa_make_realtime(u->core->realtime_priority+1);      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll); -    u->thread_info.timestamp = pa_rtclock_usec(); +    u->thread_info.timestamp = pa_rtclock_now();      u->thread_info.in_null_mode = FALSE;      for (;;) { @@ -294,7 +294,7 @@ static void thread_func(void *userdata) {          if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && !u->thread_info.active_outputs) {              pa_usec_t now; -            now = pa_rtclock_usec(); +            now = pa_rtclock_now();              if (!u->thread_info.in_null_mode || u->thread_info.timestamp <= now)                  process_render_null(u, now); @@ -591,7 +591,7 @@ static void unsuspend(struct userdata *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_sink_suspend(o->sink, FALSE, PA_SUSPEND_IDLE);          if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))              enable_output(o); @@ -649,7 +649,7 @@ static void update_max_request(struct userdata *u) {      if (max_request <= 0)          max_request = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); -    pa_sink_set_max_request(u->sink, max_request); +    pa_sink_set_max_request_within_thread(u->sink, max_request);  }  /* Called from thread context of the io thread */ @@ -662,16 +662,16 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse              pa_atomic_store(&u->thread_info.running, PA_PTR_TO_UINT(data) == PA_SINK_RUNNING);              if (PA_PTR_TO_UINT(data) == PA_SINK_SUSPENDED) -                pa_smoother_pause(u->thread_info.smoother, pa_rtclock_usec()); +                pa_smoother_pause(u->thread_info.smoother, pa_rtclock_now());              else -                pa_smoother_resume(u->thread_info.smoother, pa_rtclock_usec()); +                pa_smoother_resume(u->thread_info.smoother, pa_rtclock_now(), TRUE);              break;          case PA_SINK_MESSAGE_GET_LATENCY: {              pa_usec_t x, y, c, *delay = data; -            x = pa_rtclock_usec(); +            x = pa_rtclock_now();              y = pa_smoother_get(u->thread_info.smoother, x);              c = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); @@ -728,7 +728,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse          case SINK_MESSAGE_UPDATE_LATENCY: {              pa_usec_t x, y, latency = (pa_usec_t) offset; -            x = pa_rtclock_usec(); +            x = pa_rtclock_now();              y = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec);              if (y > latency) @@ -757,6 +757,9 @@ static void update_description(struct userdata *u) {      pa_assert(u); +    if (!u->auto_desc) +        return; +      if (pa_idxset_isempty(u->outputs)) {          pa_sink_set_description(u->sink, "Simultaneous output");          return; @@ -817,7 +820,7 @@ static int output_create_sink_input(struct output *o) {      o->sink_input->kill = sink_input_kill_cb;      o->sink_input->userdata = o; -    pa_sink_input_set_requested_latency(o->sink_input, REQUEST_LATENCY_USEC); +    pa_sink_input_set_requested_latency(o->sink_input, BLOCK_USEC);      return 0;  } @@ -871,7 +874,7 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) {      }      if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { -        pa_sink_suspend(sink, FALSE); +        pa_sink_suspend(sink, FALSE, PA_SUSPEND_IDLE);          if (PA_SINK_IS_OPENED(pa_sink_get_state(sink)))              if (output_create_sink_input(o) < 0) @@ -1043,7 +1046,14 @@ int pa__init(pa_module*m) {      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, TRUE, 10); +    u->thread_info.smoother = pa_smoother_new( +            PA_USEC_PER_SEC, +            PA_USEC_PER_SEC*2, +            TRUE, +            TRUE, +            10, +            0, +            FALSE);      if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {          pa_log("Failed to parse adjust_time value"); @@ -1067,12 +1077,25 @@ 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, "Simultaneous Output");      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "filter");      if (slaves)          pa_proplist_sets(data.proplist, "combine.slaves", slaves); +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } + +    /* Check proplist for a description & fill in a default value if not */ +    u->auto_desc = FALSE; +    if (NULL == pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION)) { +        u->auto_desc = TRUE; +        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); @@ -1088,11 +1111,8 @@ int pa__init(pa_module*m) {      pa_sink_set_rtpoll(u->sink, u->rtpoll);      pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); -    pa_sink_set_latency_range(u->sink, REQUEST_LATENCY_USEC, REQUEST_LATENCY_USEC); -    u->block_usec = u->sink->thread_info.max_latency; - -    u->sink->thread_info.max_request = -        pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); +    u->block_usec = BLOCK_USEC; +    pa_sink_set_max_request(u->sink, pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec));      if (!u->automatic) {          const char*split_state; @@ -1158,12 +1178,8 @@ int pa__init(pa_module*m) {          if (o->sink_input)              pa_sink_input_put(o->sink_input); -    if (u->adjust_time > 0) { -        struct timeval tv; -        pa_gettimeofday(&tv); -        tv.tv_sec += (time_t) u->adjust_time; -        u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u); -    } +    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);      pa_modargs_free(ma); diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c index 3fba7ef6..a666073c 100644 --- a/src/modules/module-console-kit.c +++ b/src/modules/module-console-kit.c @@ -44,8 +44,8 @@  #include <pulsecore/namereg.h>  #include <pulsecore/core-scache.h>  #include <pulsecore/modargs.h> +#include <pulsecore/dbus-shared.h> -#include "dbus-util.h"  #include "module-console-kit-symdef.h"  PA_MODULE_AUTHOR("Lennart Poettering"); diff --git a/src/modules/module-cork-music-on-phone.c b/src/modules/module-cork-music-on-phone.c index c0f5eea4..d3a2b00e 100644 --- a/src/modules/module-cork-music-on-phone.c +++ b/src/modules/module-cork-music-on-phone.c @@ -143,7 +143,6 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc  }  static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { -    pa_core_assert_ref(core);      pa_sink_input_assert_ref(i);      return process(u, i, FALSE); diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c index a25aafcb..27ae60e5 100644 --- a/src/modules/module-default-device-restore.c +++ b/src/modules/module-default-device-restore.c @@ -26,6 +26,7 @@  #include <errno.h>  #include <stdio.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/util.h> @@ -42,7 +43,7 @@ PA_MODULE_DESCRIPTION("Automatically restore the default sink and source");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(TRUE); -#define DEFAULT_SAVE_INTERVAL 5 +#define SAVE_INTERVAL (5 * PA_USEC_PER_SEC)  struct userdata {      pa_core *core; @@ -63,7 +64,7 @@ static void load(struct userdata *u) {          char ln[256] = "";          pa_sink *s; -        fgets(ln, sizeof(ln)-1, f); +        (void) fgets(ln, sizeof(ln)-1, f);          pa_strip_nl(ln);          fclose(f); @@ -84,7 +85,7 @@ static void load(struct userdata *u) {          char ln[256] = "";          pa_source *s; -        fgets(ln, sizeof(ln)-1, f); +        (void) fgets(ln, sizeof(ln)-1, f);          pa_strip_nl(ln);          fclose(f); @@ -127,7 +128,7 @@ static void save(struct userdata *u) {      u->modified = FALSE;  } -static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {      struct userdata *u = userdata;      pa_assert(u); @@ -146,12 +147,8 @@ static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t id      u->modified = TRUE; -    if (!u->time_event) { -        struct timeval tv; -        pa_gettimeofday(&tv); -        pa_timeval_add(&tv, DEFAULT_SAVE_INTERVAL*PA_USEC_PER_SEC); -        u->time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, time_cb, u); -    } +    if (!u->time_event) +        u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, time_cb, u);  }  int pa__init(pa_module *m) { diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 index f9924cfa..b6a60b6a 100644 --- a/src/modules/module-defs.h.m4 +++ b/src/modules/module-defs.h.m4 @@ -17,6 +17,7 @@ gen_symbol(pa__get_author)  gen_symbol(pa__get_description)  gen_symbol(pa__get_usage)  gen_symbol(pa__get_version) +gen_symbol(pa__get_deprecated)  gen_symbol(pa__load_once)  gen_symbol(pa__get_n_used) @@ -28,6 +29,7 @@ const char* pa__get_author(void);  const char* pa__get_description(void);  const char* pa__get_usage(void);  const char* pa__get_version(void); +const char* pa__get_deprecated(void);  pa_bool_t pa__load_once(void);  #endif diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 49127abc..18479df3 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -50,6 +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!");  static const char* const valid_modargs[] = {      "just-one", diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index 0ca3dd83..120b762c 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -30,12 +30,12 @@  #include <stdio.h>  #include <stdlib.h>  #include <ctype.h> -#include <gdbm.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> @@ -46,6 +46,7 @@  #include <pulsecore/sink-input.h>  #include <pulsecore/source-output.h>  #include <pulsecore/namereg.h> +#include <pulsecore/database.h>  #include "module-device-restore-symdef.h" @@ -54,14 +55,16 @@ PA_MODULE_DESCRIPTION("Automatically restore the volume/mute state of devices");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(TRUE);  PA_MODULE_USAGE( +        "restore_port=<Save/restore port?> "          "restore_volume=<Save/restore volumes?> "          "restore_muted=<Save/restore muted states?>"); -#define SAVE_INTERVAL 10 +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)  static const char* const valid_modargs[] = {      "restore_volume",      "restore_muted", +    "restore_port",      NULL  }; @@ -70,78 +73,82 @@ struct userdata {      pa_module *module;      pa_subscription *subscription;      pa_hook_slot +        *sink_new_hook_slot,          *sink_fixate_hook_slot, +        *source_new_hook_slot,          *source_fixate_hook_slot;      pa_time_event *save_time_event; -    GDBM_FILE gdbm_file; +    pa_database *database;      pa_bool_t restore_volume:1;      pa_bool_t restore_muted:1; +    pa_bool_t restore_port:1;  }; -#define ENTRY_VERSION 1 +#define ENTRY_VERSION 2  struct entry {      uint8_t version; +    pa_bool_t muted_valid:1, volume_valid:1, port_valid:1;      pa_bool_t muted:1;      pa_channel_map channel_map;      pa_cvolume volume; +    char port[PA_NAME_MAX];  } PA_GCC_PACKED; -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +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(tv);      pa_assert(u);      pa_assert(e == u->save_time_event);      u->core->mainloop->time_free(u->save_time_event);      u->save_time_event = NULL; -    gdbm_sync(u->gdbm_file); +    pa_database_sync(u->database);      pa_log_info("Synced.");  }  static struct entry* read_entry(struct userdata *u, const char *name) { -    datum key, data; +    pa_datum key, data;      struct entry *e;      pa_assert(u);      pa_assert(name); -    key.dptr = (char*) name; -    key.dsize = (int) strlen(name); +    key.data = (char*) name; +    key.size = strlen(name); -    data = gdbm_fetch(u->gdbm_file, key); +    pa_zero(data); -    if (!data.dptr) +    if (!pa_database_get(u->database, &key, &data))          goto fail; -    if (data.dsize != 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.dsize, (unsigned long) sizeof(struct entry)); +    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.dptr; +    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 (!(pa_cvolume_valid(&e->volume))) { -        pa_log_warn("Invalid volume stored in database for device %s", name); +    if (!memchr(e->port, 0, sizeof(e->port))) { +        pa_log_warn("Database contains entry for device %s with missing NUL byte in port name", name);          goto fail;      } -    if (!(pa_channel_map_valid(&e->channel_map))) { +    if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {          pa_log_warn("Invalid channel map stored in database for device %s", name);          goto fail;      } -    if (e->volume.channels != e->channel_map.channels) { +    if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) {          pa_log_warn("Volume and channel map don't match in database entry for device %s", name);          goto fail;      } @@ -150,26 +157,41 @@ static struct entry* read_entry(struct userdata *u, const char *name) {  fail: -    pa_xfree(data.dptr); +    pa_datum_free(&data);      return NULL;  }  static void trigger_save(struct userdata *u) { -    struct timeval tv; -      if (u->save_time_event)          return; -    pa_gettimeofday(&tv); -    tv.tv_sec += SAVE_INTERVAL; -    u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); +    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_cvolume t; + +    if (a->port_valid != b->port_valid || +        (a->port_valid && strncmp(a->port, b->port, sizeof(a->port)))) +        return FALSE; + +    if (a->muted_valid != b->muted_valid || +        (a->muted_valid && (a->muted != b->muted))) +        return FALSE; + +    t = b->volume; +    if (a->volume_valid != b->volume_valid || +        (a->volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->volume))) +        return FALSE; + +    return TRUE;  }  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; -    datum key, data; +    pa_datum key, data;      pa_assert(c);      pa_assert(u); @@ -180,7 +202,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE))          return; -    memset(&entry, 0, sizeof(entry)); +    pa_zero(entry);      entry.version = ENTRY_VERSION;      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { @@ -190,9 +212,25 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3              return;          name = pa_sprintf_malloc("sink:%s", sink->name); -        entry.channel_map = sink->channel_map; -        entry.volume = *pa_sink_get_volume(sink, FALSE); -        entry.muted = pa_sink_get_mute(sink, FALSE); + +        if ((old = read_entry(u, name))) +            entry = *old; + +        if (sink->save_volume) { +            entry.channel_map = sink->channel_map; +            entry.volume = *pa_sink_get_volume(sink, FALSE, TRUE); +            entry.volume_valid = TRUE; +        } + +        if (sink->save_muted) { +            entry.muted = pa_sink_get_mute(sink, FALSE); +            entry.muted_valid = TRUE; +        } + +        if (sink->save_port) { +            pa_strlcpy(entry.port, sink->active_port ? sink->active_port->name : "", sizeof(entry.port)); +            entry.port_valid = TRUE; +        }      } else {          pa_source *source; @@ -203,16 +241,30 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3              return;          name = pa_sprintf_malloc("source:%s", source->name); -        entry.channel_map = source->channel_map; -        entry.volume = *pa_source_get_volume(source, FALSE); -        entry.muted = pa_source_get_mute(source, FALSE); -    } -    if ((old = read_entry(u, name))) { +        if ((old = read_entry(u, name))) +            entry = *old; + +        if (source->save_volume) { +            entry.channel_map = source->channel_map; +            entry.volume = *pa_source_get_volume(source, FALSE); +            entry.volume_valid = TRUE; +        } + +        if (source->save_muted) { +            entry.muted = pa_source_get_mute(source, FALSE); +            entry.muted_valid = TRUE; +        } + +        if (source->save_port) { +            pa_strlcpy(entry.port, source->active_port ? source->active_port->name : "", sizeof(entry.port)); +            entry.port_valid = TRUE; +        } +    } -        if (pa_cvolume_equal(pa_cvolume_remap(&old->volume, &old->channel_map, &entry.channel_map), &entry.volume) && -            !old->muted == !entry.muted) { +    if (old) { +        if (entries_equal(old, &entry)) {              pa_xfree(old);              pa_xfree(name);              return; @@ -221,46 +273,86 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          pa_xfree(old);      } -    key.dptr = name; -    key.dsize = (int) strlen(name); +    key.data = name; +    key.size = strlen(name); -    data.dptr = (void*) &entry; -    data.dsize = sizeof(entry); +    data.data = &entry; +    data.size = sizeof(entry); -    pa_log_info("Storing volume/mute for device %s.", name); +    pa_log_info("Storing volume/mute/port for device %s.", name); -    gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); +    pa_database_set(u->database, &key, &data, TRUE);      pa_xfree(name);      trigger_save(u);  } +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); +    pa_assert(u->restore_port); + +    name = pa_sprintf_malloc("sink:%s", new_data->name); + +    if ((e = read_entry(u, name))) { + +        if (e->port_valid) { +            if (!new_data->active_port) { +                pa_log_info("Restoring port for sink %s.", name); +                pa_sink_new_data_set_port(new_data, e->port); +                new_data->save_port = TRUE; +            } else +                pa_log_debug("Not restoring port for sink %s, because already set.", name); +        } + +        pa_xfree(e); +    } + +    pa_xfree(name); + +    return PA_HOOK_OK; +} +  static pa_hook_result_t sink_fixate_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); +    pa_assert(u->restore_volume || u->restore_muted);      name = pa_sprintf_malloc("sink:%s", new_data->name);      if ((e = read_entry(u, name))) { -        if (u->restore_volume) { +        if (u->restore_volume && e->volume_valid) {              if (!new_data->volume_is_set) { +                pa_cvolume v; +                  pa_log_info("Restoring volume for sink %s.", new_data->name); -                pa_sink_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map)); + +                v = e->volume; +                pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); +                pa_sink_new_data_set_volume(new_data, &v); + +                new_data->save_volume = TRUE;              } else                  pa_log_debug("Not restoring volume for sink %s, because already set.", new_data->name); -          } -        if (u->restore_muted) { +        if (u->restore_muted && e->muted_valid) {              if (!new_data->muted_is_set) {                  pa_log_info("Restoring mute state for sink %s.", new_data->name);                  pa_sink_new_data_set_muted(new_data, e->muted); +                new_data->save_muted = TRUE;              } else                  pa_log_debug("Not restoring mute state for sink %s, because already set.", new_data->name);          } @@ -273,30 +365,71 @@ static pa_hook_result_t sink_fixate_hook_callback(pa_core *c, pa_sink_new_data *      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); +    pa_assert(u->restore_port); + +    name = pa_sprintf_malloc("source:%s", new_data->name); + +    if ((e = read_entry(u, name))) { + +        if (e->port_valid) { +            if (!new_data->active_port) { +                pa_log_info("Restoring port for source %s.", name); +                pa_source_new_data_set_port(new_data, e->port); +                new_data->save_port = TRUE; +            } else +                pa_log_debug("Not restoring port for source %s, because already set.", name); +        } + +        pa_xfree(e); +    } + +    pa_xfree(name); + +    return PA_HOOK_OK; +} +  static pa_hook_result_t source_fixate_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); +    pa_assert(u->restore_volume || u->restore_muted);      name = pa_sprintf_malloc("source:%s", new_data->name);      if ((e = read_entry(u, name))) { -        if (u->restore_volume) { +        if (u->restore_volume && e->volume_valid) {              if (!new_data->volume_is_set) { +                pa_cvolume v; +                  pa_log_info("Restoring volume for source %s.", new_data->name); -                pa_source_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map)); + +                v = e->volume; +                pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); +                pa_source_new_data_set_volume(new_data, &v); + +                new_data->save_volume = TRUE;              } else                  pa_log_debug("Not restoring volume for source %s, because already set.", new_data->name);          } -        if (u->restore_muted) { +        if (u->restore_muted && e->muted_valid) {              if (!new_data->muted_is_set) {                  pa_log_info("Restoring mute state for source %s.", new_data->name);                  pa_source_new_data_set_muted(new_data, e->muted); +                new_data->save_muted = TRUE;              } else                  pa_log_debug("Not restoring mute state for source %s, because already set.", new_data->name);          } @@ -312,12 +445,11 @@ static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_da  int pa__init(pa_module*m) {      pa_modargs *ma = NULL;      struct userdata *u; -    char *fname, *fn; +    char *fname;      pa_sink *sink;      pa_source *source;      uint32_t idx; -    pa_bool_t restore_volume = TRUE, restore_muted = TRUE; -    int gdbm_cache_size; +    pa_bool_t restore_volume = TRUE, restore_muted = TRUE, restore_port = TRUE;      pa_assert(m); @@ -327,50 +459,43 @@ int pa__init(pa_module*m) {      }      if (pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0 || -        pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0) { -        pa_log("restore_volume= and restore_muted= expect boolean arguments"); +        pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0 || +        pa_modargs_get_value_boolean(ma, "restore_port", &restore_port) < 0) { +        pa_log("restore_port=, restore_volume= and restore_muted= expect boolean arguments");          goto fail;      } -    if (!restore_muted && !restore_volume) -        pa_log_warn("Neither restoring volume nor restoring muted enabled!"); +    if (!restore_muted && !restore_volume && !restore_port) +        pa_log_warn("Neither restoring volume, nor restoring muted, nor restoring port enabled!"); -    m->userdata = u = pa_xnew(struct userdata, 1); +    m->userdata = u = pa_xnew0(struct userdata, 1);      u->core = m->core;      u->module = m; -    u->save_time_event = NULL;      u->restore_volume = restore_volume;      u->restore_muted = restore_muted; -    u->gdbm_file = NULL; +    u->restore_port = restore_port;      u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); +    if (restore_port) { +        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); +    } +      if (restore_muted || restore_volume) {          u->sink_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_fixate_hook_callback, u);          u->source_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_fixate_hook_callback, u);      } -    /* We include the host identifier in the file name because gdbm -     * files are CPU dependant, and we don't want things to go wrong -     * if we are on a multiarch system. */ - -    fn = pa_sprintf_malloc("device-volumes."CANONICAL_HOST".gdbm"); -    fname = pa_state_path(fn, TRUE); -    pa_xfree(fn); - -    if (!fname) +    if (!(fname = pa_state_path("device-volumes", TRUE)))          goto fail; -    if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { -        pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); +    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;      } -    /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ -    gdbm_cache_size = 10; -    gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); -      pa_log_info("Sucessfully opened database file '%s'.", fname);      pa_xfree(fname); @@ -407,12 +532,16 @@ void pa__done(pa_module*m) {          pa_hook_slot_free(u->sink_fixate_hook_slot);      if (u->source_fixate_hook_slot)          pa_hook_slot_free(u->source_fixate_hook_slot); +    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->save_time_event)          u->core->mainloop->time_free(u->save_time_event); -    if (u->gdbm_file) -        gdbm_close(u->gdbm_file); +    if (u->database) +        pa_database_close(u->database);      pa_xfree(u);  } diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index 2b45e302..d7c678ca 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -41,13 +41,15 @@  #include <linux/sockios.h>  #endif -#include <pulse/xmalloc.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h> +#include <pulse/xmalloc.h>  #include <pulsecore/core-error.h>  #include <pulsecore/iochannel.h>  #include <pulsecore/sink.h>  #include <pulsecore/module.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/modargs.h>  #include <pulsecore/log.h> @@ -57,7 +59,6 @@  #include <pulsecore/thread-mq.h>  #include <pulsecore/thread.h>  #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h>  #include <pulsecore/socket-util.h>  #include "module-esound-sink-symdef.h" @@ -68,10 +69,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> "          "server=<address> cookie=<filename>  "          "format=<sample format> " -        "channels=<number of channels> " -        "rate=<sample rate>"); +        "rate=<sample rate> " +        "channels=<number of channels>");  #define DEFAULT_SINK_NAME "esound_out" @@ -118,12 +120,13 @@ struct userdata {  };  static const char* const valid_modargs[] = { +    "sink_name", +    "sink_properties",      "server",      "cookie", -    "rate",      "format", +    "rate",      "channels", -    "sink_name",      NULL  }; @@ -143,14 +146,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse                  case PA_SINK_SUSPENDED:                      pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); -                    pa_smoother_pause(u->smoother, pa_rtclock_usec()); +                    pa_smoother_pause(u->smoother, pa_rtclock_now());                      break;                  case PA_SINK_IDLE:                  case PA_SINK_RUNNING:                      if (u->sink->thread_info.state == PA_SINK_SUSPENDED) -                        pa_smoother_resume(u->smoother, pa_rtclock_usec()); +                        pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);                      break; @@ -165,7 +168,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse          case PA_SINK_MESSAGE_GET_LATENCY: {              pa_usec_t w, r; -            r = pa_smoother_get(u->smoother, pa_rtclock_usec()); +            r = pa_smoother_get(u->smoother, pa_rtclock_now());              w = pa_bytes_to_usec((uint64_t) u->offset + u->memchunk.length, &u->sink->sample_spec);              *((pa_usec_t*) data) = w > r ? w - r : 0; @@ -198,9 +201,8 @@ static void thread_func(void *userdata) {      pa_log_debug("Thread starting up");      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll); -    pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); +    pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());      for (;;) {          int ret; @@ -293,7 +295,7 @@ static void thread_func(void *userdata) {                  else                      usec = 0; -                pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); +                pa_smoother_put(u->smoother, pa_rtclock_now(), usec);              }              /* Hmm, nothing to do. Let's sleep */ @@ -354,6 +356,9 @@ static int do_write(struct userdata *u) {      }      if (!u->write_data && u->state == STATE_PREPARE) { +        int so_sndbuf = 0; +        socklen_t sl = sizeof(int); +          /* OK, we're done with sending all control data we need to, so           * let's hand the socket over to the IO thread now */ @@ -366,6 +371,13 @@ static int do_write(struct userdata *u) {          pa_make_tcp_socket_low_delay(u->fd); +        if (getsockopt(u->fd, SOL_SOCKET, SO_SNDBUF, &so_sndbuf, &sl) < 0) +            pa_log_warn("getsockopt(SO_SNDBUF) failed: %s", pa_cstrerror(errno)); +        else { +            pa_log_debug("SO_SNDBUF is %zu.", (size_t) so_sndbuf); +            pa_sink_set_max_request(u->sink, PA_MAX((size_t) so_sndbuf, u->block_size)); +        } +          pa_log_debug("Connection authenticated, handing fd to IO thread...");          pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); @@ -535,7 +547,14 @@ int pa__init(pa_module*m) {      u->module = m;      m->userdata = u;      u->fd = -1; -    u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); +    u->smoother = pa_smoother_new( +            PA_USEC_PER_SEC, +            PA_USEC_PER_SEC*2, +            TRUE, +            TRUE, +            10, +            0, +            FALSE);      pa_memchunk_reset(&u->memchunk);      u->offset = 0; @@ -566,7 +585,14 @@ 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_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, espeaker); -    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Esound sink '%s'", espeaker); +    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "esd"); +    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "EsounD Output on %s", espeaker); + +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    }      u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK);      pa_sink_new_data_done(&data); @@ -582,7 +608,7 @@ int pa__init(pa_module*m) {      pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);      pa_sink_set_rtpoll(u->sink, u->rtpoll); -    if (!(u->client = pa_socket_client_new_string(u->core->mainloop, espeaker, ESD_DEFAULT_PORT))) { +    if (!(u->client = pa_socket_client_new_string(u->core->mainloop, TRUE, espeaker, ESD_DEFAULT_PORT))) {          pa_log("Failed to connect to server.");          goto fail;      } diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index 9b0d71c9..658b3e55 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -45,10 +45,10 @@  #include <pulsecore/namereg.h>  #include <pulsecore/core-scache.h>  #include <pulsecore/modargs.h> +#include <pulsecore/dbus-shared.h>  #include <hal/libhal.h> -#include "dbus-util.h"  #include "module-hal-detect-symdef.h"  PA_MODULE_AUTHOR("Shahms King"); @@ -64,6 +64,7 @@ PA_MODULE_USAGE("api=<alsa> "  #elif defined(HAVE_OSS)  PA_MODULE_USAGE("api=<oss>");  #endif +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");  struct device {      char *udi, *originating_udi; @@ -121,6 +122,7 @@ static const char *strip_udi(const char *udi) {  enum alsa_type {      ALSA_TYPE_PLAYBACK,      ALSA_TYPE_CAPTURE, +    ALSA_TYPE_CONTROL,      ALSA_TYPE_OTHER  }; @@ -141,6 +143,8 @@ static enum alsa_type hal_alsa_device_get_type(LibHalContext *context, const cha          t = ALSA_TYPE_PLAYBACK;      else if (pa_streq(type, "capture"))          t = ALSA_TYPE_CAPTURE; +    else if (pa_streq(type, "control")) +        t = ALSA_TYPE_CONTROL;      libhal_free_string(type); @@ -171,7 +175,8 @@ static pa_bool_t hal_alsa_device_is_modem(LibHalContext *context, const char *ud  finish:      if (dbus_error_is_set(&error)) { -        pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message); +        if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty")) +            pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);          dbus_error_free(&error);      } @@ -193,10 +198,23 @@ static int hal_device_load_alsa(struct userdata *u, const char *udi, struct devi      /* We only care for PCM devices */      type = hal_alsa_device_get_type(u->context, udi); -    if (type == ALSA_TYPE_OTHER) + +    /* For each ALSA card that appears the control device will be the +     * last one to be created, this is considered part of the ALSA +     * usperspace API. We rely on this and load our modules only when +     * the control device is available assuming that *all* device +     * nodes have been properly created and assigned the right ACLs at +     * that time. Also see: +     * +     * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html +     * +     * and the associated thread.*/ + +    if (type != ALSA_TYPE_CONTROL)          goto fail; -    /* We don't care for modems */ +    /* We don't care for modems -- this is most likely not set for +     * control devices, so kind of pointless here. */      if (hal_alsa_device_is_modem(u->context, udi))          goto fail; @@ -215,7 +233,7 @@ static int hal_device_load_alsa(struct userdata *u, const char *udi, struct devi          goto fail;      card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi)); -    args = pa_sprintf_malloc("device_id=%u name=%s card_name=%s tsched=%i", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); +    args = pa_sprintf_malloc("device_id=%u name=\"%s\" card_name=\"%s\" tsched=%i card_properties=\"module-hal-detect.discovered=1\"", card, strip_udi(originating_udi), 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); @@ -411,9 +429,10 @@ static int hal_device_add_all(struct userdata *u) {          for (i = 0; i < n; i++) {              struct device *d; -            if ((d = hal_device_add(u, udis[i]))) +            if ((d = hal_device_add(u, udis[i]))) {                  count++; -            else +                pa_log_debug("Loaded device %s", udis[i]); +            } else                  pa_log_debug("Not loaded device %s", udis[i]);          }      } @@ -449,6 +468,8 @@ static void device_added_cb(LibHalContext *context, const char *udi) {      if (!hal_device_add(u, udi))          pa_log_debug("Not loaded device %s", udi); +    else +        pa_log_debug("Loaded device %s", udi);  finish:      if (dbus_error_is_set(&error)) { @@ -547,7 +568,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo                      pa_sink *sink;                      if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) { -                        pa_bool_t success = pa_sink_suspend(sink, suspend) >= 0; +                        pa_bool_t success = pa_sink_suspend(sink, suspend, PA_SUSPEND_SESSION) >= 0;                          if (!success && !suspend)                              d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -560,7 +581,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo                      pa_source *source;                      if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) { -                        pa_bool_t success = pa_source_suspend(source, suspend) >= 0; +                        pa_bool_t success = pa_source_suspend(source, suspend, PA_SUSPEND_SESSION) >= 0;                          if (!success && !suspend)                              d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -573,7 +594,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo                      pa_card *card;                      if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) { -                        pa_bool_t success = pa_card_suspend(card, suspend) >= 0; +                        pa_bool_t success = pa_card_suspend(card, suspend, PA_SUSPEND_SESSION) >= 0;                          if (!success && !suspend)                              d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -617,21 +638,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo                      pa_sink *sink;                      if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) -                        pa_sink_suspend(sink, FALSE); +                        pa_sink_suspend(sink, FALSE, PA_SUSPEND_SESSION);                  }                  if (d->source_name) {                      pa_source *source;                      if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) -                        pa_source_suspend(source, FALSE); +                        pa_source_suspend(source, FALSE, PA_SUSPEND_SESSION);                  }                  if (d->card_name) {                      pa_card *card;                      if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD))) -                        pa_card_suspend(card, FALSE); +                        pa_card_suspend(card, FALSE, PA_SUSPEND_SESSION);                  }              } diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c new file mode 100644 index 00000000..c697209a --- /dev/null +++ b/src/modules/module-intended-roles.c @@ -0,0 +1,428 @@ +/*** +  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 <pulse/volume.h> +#include <pulse/timeval.h> +#include <pulse/util.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 "module-intended-roles-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically set device of streams based of intended roles of devices"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( +        "on_hotplug=<When new device becomes available, recheck streams?> " +        "on_rescue=<When device becomes unavailable, recheck streams?>"); + +static const char* const valid_modargs[] = { +    "on_hotplug", +    "on_rescue", +    NULL +}; + +struct userdata { +    pa_core *core; +    pa_module *module; + +    pa_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; + +    pa_bool_t on_hotplug:1; +    pa_bool_t on_rescue:1; +}; + +static pa_bool_t role_match(pa_proplist *proplist, const char *role) { +    const char *ir; +    char *r; +    const char *state = NULL; + +    if (!(ir = pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES))) +        return FALSE; + +    while ((r = pa_split_spaces(ir, &state))) { + +        if (pa_streq(role, r)) { +            pa_xfree(r); +            return TRUE; +        } + +        pa_xfree(r); +    } + +    return FALSE; +} + +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; +    pa_sink *s, *def; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(new_data); +    pa_assert(u); + +    if (!new_data->proplist) { +        pa_log_debug("New stream lacks property data."); +        return PA_HOOK_OK; +    } + +    if (new_data->sink) { +        pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); +        return PA_HOOK_OK; +    } + +    if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { +        pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); +        return PA_HOOK_OK; +    } + +    /* Prefer the default sink over any other sink, just in case... */ +    if ((def = pa_namereg_get_default_sink(c))) +        if (role_match(def->proplist, role)) { +            new_data->sink = def; +            new_data->save_sink = FALSE; +            return PA_HOOK_OK; +        } + +    PA_IDXSET_FOREACH(s, c->sinks, idx) { +        if (s == def) +            continue; + +        if (role_match(s->proplist, role)) { +            new_data->sink = s; +            new_data->save_sink = FALSE; +            return PA_HOOK_OK; +        } +    } + +    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; +    pa_source *s, *def; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(new_data); +    pa_assert(u); + +    if (!new_data->proplist) { +        pa_log_debug("New stream lacks property data."); +        return PA_HOOK_OK; +    } + +    if (new_data->source) { +        pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); +        return PA_HOOK_OK; +    } + +    if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { +        pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); +        return PA_HOOK_OK; +    } + +    /* Prefer the default source over any other source, just in case... */ +    if ((def = pa_namereg_get_default_source(c))) +        if (role_match(def->proplist, role)) { +            new_data->source = def; +            new_data->save_source = FALSE; +            return PA_HOOK_OK; +        } + +    PA_IDXSET_FOREACH(s, c->sources, idx) { +        if (s == def) +            continue; + +        if (role_match(s->proplist, role)) { +            new_data->source = s; +            new_data->save_source = FALSE; +            return PA_HOOK_OK; +        } +    } + +    return PA_HOOK_OK; +} + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { +    pa_sink_input *si; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(sink); +    pa_assert(u); +    pa_assert(u->on_hotplug); + +    PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { +        const char *role; + +        if (si->sink == sink) +            continue; + +        if (si->save_sink) +            continue; + +        if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) +            continue; + +        if (role_match(si->sink->proplist, role)) +            continue; + +        if (!role_match(sink->proplist, role)) +            continue; + +        pa_sink_input_move_to(si, sink, FALSE); +    } + +    return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { +    pa_source_output *so; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(source); +    pa_assert(u); +    pa_assert(u->on_hotplug); + +    PA_IDXSET_FOREACH(so, c->source_outputs, idx) { +        const char *role; + +        if (so->source == source) +            continue; + +        if (so->save_source) +            continue; + +        if (so->direct_on_input) +            continue; + +        if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) +            continue; + +        if (role_match(so->source->proplist, role)) +            continue; + +        if (!role_match(source->proplist, role)) +            continue; + +        pa_source_output_move_to(so, source, FALSE); +    } + +    return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { +    pa_sink_input *si; +    uint32_t idx; +    pa_sink *def; + +    pa_assert(c); +    pa_assert(sink); +    pa_assert(u); +    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; + +    /* If there not default sink, then there is no sink at all */ +    if (!(def = pa_namereg_get_default_sink(c))) +        return PA_HOOK_OK; + +    PA_IDXSET_FOREACH(si, sink->inputs, idx) { +        const char *role; +        uint32_t jdx; +        pa_sink *d; + +        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; +        } + +        /* 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; +            } +        } +    } + +    return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { +    pa_source_output *so; +    uint32_t idx; +    pa_source *def; + +    pa_assert(c); +    pa_assert(source); +    pa_assert(u); +    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; + +    /* If there not default source, then there is no source at all */ +    if (!(def = pa_namereg_get_default_source(c))) +        return PA_HOOK_OK; + +    PA_IDXSET_FOREACH(so, source->outputs, idx) { +        const char *role; +        uint32_t jdx; +        pa_source *d; + +        if (so->direct_on_input) +            continue; + +        if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) +            continue; + +        /* Would the default source fit? If so, let's use it */ +        if (def != source && role_match(def->proplist, role) && !source->monitor_of == !def->monitor_of) { +            pa_source_output_move_to(so, def, FALSE); +            continue; +        } + +        /* Try to find some other fitting source */ +        PA_IDXSET_FOREACH(d, c->sources, jdx) { +            if (d == def || d == source) +                continue; + +            if (role_match(d->proplist, role) && !source->monitor_of == !d->monitor_of) { +                pa_source_output_move_to(so, d, FALSE); +                break; +            } +        } +    } + +    return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { +    pa_modargs *ma = NULL; +    struct userdata *u; +    pa_bool_t 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, "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->on_hotplug = on_hotplug; +    u->on_rescue = on_rescue; + +    /* A little bit later than module-stream-restore */ +    u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+10, (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+10, (pa_hook_cb_t) source_output_new_hook_callback, u); + +    if (on_hotplug) { +        /* A little bit later than module-stream-restore */ +        u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (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+10, (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-rescue-streams, ... */ +        u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+10, (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+10, (pa_hook_cb_t) source_unlink_hook_callback, u); +    } + +    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->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); + +    pa_xfree(u); +} diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 33562b10..21f4a8f1 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -27,6 +27,7 @@  #endif  #include <pulse/xmalloc.h> +#include <pulse/i18n.h>  #include <pulsecore/core-error.h>  #include <pulsecore/namereg.h> @@ -45,19 +46,20 @@  #include "ladspa.h"  PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Virtual LADSPA sink"); +PA_MODULE_DESCRIPTION(_("Virtual LADSPA sink"));  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE( -        "sink_name=<name for the sink> " -        "master=<name of sink to remap> " -        "format=<sample format> " -        "channels=<number of channels> " -        "rate=<sample rate> " -        "channel_map=<channel map> " -        "plugin=<ladspa plugin name> " -        "label=<ladspa plugin label> " -        "control=<comma seperated list of input control values>"); +        _("sink_name=<name for the sink> " +          "sink_properties=<properties for the sink> " +          "master=<name of sink to filter> " +          "format=<sample format> " +          "rate=<sample rate> " +          "channels=<number of channels> " +          "channel_map=<channel map> " +          "plugin=<ladspa plugin name> " +          "label=<ladspa plugin label> " +          "control=<comma seperated list of input control values>"));  #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) @@ -85,10 +87,11 @@ struct userdata {  static const char* const valid_modargs[] = {      "sink_name", +    "sink_properties",      "master",      "format", -    "channels",      "rate", +    "channels",      "channel_map",      "plugin",      "label", @@ -235,7 +238,7 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {          if (amount > 0) {              unsigned c; -            pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE); +            pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE);              pa_log_debug("Resetting plugin"); @@ -264,7 +267,7 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {          return;      pa_memblockq_set_maxrewind(u->memblockq, nbytes); -    pa_sink_set_max_rewind(u->sink, nbytes); +    pa_sink_set_max_rewind_within_thread(u->sink, nbytes);  }  /* Called from I/O thread context */ @@ -277,7 +280,7 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {      if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))          return; -    pa_sink_set_max_request(u->sink, nbytes); +    pa_sink_set_max_request_within_thread(u->sink, nbytes);  }  /* Called from I/O thread context */ @@ -290,7 +293,7 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {      if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))          return; -    pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +    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 */ @@ -322,7 +325,7 @@ static void sink_input_attach_cb(pa_sink_input *i) {      pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);      pa_sink_attach_within_thread(u->sink); -    pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); +    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 */ @@ -705,7 +708,13 @@ int pa__init(pa_module*m) {      pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright);      pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID); -    u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY); +    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_LATENCY|PA_SINK_DYNAMIC_LATENCY);      pa_sink_new_data_done(&sink_data);      if (!u->sink) { diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index bdb8bb71..06efeb8f 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -63,8 +63,6 @@ struct userdata {      float mute_toggle_save;  }; -static int lirc_in_use = 0; -  static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) {      struct userdata *u = userdata;      char *name = NULL, *code = NULL; @@ -114,7 +112,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                  volchange = RESET;              if (volchange == INVALID) -                pa_log_warn("Recieved unknown IR code '%s'", name); +                pa_log_warn("Received unknown IR code '%s'", name);              else {                  pa_sink *s; @@ -122,48 +120,48 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                      pa_log("Failed to get sink '%s'", u->sink_name);                  else {                      int i; -                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE); +                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);  #define DELTA (PA_VOLUME_NORM/20)                      switch (volchange) {                          case UP:                              for (i = 0; i < cv.channels; i++) { -                                cv.values[i] += DELTA; - -                                if (cv.values[i] > PA_VOLUME_NORM) -                                    cv.values[i] = PA_VOLUME_NORM; +                                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); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case DOWN:                              for (i = 0; i < cv.channels; i++) { -                                if (cv.values[i] >= DELTA) +                                if (cv.values[i] > DELTA)                                      cv.values[i] -= DELTA;                                  else                                      cv.values[i] = PA_VOLUME_MUTED;                              } -                            pa_sink_set_volume(s, &cv, TRUE, TRUE); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case MUTE: -                            pa_sink_set_mute(s, 0); +                            pa_sink_set_mute(s, TRUE, TRUE);                              break;                          case RESET: -                            pa_sink_set_mute(s, 1); +                            pa_sink_set_mute(s, FALSE, TRUE);                              break;                          case MUTE_TOGGLE: -                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); +                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);                              break;                          case INVALID: -                            ; +                            pa_assert_not_reached();                      }                  }              } @@ -189,11 +187,6 @@ int pa__init(pa_module*m) {      pa_assert(m); -    if (lirc_in_use) { -        pa_log("module-lirc may no be loaded twice."); -        return -1; -    } -      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {          pa_log("Failed to parse module arguments");          goto fail; @@ -219,8 +212,6 @@ int pa__init(pa_module*m) {      u->io = m->core->mainloop->io_new(m->core->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); -    lirc_in_use = 1; -      pa_modargs_free(ma);      return 0; @@ -252,6 +243,4 @@ void pa__done(pa_module*m) {      pa_xfree(u->sink_name);      pa_xfree(u); - -    lirc_in_use = 0;  } diff --git a/src/modules/module-match.c b/src/modules/module-match.c index d7365ca7..625f2a8b 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -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); +                pa_sink_input_set_volume(si, &cv, TRUE, TRUE);              }          }      } diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index 2f87dd22..b30fae51 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -52,17 +52,6 @@ PA_MODULE_USAGE("device=<evdev device> sink=<sink name>");  #define DEFAULT_DEVICE "/dev/input/event0" -/* - * This isn't defined in older kernel headers and there is no way of - * detecting it. - */ -struct _input_id { -    __u16 bustype; -    __u16 vendor; -    __u16 product; -    __u16 version; -}; -  static const char* const valid_modargs[] = {      "device",      "sink", @@ -113,40 +102,40 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                      pa_log("Failed to get sink '%s'", u->sink_name);                  else {                      int i; -                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE); +                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);  #define DELTA (PA_VOLUME_NORM/20)                      switch (volchange) {                          case UP:                              for (i = 0; i < cv.channels; i++) { -                                cv.values[i] += DELTA; - -                                if (cv.values[i] > PA_VOLUME_NORM) -                                    cv.values[i] = PA_VOLUME_NORM; +                                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); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case DOWN:                              for (i = 0; i < cv.channels; i++) { -                                if (cv.values[i] >= DELTA) +                                if (cv.values[i] > DELTA)                                      cv.values[i] -= DELTA;                                  else                                      cv.values[i] = PA_VOLUME_MUTED;                              } -                            pa_sink_set_volume(s, &cv, TRUE, TRUE); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case MUTE_TOGGLE: -                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); +                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);                              break;                          case INVALID: -                            ; +                            pa_assert_not_reached();                      }                  }              } @@ -169,7 +158,7 @@ int pa__init(pa_module*m) {      pa_modargs *ma = NULL;      struct userdata *u;      int version; -    struct _input_id input_id; +    struct input_id input_id;      char name[256];      uint8_t evtype_bitmask[EV_MAX/8 + 1]; @@ -180,15 +169,15 @@ int pa__init(pa_module*m) {          goto fail;      } -    m->userdata = u = pa_xnew(struct userdata,1); +    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; -    if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY)) < 0) { -        pa_log("failed to open evdev device: %s", pa_cstrerror(errno)); +    if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY|O_NOCTTY)) < 0) { +        pa_log("Failed to open evdev device: %s", pa_cstrerror(errno));          goto fail;      } @@ -208,7 +197,7 @@ int pa__init(pa_module*m) {                  input_id.vendor, input_id.product, input_id.version, input_id.bustype);      memset(name, 0, sizeof(name)); -    if(ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) { +    if (ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) {          pa_log("EVIOCGNAME failed: %s", pa_cstrerror(errno));          goto fail;      } diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index b5952d69..228a9716 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -32,12 +32,14 @@  #include <unistd.h>  #include <limits.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h>  #include <pulsecore/macro.h>  #include <pulsecore/sink.h>  #include <pulsecore/module.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/core-error.h>  #include <pulsecore/modargs.h> @@ -45,7 +47,6 @@  #include <pulsecore/thread.h>  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h>  #include "module-null-sink-symdef.h" @@ -54,15 +55,15 @@ PA_MODULE_DESCRIPTION("Clocked NULL sink");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE( +        "sink_name=<name of sink> " +        "sink_properties=<properties for the sink> "          "format=<sample format> " -        "channels=<number of channels> "          "rate=<sample rate> " -        "sink_name=<name of sink> " -        "channel_map=<channel map> " -        "description=<description for the sink>"); +        "channels=<number of channels> " +        "channel_map=<channel map>");  #define DEFAULT_SINK_NAME "null" -#define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2) +#define BLOCK_USEC (PA_USEC_PER_SEC * 2)  struct userdata {      pa_core *core; @@ -78,12 +79,13 @@ struct userdata {  };  static const char* const valid_modargs[] = { -    "rate", +    "sink_name", +    "sink_properties",      "format", +    "rate",      "channels", -    "sink_name",      "channel_map", -    "description", +    "description", /* supported for compatibility reasons, made redundant by sink_properties= */      NULL  }; @@ -100,14 +102,14 @@ static int sink_process_msg(          case PA_SINK_MESSAGE_SET_STATE:              if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING) -                u->timestamp = pa_rtclock_usec(); +                u->timestamp = pa_rtclock_now();              break;          case PA_SINK_MESSAGE_GET_LATENCY: {              pa_usec_t now; -            now = pa_rtclock_usec(); +            now = pa_rtclock_now();              *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0ULL;              return 0; @@ -119,6 +121,7 @@ static int sink_process_msg(  static void sink_update_requested_latency_cb(pa_sink *s) {      struct userdata *u; +    size_t nbytes;      pa_sink_assert_ref(s);      pa_assert_se(u = s->userdata); @@ -127,6 +130,10 @@ static void sink_update_requested_latency_cb(pa_sink *s) {      if (u->block_usec == (pa_usec_t) -1)          u->block_usec = s->thread_info.max_latency; + +    nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec); +    pa_sink_set_max_rewind_within_thread(s, nbytes); +    pa_sink_set_max_request_within_thread(s, nbytes);  }  static void process_rewind(struct userdata *u, pa_usec_t now) { @@ -202,9 +209,8 @@ static void thread_func(void *userdata) {      pa_log_debug("Thread starting up");      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll); -    u->timestamp = pa_rtclock_usec(); +    u->timestamp = pa_rtclock_now();      for (;;) {          int ret; @@ -213,7 +219,7 @@ static void thread_func(void *userdata) {          if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {              pa_usec_t now; -            now = pa_rtclock_usec(); +            now = pa_rtclock_now();              if (u->sink->thread_info.rewind_requested) {                  if (u->sink->thread_info.rewind_nbytes > 0) @@ -253,6 +259,7 @@ int pa__init(pa_module*m) {      pa_channel_map map;      pa_modargs *ma = NULL;      pa_sink_new_data data; +    size_t nbytes;      pa_assert(m); @@ -283,7 +290,13 @@ int pa__init(pa_module*m) {      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"); -    u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } + +    u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY);      pa_sink_new_data_done(&data);      if (!u->sink) { @@ -298,12 +311,10 @@ 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_latency_range(u->sink, (pa_usec_t) -1, MAX_LATENCY_USEC); -    u->block_usec = u->sink->thread_info.max_latency; - -    u->sink->thread_info.max_rewind = -        u->sink->thread_info.max_request = -        pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); +    u->block_usec = BLOCK_USEC; +    nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); +    pa_sink_set_max_rewind(u->sink, nbytes); +    pa_sink_set_max_request(u->sink, nbytes);      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log("Failed to create thread."); diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index def4f758..8a7dc846 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -54,10 +54,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> "          "file=<path of the FIFO> "          "format=<sample format> " -        "channels=<number of channels> "          "rate=<sample rate>" +        "channels=<number of channels> "          "channel_map=<channel map>");  #define DEFAULT_FILE_NAME "fifo_output" @@ -83,11 +84,12 @@ struct userdata {  };  static const char* const valid_modargs[] = { +    "sink_name", +    "sink_properties",      "file", -    "rate",      "format", +    "rate",      "channels", -    "sink_name",      "channel_map",      NULL  }; @@ -168,7 +170,6 @@ static void thread_func(void *userdata) {      pa_log_debug("Thread starting up");      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          struct pollfd *pollfd; @@ -279,6 +280,12 @@ int pa__init(pa_module*m) {      pa_sink_new_data_set_sample_spec(&data, &ss);      pa_sink_new_data_set_channel_map(&data, &map); +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } +      u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY);      pa_sink_new_data_done(&data); @@ -292,6 +299,8 @@ 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_max_request(u->sink, PIPE_BUF); +    pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec));      u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);      pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index 3d40fdf3..e5609fb5 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -54,10 +54,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "source_name=<name for the source> " +        "source_properties=<properties for the source> "          "file=<path of the FIFO> "          "format=<sample format> " -        "channels=<number of channels> "          "rate=<sample rate> " +        "channels=<number of channels> "          "channel_map=<channel map>");  #define DEFAULT_FILE_NAME "/tmp/music.input" @@ -81,11 +82,12 @@ struct userdata {  };  static const char* const valid_modargs[] = { +    "source_name", +    "source_properties",      "file", +    "format",      "rate",      "channels", -    "format", -    "source_name",      "channel_map",      NULL  }; @@ -127,7 +129,6 @@ static void thread_func(void *userdata) {      pa_log_debug("Thread starting up");      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          int ret; @@ -264,6 +265,12 @@ int pa__init(pa_module*m) {      pa_source_new_data_set_sample_spec(&data, &ss);      pa_source_new_data_set_channel_map(&data, &map); +    if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_source_new_data_done(&data); +        goto fail; +    } +      u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY);      pa_source_new_data_done(&data); @@ -277,6 +284,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(PIPE_BUF, &u->source->sample_spec));      u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);      pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index ce3dcd03..5b351d19 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -251,7 +251,7 @@ int pa__init(pa_module*m) {      int r;  #endif -#if defined(USE_PROTOCOL_NATIVE) +#if defined(USE_PROTOCOL_NATIVE) || defined(USE_PROTOCOL_HTTP)      char t[256];  #endif @@ -382,6 +382,24 @@ int pa__init(pa_module*m) {  #  endif  #endif +#if defined(USE_PROTOCOL_HTTP) +#if defined(USE_TCP_SOCKETS) +    if (u->socket_server_ipv4) +        if (pa_socket_server_get_address(u->socket_server_ipv4, t, sizeof(t))) +            pa_http_protocol_add_server_string(u->http_protocol, t); + +#ifdef HAVE_IPV6 +    if (u->socket_server_ipv6) +        if (pa_socket_server_get_address(u->socket_server_ipv6, t, sizeof(t))) +            pa_http_protocol_add_server_string(u->http_protocol, t); +#endif /* HAVE_IPV6 */ +#else /* USE_TCP_SOCKETS */ +    if (pa_socket_server_get_address(u->socket_server_unix, t, sizeof(t))) +        pa_http_protocol_add_server_string(u->http_protocol, t); + +#endif /* USE_TCP_SOCKETS */ +#endif /* USE_PROTOCOL_HTTP */ +      if (ma)          pa_modargs_free(ma); @@ -419,6 +437,24 @@ void pa__done(pa_module*m) {      }  #elif defined(USE_PROTOCOL_HTTP)      if (u->http_protocol) { +        char t[256]; + +#if defined(USE_TCP_SOCKETS) +        if (u->socket_server_ipv4) +            if (pa_socket_server_get_address(u->socket_server_ipv4, t, sizeof(t))) +                pa_http_protocol_remove_server_string(u->http_protocol, t); + +#ifdef HAVE_IPV6 +        if (u->socket_server_ipv6) +            if (pa_socket_server_get_address(u->socket_server_ipv6, t, sizeof(t))) +                pa_http_protocol_remove_server_string(u->http_protocol, t); +#endif /* HAVE_IPV6 */ +#else /* USE_TCP_SOCKETS */ +        if (u->socket_server_unix) +            if (pa_socket_server_get_address(u->socket_server_unix, t, sizeof(t))) +                pa_http_protocol_remove_server_string(u->http_protocol, t); +#endif /* USE_PROTOCOL_HTTP */ +          pa_http_protocol_disconnect(u->http_protocol, u->module);          pa_http_protocol_unref(u->http_protocol);      } diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 31824bc5..119f5b9f 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -44,6 +44,7 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> "          "master=<name of sink to remap> "          "master_channel_map=<channel map> "          "format=<sample format> " @@ -62,10 +63,11 @@ struct userdata {  static const char* const valid_modargs[] = {      "sink_name", +    "sink_properties",      "master",      "master_channel_map", -    "rate",      "format", +    "rate",      "channels",      "channel_map",      "remix", @@ -179,7 +181,7 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {      if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))          return; -    pa_sink_set_max_rewind(u->sink, nbytes); +    pa_sink_set_max_rewind_within_thread(u->sink, nbytes);  }  /* Called from I/O thread context */ @@ -192,7 +194,7 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {      if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))          return; -    pa_sink_set_max_request(u->sink, nbytes); +    pa_sink_set_max_request_within_thread(u->sink, nbytes);  }  /* Called from I/O thread context */ @@ -205,7 +207,7 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {      if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))          return; -    pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +    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 */ @@ -237,7 +239,7 @@ static void sink_input_attach_cb(pa_sink_input *i) {      pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);      pa_sink_attach_within_thread(u->sink); -    pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); +    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 */ @@ -354,7 +356,13 @@ int pa__init(pa_module*m) {      pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);      pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); -    u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY); +    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_LATENCY|PA_SINK_DYNAMIC_LATENCY);      pa_sink_new_data_done(&sink_data);      if (!u->sink) { diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 4f616e05..c23feceb 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -31,6 +31,7 @@  #include <pulsecore/modargs.h>  #include <pulsecore/log.h>  #include <pulsecore/namereg.h> +#include <pulsecore/core-util.h>  #include "module-rescue-streams-symdef.h" @@ -49,6 +50,7 @@ struct userdata {  static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {      pa_sink_input *i; +    uint32_t idx;      pa_sink *target;      pa_assert(c); @@ -58,40 +60,41 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user      if (c->state == PA_CORE_SHUTDOWN)          return PA_HOOK_OK; -    if (!pa_idxset_size(sink->inputs)) { +    if (pa_idxset_size(sink->inputs) <= 0) {          pa_log_debug("No sink inputs to move away.");          return PA_HOOK_OK;      } -    if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK)) || target == sink) { -        uint32_t idx; +    if (!(target = pa_namereg_get_default_sink(c)) || target == sink) { -        for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) +        PA_IDXSET_FOREACH(target, c->sinks, idx)              if (target != sink)                  break;          if (!target) { -            pa_log_info("No evacuation sink found."); +            pa_log_debug("No evacuation sink found.");              return PA_HOOK_OK;          }      } -    while ((i = pa_idxset_first(sink->inputs, NULL))) { -        if (pa_sink_input_move_to(i, target, FALSE) < 0) { -            pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); -            return PA_HOOK_OK; -        } +    pa_assert(target != sink); -        pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); +    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); +        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_OK;  }  static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) {      pa_source_output *o;      pa_source *target; +    uint32_t idx;      pa_assert(c);      pa_assert(source); @@ -100,15 +103,14 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void      if (c->state == PA_CORE_SHUTDOWN)          return PA_HOOK_OK; -    if (!pa_idxset_size(source->outputs)) { +    if (pa_idxset_size(source->outputs) <= 0) {          pa_log_debug("No source outputs to move away.");          return PA_HOOK_OK;      } -    if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE)) || target == source) { -        uint32_t idx; +    if (!(target = pa_namereg_get_default_source(c)) || target == source) { -        for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx)) +        PA_IDXSET_FOREACH(target, c->sources, idx)              if (target != source && !target->monitor_of == !source->monitor_of)                  break; @@ -120,21 +122,20 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void      pa_assert(target != source); -    while ((o = pa_idxset_first(source->outputs, NULL))) { -        if (pa_source_output_move_to(o, target, FALSE) < 0) { -            pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); -            return PA_HOOK_OK; -        } - -        pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); +    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); +        else +            pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, +                        pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name);      } -      return PA_HOOK_OK;  }  int pa__init(pa_module*m) { -    pa_modargs *ma = NULL; +    pa_modargs *ma;      struct userdata *u;      pa_assert(m); @@ -145,8 +146,10 @@ int pa__init(pa_module*m) {      }      m->userdata = u = pa_xnew(struct userdata, 1); -    u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_hook_callback, NULL); -    u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_hook_callback, NULL); + +    /* 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);      pa_modargs_free(ma);      return 0; @@ -157,10 +160,9 @@ void pa__done(pa_module*m) {      pa_assert(m); -    if (!m->userdata) +    if (!(u = m->userdata))          return; -    u = m->userdata;      if (u->sink_slot)          pa_hook_slot_free(u->sink_slot);      if (u->source_slot) diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c new file mode 100644 index 00000000..4c02e958 --- /dev/null +++ b/src/modules/module-rygel-media-server.c @@ -0,0 +1,847 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2005-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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/modargs.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/namereg.h> +#include <pulsecore/mime-type.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/protocol-http.h> +#include <pulsecore/parseaddr.h> + +#include "module-rygel-media-server-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( +        "display_name=<UPnP Media Server name>"); + +/* This implements http://live.gnome.org/Rygel/MediaServerSpec */ + +#define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio" + +#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio" +#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks" +#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources" + +#define CONTAINER_INTROSPECT_XML_PREFIX                                 \ +    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \ +    "<node>"                                                            \ +    " <!-- If you are looking for documentation make sure to check out" \ +    "      http://live.gnome.org/Rygel/MediaServerSpec -->"             \ +    " <interface name=\"org.gnome.UPnP.MediaContainer1\">"              \ +    "  <signal name=\"Updated\">"                                       \ +    "   <arg name=\"path\" type=\"o\"/>"                                \ +    "  </signal>"                                                       \ +    "  <property name=\"Items\" type=\"ao\" access=\"read\"/>"          \ +    "  <property name=\"ItemCount\" type=\"u\" access=\"read\"/>"       \ +    "  <property name=\"Containers\" type=\"ao\" access=\"read\"/>"     \ +    "  <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>"  \ +    " </interface>"                                                     \ +    " <interface name=\"org.gnome.UPnP.MediaObject1\">"                 \ +    "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \ +    "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \ +    " </interface>"                                                     \ +    " <interface name=\"org.freedesktop.DBus.Properties\">"             \ +    "  <method name=\"Get\">"                                           \ +    "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \ +    "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \ +    "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \ +    "  </method>"                                                       \ +    "  <method name=\"GetAll\">"                                        \ +    "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \ +    "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \ +    "  </method>"                                                       \ +    " </interface>"                                                     \ +    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \ +    "  <method name=\"Introspect\">"                                    \ +    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \ +    "  </method>"                                                       \ +    " </interface>" + +#define CONTAINER_INTROSPECT_XML_POSTFIX                                \ +    "</node>" + +#define ROOT_INTROSPECT_XML                                             \ +    CONTAINER_INTROSPECT_XML_PREFIX                                     \ +    "<node name=\"Sinks\"/>"                                            \ +    "<node name=\"Sources\"/>"                                          \ +    CONTAINER_INTROSPECT_XML_POSTFIX + +#define ITEM_INTROSPECT_XML                                             \ +    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \ +    "<node>"                                                            \ +    " <!-- If you are looking for documentation make sure to check out" \ +    "      http://live.gnome.org/Rygel/MediaProviderSpec -->"           \ +    " <interface name=\"org.gnome.UPnP.MediaItem1\">"                   \ +    "  <property name=\"URLs\" type=\"as\" access=\"read\"/>"           \ +    "  <property name=\"MIMEType\" type=\"s\" access=\"read\"/>"        \ +    "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \ +    " </interface>"                                                     \ +    " <interface name=\"org.gnome.UPnP.MediaObject1\">"                 \ +    "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \ +    "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \ +    " </interface>"                                                     \ +    " <interface name=\"org.freedesktop.DBus.Properties\">"             \ +    "  <method name=\"Get\">"                                           \ +    "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \ +    "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \ +    "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \ +    "  </method>"                                                       \ +    "  <method name=\"GetAll\">"                                        \ +    "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \ +    "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \ +    "  </method>"                                                       \ +    " </interface>"                                                     \ +    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \ +    "  <method name=\"Introspect\">"                                    \ +    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \ +    "  </method>"                                                       \ +    " </interface>"                                                     \ +    "</node>" + + +static const char* const valid_modargs[] = { +    "display_name", +    NULL +}; + +struct userdata { +    pa_core *core; +    pa_module *module; + +    pa_dbus_connection *bus; +    pa_bool_t got_name:1; + +    char *display_name; + +    pa_hook_slot *source_new_slot, *source_unlink_slot; + +    pa_http_protocol *http; +}; + +static void send_signal(struct userdata *u, pa_source *s) { +    DBusMessage *m; +    const char *parent; + +    pa_assert(u); +    pa_source_assert_ref(s); + +    if (u->core->state == PA_CORE_SHUTDOWN) +        return; + +    if (s->monitor_of) +        parent = OBJECT_SINKS; +    else +        parent = OBJECT_SOURCES; + +    pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer1", "Updated")); +    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL)); + +    dbus_message_unref(m); +} + +static pa_hook_result_t source_new_or_unlink_cb(pa_core *c, pa_source *s, struct userdata *u) { +    pa_assert(c); +    pa_source_assert_ref(s); + +    send_signal(u, s); + +    return PA_HOOK_OK; +} + +static pa_bool_t message_is_property_get(DBusMessage *m, const char *interface, const char *property) { +    const char *i, *p; +    DBusError error; + +    dbus_error_init(&error); + +    pa_assert(m); + +    if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")) +        return FALSE; + +    if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { +        dbus_error_free(&error); +        return FALSE; +    } + +    return pa_streq(i, interface) && pa_streq(p, property); +} + +static pa_bool_t message_is_property_get_all(DBusMessage *m, const char *interface) { +    const char *i; +    DBusError error; + +    dbus_error_init(&error); + +    pa_assert(m); + +    if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll")) +        return FALSE; + +    if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { +        dbus_error_free(&error); +        return FALSE; +    } + +    return pa_streq(i, interface); +} + +static void append_variant_object_array(DBusMessage *m, DBusMessageIter *iter, const char *path[], unsigned n) { +    DBusMessageIter _iter, variant, array; +    unsigned c; + +    pa_assert(m); +    pa_assert(path); + +    if (!iter) { +        dbus_message_iter_init_append(m, &_iter); +        iter = &_iter; +    } + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ao", &variant)); +    pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "o", &array)); + +    for (c = 0; c < n; c++) +        pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, path + c)); + +    pa_assert_se(dbus_message_iter_close_container(&variant, &array)); +    pa_assert_se(dbus_message_iter_close_container(iter, &variant)); +} + +static void append_variant_string(DBusMessage *m, DBusMessageIter *iter, const char *s) { +    DBusMessageIter _iter, sub; + +    pa_assert(m); +    pa_assert(s); + +    if (!iter) { +        dbus_message_iter_init_append(m, &_iter); +        iter = &_iter; +    } + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &s)); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_object(DBusMessage *m, DBusMessageIter *iter, const char *s) { +    DBusMessageIter _iter, sub; + +    pa_assert(m); +    pa_assert(s); + +    if (!iter) { +        dbus_message_iter_init_append(m, &_iter); +        iter = &_iter; +    } + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "o", &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &s)); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsigned u) { +    DBusMessageIter _iter, sub; + +    pa_assert(m); + +    if (!iter) { +        dbus_message_iter_init_append(m, &_iter); +        iter = &_iter; +    } + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "u", &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u)); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) { +    DBusMessageIter sub; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); +    append_variant_object_array(m, &sub, path, n); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_string(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) { +    DBusMessageIter sub; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); +    append_variant_string(m, &sub, value); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_object(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) { +    DBusMessageIter sub; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); +    append_variant_object(m, &sub, value); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter *iter, const char *name, unsigned u) { +    DBusMessageIter sub; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); +    append_variant_unsigned(m, &sub, u); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES }; +static const char *array_no_children[] = { }; + +static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *userdata) { +    struct userdata *u = userdata; +    DBusMessage *r = NULL; + +    pa_assert(u); + +    if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers)); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + +    } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { +        DBusMessageIter iter, sub; + +        pa_assert_se(r = dbus_message_new_method_return(m)); +        dbus_message_iter_init_append(r, &iter); + +        pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +        append_property_dict_entry_object_array(r, &sub, "Containers", array_root_containers, PA_ELEMENTSOF(array_root_containers)); +        append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); +        append_property_dict_entry_object_array(r, &sub, "Items", array_no_children, PA_ELEMENTSOF(array_no_children)); +        append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children)); +        pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_object(r, NULL, OBJECT_ROOT); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_string(r, NULL, u->display_name); + +    } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { +        DBusMessageIter iter, sub; + +        pa_assert_se(r = dbus_message_new_method_return(m)); +        dbus_message_iter_init_append(r, &iter); + +        pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +        append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name); +        pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +    } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { +        const char *xml = ROOT_INTROSPECT_XML; + +        pa_assert_se(r = dbus_message_new_method_return(m)); +        pa_assert_se(dbus_message_append_args( +                             r, +                             DBUS_TYPE_STRING, &xml, +                             DBUS_TYPE_INVALID)); + +    } else +        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +    if (r) { +        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL)); +        dbus_message_unref(r); +    } + +    return DBUS_HANDLER_RESULT_HANDLED; +} + +static char *compute_url(struct userdata *u, const char *name) { +    pa_strlist *i; + +    pa_assert(u); +    pa_assert(name); + +    for (i = pa_http_protocol_servers(u->http); i; i = pa_strlist_next(i)) { +        pa_parsed_address a; + +        if (pa_parse_address(pa_strlist_data(i), &a) >= 0 && +            (a.type == PA_PARSED_ADDRESS_TCP4 || +             a.type == PA_PARSED_ADDRESS_TCP6 || +             a.type == PA_PARSED_ADDRESS_TCP_AUTO)) { + +            const char *address; +            char *s; + +            if (pa_is_ip_address(a.path_or_host)) +                address = a.path_or_host; +            else +                address = "@ADDRESS@"; + +            if (a.port <= 0) +                a.port = 4714; + +            s = pa_sprintf_malloc("http://%s:%u/listen/source/%s", address, a.port, name); + +            pa_xfree(a.path_or_host); +            return s; +        } + +        pa_xfree(a.path_or_host); +    } + +    return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name); +} + +static char **child_array(struct userdata *u, const char *path, unsigned *n) { +    unsigned m; +    uint32_t idx; +    char **array; + +    pa_assert(u); +    pa_assert(path); +    pa_assert(n); + +    if (pa_streq(path, OBJECT_SINKS)) +        m = pa_idxset_size(u->core->sinks); +    else +        m = pa_idxset_size(u->core->sources); + +    array = pa_xnew(char*, m); +    *n = 0; + +    if (pa_streq(path, OBJECT_SINKS)) { +        pa_sink *sink; + +        PA_IDXSET_FOREACH(sink, u->core->sinks, idx) +            array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index); +    } else { +        pa_source *source; + +        PA_IDXSET_FOREACH(source, u->core->sources, idx) +            if (!source->monitor_of) +                array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index); +    } + +    pa_assert((*n) <= m); + +    return array; +} + +static void free_child_array(char **array, unsigned n) { + +    for (; n >= 1; n--) +        pa_xfree(array[n-1]); + +    pa_xfree(array); +} + +static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) { +    struct userdata *u = userdata; +    DBusMessage *r = NULL; +    const char *path; + +    pa_assert(u); + +    path = dbus_message_get_path(m); + +    if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) { + +        /* Container nodes */ + +        if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { +            char ** array; +            unsigned n; + +            array = child_array(u, path, &n); + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_object_array(r, NULL, (const char**) array, n); + +            free_child_array(array, n); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_unsigned(r, NULL, +                                    pa_streq(path, OBJECT_SINKS) ? +                                    pa_idxset_size(u->core->sinks) : +                                    pa_idxset_size(u->core->sources)); + +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { +            DBusMessageIter iter, sub; +            char **array; +            unsigned n; + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            dbus_message_iter_init_append(r, &iter); + +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +            append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children)); +            append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); + +            array = child_array(u, path, &n); +            append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n); +            free_child_array(array, n); +            append_property_dict_entry_unsigned(r, &sub, "ItemCount", +                                                pa_streq(path, OBJECT_SINKS) ? +                                                pa_idxset_size(u->core->sinks) : +                                                pa_idxset_size(u->core->sources)); + +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_object(r, NULL, OBJECT_ROOT); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_string(r, +                                  NULL, +                                  pa_streq(path, OBJECT_SINKS) ? +                                  _("Output Devices") : +                                  _("Input Devices")); + +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { +            DBusMessageIter iter, sub; + +            pa_assert_se(r = dbus_message_new_method_return(m)); + +            dbus_message_iter_init_append(r, &iter); + +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +            append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT); +            append_property_dict_entry_string(m, &sub, "DisplayName", +                                              pa_streq(path, OBJECT_SINKS) ? +                                              _("Output Devices") : +                                              _("Input Devices")); +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +        } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { +            pa_strbuf *sb; +            char *xml; +            uint32_t idx; + +            sb = pa_strbuf_new(); +            pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX); + +            if (pa_streq(path, OBJECT_SINKS)) { +                pa_sink *sink; + +                PA_IDXSET_FOREACH(sink, u->core->sinks, idx) +                    pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index); +            } else { +                pa_source *source; + +                PA_IDXSET_FOREACH(source, u->core->sources, idx) +                    if (!source->monitor_of) +                        pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index); +            } + +            pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX); +            xml = pa_strbuf_tostring_free(sb); + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            pa_assert_se(dbus_message_append_args( +                                 r, +                                 DBUS_TYPE_STRING, &xml, +                                 DBUS_TYPE_INVALID)); + +            pa_xfree(xml); +        } else +            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +    } else { +        pa_sink *sink = NULL; +        pa_source *source = NULL; + +        /* Child nodes */ + +        if (pa_startswith(path, OBJECT_SINKS "/")) +            sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK); +        else if (pa_startswith(path, OBJECT_SOURCES "/")) +            source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE); + +        if (!sink && !source) +            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +        if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { +            DBusMessageIter iter, sub; + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            dbus_message_iter_init_append(r, &iter); + +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +            append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); +            append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_string(r, NULL, "audio"); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) { +            char *t; + +            if (sink) +                t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); +            else +                t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_string(r, NULL, t); +            pa_xfree(t); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) { +            DBusMessageIter iter, sub, array; +            char *url; + +            pa_assert_se(r = dbus_message_new_method_return(m)); + +            dbus_message_iter_init_append(r, &iter); + +            url = compute_url(u, sink ? sink->monitor_source->name : source->name); + +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub)); +            pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); +            pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); +            pa_assert_se(dbus_message_iter_close_container(&sub, &array)); +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +            pa_xfree(url); + +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) { +            DBusMessageIter iter, sub, dict, variant, array; +            char *url, *t; +            const char *un = "URLs"; + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            dbus_message_iter_init_append(r, &iter); + +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +            append_property_dict_entry_string(r, &sub, "Type", "audio"); + +            if (sink) +                t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); +            else +                t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + +            append_property_dict_entry_string(r, &sub, "MIMEType", t); +            pa_xfree(t); + +            pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict)); +            pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un)); + +            url = compute_url(u, sink ? sink->monitor_source->name : source->name); + +            pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant)); +            pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array)); +            pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); +            pa_assert_se(dbus_message_iter_close_container(&variant, &array)); +            pa_assert_se(dbus_message_iter_close_container(&dict, &variant)); +            pa_assert_se(dbus_message_iter_close_container(&sub, &dict)); + +            pa_xfree(url); + +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +        } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { +            const char *xml = +                ITEM_INTROSPECT_XML; + +            pa_assert_se(r = dbus_message_new_method_return(m)); +            pa_assert_se(dbus_message_append_args( +                                 r, +                                 DBUS_TYPE_STRING, &xml, +                                 DBUS_TYPE_INVALID)); + +        } else +            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +    } + +    if (r) { +        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL)); +        dbus_message_unref(r); +    } + +    return DBUS_HANDLER_RESULT_HANDLED; +} + +int pa__init(pa_module *m) { + +    struct userdata *u; +    pa_modargs *ma = NULL; +    DBusError error; +    const char *t; + +    static const DBusObjectPathVTable vtable_root = { +        .message_function = root_handler, +    }; +    static const DBusObjectPathVTable vtable_sinks_and_sources = { +        .message_function = sinks_and_sources_handler, +    }; + +    dbus_error_init(&error); + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("Failed to parse module arguments."); +        goto fail; +    } + +    m->userdata = u = pa_xnew0(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->http = pa_http_protocol_get(u->core); + +    if ((t = pa_modargs_get_value(ma, "display_name", NULL))) +        u->display_name = pa_utf8_filter(t); +    else +        u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@")); + +    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u); +    u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u); + +    if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) { +        pa_log("Failed to get session bus connection: %s", error.message); +        goto fail; +    } + +    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u)); +    pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u)); +    pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u)); + +    if (dbus_bus_request_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { +        pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message); +        goto fail; +    } + +    u->got_name = TRUE; + +    pa_modargs_free(ma); + +    return 0; + +fail: +    pa__done(m); + +    if (ma) +        pa_modargs_free(ma); + +    dbus_error_free(&error); + +    return -1; +} + +void pa__done(pa_module*m) { +    struct userdata*u; +    pa_assert(m); + +    if (!(u = m->userdata)) +        return; + +    if (u->source_new_slot) +        pa_hook_slot_free(u->source_new_slot); +    if (u->source_unlink_slot) +        pa_hook_slot_free(u->source_unlink_slot); + +    if (u->bus) { +        DBusError error; + +        dbus_error_init(&error); + +        dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT); +        dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS); +        dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES); + +        if (u->got_name) { +            if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) { +                pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message); +                dbus_error_free(&error); +            } +        } + +        pa_dbus_connection_unref(u->bus); +    } + +    pa_xfree(u->display_name); + +    if (u->http) +        pa_http_protocol_unref(u->http); + +    pa_xfree(u); +} diff --git a/src/modules/module-sine-source.c b/src/modules/module-sine-source.c index 23831a0d..3145684c 100644 --- a/src/modules/module-sine-source.c +++ b/src/modules/module-sine-source.c @@ -34,19 +34,20 @@  #include <sys/ioctl.h>  #include <sys/poll.h> -#include <pulse/xmalloc.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h> +#include <pulse/xmalloc.h>  #include <pulsecore/core-error.h>  #include <pulsecore/source.h>  #include <pulsecore/module.h> +#include <pulsecore/core-rtclock.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/rtclock.h>  #include "module-sine-source-symdef.h" @@ -55,12 +56,13 @@ PA_MODULE_DESCRIPTION("Sine wave generator source");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE( -        "rate=<sample rate> "          "source_name=<name for the source> " +        "source_properties=<properties for the source> " +        "rate=<sample rate> "          "frequency=<frequency in Hz>");  #define DEFAULT_SOURCE_NAME "sine_input" -#define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2) +#define BLOCK_USEC (PA_USEC_PER_SEC * 2)  struct userdata {      pa_core *core; @@ -79,8 +81,9 @@ struct userdata {  };  static const char* const valid_modargs[] = { -    "rate",      "source_name", +    "source_properties", +    "rate",      "frequency",      NULL  }; @@ -99,14 +102,14 @@ static int source_process_msg(          case PA_SOURCE_MESSAGE_SET_STATE:              if (PA_PTR_TO_UINT(data) == PA_SOURCE_RUNNING) -                u->timestamp = pa_rtclock_usec(); +                u->timestamp = pa_rtclock_now();              break;          case PA_SOURCE_MESSAGE_GET_LATENCY: {              pa_usec_t now, left_to_fill; -            now = pa_rtclock_usec(); +            now = pa_rtclock_now();              left_to_fill = u->timestamp > now ? u->timestamp - now : 0ULL;              *((pa_usec_t*) data) = u->block_usec > left_to_fill ? u->block_usec - left_to_fill : 0ULL; @@ -164,9 +167,8 @@ static void thread_func(void *userdata) {      pa_log_debug("Thread starting up");      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll); -    u->timestamp = pa_rtclock_usec(); +    u->timestamp = pa_rtclock_now();      for (;;) {          int ret; @@ -174,7 +176,7 @@ static void thread_func(void *userdata) {          if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {              pa_usec_t now; -            now = pa_rtclock_usec(); +            now = pa_rtclock_now();              if (u->timestamp <= now)                  process_render(u, now); @@ -248,6 +250,12 @@ int pa__init(pa_module*m) {      pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency);      pa_source_new_data_set_sample_spec(&data, &ss); +    if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_source_new_data_done(&data); +        goto fail; +    } +      u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY);      pa_source_new_data_done(&data); @@ -260,11 +268,11 @@ int pa__init(pa_module*m) {      u->source->update_requested_latency = source_update_requested_latency_cb;      u->source->userdata = u; +    u->block_usec = BLOCK_USEC; +      pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);      pa_source_set_rtpoll(u->source, u->rtpoll); - -    pa_source_set_latency_range(u->source, (pa_usec_t) -1, MAX_LATENCY_USEC); -    u->block_usec = u->source->thread_info.max_latency; +    pa_source_set_fixed_latency(u->source, u->block_usec);      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log("Failed to create thread."); diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 995b3c63..0920d25e 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -46,6 +46,7 @@  #include <pulse/xmalloc.h>  #include <pulse/timeval.h>  #include <pulse/util.h> +#include <pulse/rtclock.h>  #include <pulsecore/iochannel.h>  #include <pulsecore/sink.h> @@ -59,7 +60,6 @@  #include <pulsecore/thread-mq.h>  #include <pulsecore/rtpoll.h>  #include <pulsecore/thread.h> -#include <pulsecore/rtclock.h>  #include "module-solaris-symdef.h" @@ -68,14 +68,16 @@ PA_MODULE_DESCRIPTION("Solaris Sink/Source");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_USAGE(      "sink_name=<name for the sink> " +    "sink_properties=<properties for the sink> "      "source_name=<name for the source> " +    "source_properties=<properties for the source> "      "device=<audio device file name> "      "record=<enable source?> "      "playback=<enable sink?> "      "format=<sample format> "      "channels=<number of channels> "      "rate=<sample rate> " -    "buffer_size=<record buffer size> " +    "buffer_length=<milliseconds> "      "channel_map=<channel map>");  PA_MODULE_LOAD_ONCE(FALSE); @@ -94,8 +96,7 @@ struct userdata {      uint32_t frame_size;      int32_t buffer_size; -    volatile uint64_t written_bytes, read_bytes; -    pa_mutex *written_bytes_lock; +    uint64_t written_bytes, read_bytes;      char *device_name;      int mode; @@ -107,18 +108,19 @@ struct userdata {      uint32_t play_samples_msw, record_samples_msw;      uint32_t prev_playback_samples, prev_record_samples; -    pa_mutex *sample_counter_lock; -    size_t min_request; +    int32_t minimum_request;  };  static const char* const valid_modargs[] = {      "sink_name", +    "sink_properties",      "source_name", +    "source_properties",      "device",      "record",      "playback", -    "buffer_size", +    "buffer_length",      "format",      "rate",      "channels", @@ -127,13 +129,9 @@ static const char* const valid_modargs[] = {  };  #define DEFAULT_DEVICE "/dev/audio" -#define MIN_BUFFER_SIZE (640) -#define MAX_RENDER_HZ   (300) -/* This render rate limit implies a minimum latency,  but without it we waste too much CPU time in the - * IO thread. The maximum render rate and minimum latency (or minimum buffer size) are unachievable on - * common hardware anyway. Note that MIN_BUFFER_SIZE * MAX_RENDER_HZ >= 4 * 48000 Bps. - */ +#define MAX_RENDER_HZ   (300) +/* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */  static uint64_t get_playback_buffered_bytes(struct userdata *u) {      audio_info_t info; @@ -142,8 +140,6 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {      pa_assert(u->sink); -    pa_mutex_lock(u->sample_counter_lock); -      err = ioctl(u->fd, AUDIO_GETINFO, &info);      pa_assert(err >= 0); @@ -159,8 +155,6 @@ 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_mutex_unlock(u->sample_counter_lock); -      return u->written_bytes - played_bytes;  } @@ -171,11 +165,9 @@ static pa_usec_t sink_get_latency(struct userdata *u, pa_sample_spec *ss) {      pa_assert(ss);      if (u->fd >= 0) { -        pa_mutex_lock(u->written_bytes_lock);          r = pa_bytes_to_usec(get_playback_buffered_bytes(u), ss);          if (u->memchunk.memblock)              r += pa_bytes_to_usec(u->memchunk.length, ss); -        pa_mutex_unlock(u->written_bytes_lock);      }      return r;  } @@ -487,7 +479,7 @@ static void sink_set_volume(pa_sink *s) {      if (u->fd >= 0) {          AUDIO_INITINFO(&info); -        info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; +        info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;          assert(info.play.gain <= AUDIO_MAX_GAIN);          if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -523,7 +515,7 @@ static void source_set_volume(pa_source *s) {      if (u->fd >= 0) {          AUDIO_INITINFO(&info); -        info.play.gain = pa_cvolume_avg(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; +        info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;          assert(info.play.gain <= AUDIO_MAX_GAIN);          if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -580,6 +572,25 @@ static void sink_get_mute(pa_sink *s) {      }  } +static void process_rewind(struct userdata *u) { +    size_t rewind_nbytes; + +    pa_assert(u); + +    /* Figure out how much we shall rewind and reset the counter */ +    rewind_nbytes = u->sink->thread_info.rewind_nbytes; +    u->sink->thread_info.rewind_nbytes = 0; + +    if (rewind_nbytes > 0) { +        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; +        pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); +    } + +    pa_sink_process_rewind(u->sink, rewind_nbytes); +} +  static void thread_func(void *userdata) {      struct userdata *u = userdata;      unsigned short revents = 0; @@ -594,7 +605,6 @@ static void thread_func(void *userdata) {          pa_make_realtime(u->core->realtime_priority);      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          /* Render some data and write it to the dsp */ @@ -604,10 +614,13 @@ static void thread_func(void *userdata) {              uint64_t buffered_bytes;              if (u->sink->thread_info.rewind_requested) -                pa_sink_process_rewind(u->sink, 0); +                process_rewind(u);              err = ioctl(u->fd, AUDIO_GETINFO, &info); -            pa_assert(err >= 0); +            if (err < 0) { +                pa_log("AUDIO_GETINFO ioctl failed: %s", pa_cstrerror(errno)); +                goto fail; +            }              if (info.play.error) {                  pa_log_debug("buffer under-run!"); @@ -627,7 +640,7 @@ static void thread_func(void *userdata) {                   * Since we cannot modify the size of the output buffer we fake it                   * by not filling it more than u->buffer_size.                   */ -                xtime0 = pa_rtclock_usec(); +                xtime0 = pa_rtclock_now();                  buffered_bytes = get_playback_buffered_bytes(u);                  if (buffered_bytes >= (uint64_t)u->buffer_size)                      break; @@ -635,7 +648,7 @@ static void thread_func(void *userdata) {                  len = u->buffer_size - buffered_bytes;                  len -= len % u->frame_size; -                if (len < u->min_request) +                if (len < (size_t) u->minimum_request)                      break;                  if (u->memchunk.length < len) @@ -648,12 +661,16 @@ static void thread_func(void *userdata) {                  if (w <= 0) {                      switch (errno) {                          case EINTR: -                            break; +                            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, (int32_t)MIN_BUFFER_SIZE); -                            pa_sink_set_max_request(u->sink, u->buffer_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: @@ -663,10 +680,8 @@ static void thread_func(void *userdata) {                  } else {                      pa_assert(w % u->frame_size == 0); -                    pa_mutex_lock(u->written_bytes_lock);                      u->written_bytes += w;                      u->memchunk.length -= w; -                    pa_mutex_unlock(u->written_bytes_lock);                      u->memchunk.index += w;                      if (u->memchunk.length <= 0) { @@ -677,9 +692,8 @@ 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)); -        } else { +        } else              pa_rtpoll_set_timer_disabled(u->rtpoll); -        }          /* Try to read some data and pass it on to the source driver */ @@ -783,7 +797,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); +        pa_sink_get_volume(u->sink, TRUE, FALSE);          pa_sink_get_mute(u->sink, TRUE);      } @@ -797,6 +811,7 @@ int pa__init(pa_module *m) {      pa_sample_spec ss;      pa_channel_map map;      pa_modargs *ma = NULL; +    uint32_t buffer_length_msec;      int fd;      pa_sink_new_data sink_new_data;      pa_source_new_data source_new_data; @@ -822,8 +837,6 @@ int pa__init(pa_module *m) {      }      u = pa_xnew0(struct userdata, 1); -    u->sample_counter_lock = pa_mutex_new(FALSE, FALSE); -    u->written_bytes_lock = pa_mutex_new(FALSE, FALSE);      /*       * For a process (or several processes) to use the same audio device for both @@ -839,13 +852,15 @@ int pa__init(pa_module *m) {      }      u->frame_size = pa_frame_size(&ss); -    u->buffer_size = 16384; -    if (pa_modargs_get_value_s32(ma, "buffer_size", &u->buffer_size) < 0) { -        pa_log("failed to parse buffer size argument"); +    u->minimum_request = pa_usec_to_bytes(PA_USEC_PER_SEC / MAX_RENDER_HZ, &ss); + +    buffer_length_msec = 100; +    if (pa_modargs_get_value_u32(ma, "buffer_length", &buffer_length_msec) < 0) { +        pa_log("failed to parse buffer_length argument");          goto fail;      } -    u->buffer_size -= u->buffer_size % u->frame_size; -    if (u->buffer_size < (int32_t)MIN_BUFFER_SIZE) { +    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");          goto fail;      } @@ -885,10 +900,16 @@ int pa__init(pa_module *m) {          pa_source_new_data_set_channel_map(&source_new_data, &map);          pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name);          pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "solaris"); -        pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM source"); +        pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM source");          pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "serial");          pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) u->buffer_size); +        if (pa_modargs_get_proplist(ma, "source_properties", source_new_data.proplist, PA_UPDATE_REPLACE) < 0) { +            pa_log("Invalid properties"); +            pa_source_new_data_done(&source_new_data); +            goto fail; +        } +          u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL);          pa_source_new_data_done(&source_new_data);          pa_xfree(name_buf); @@ -927,9 +948,15 @@ int pa__init(pa_module *m) {          pa_sink_new_data_set_channel_map(&sink_new_data, &map);          pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name);          pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "solaris"); -        pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM sink"); +        pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM sink");          pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "serial"); +        if (pa_modargs_get_proplist(ma, "sink_properties", sink_new_data.proplist, PA_UPDATE_REPLACE) < 0) { +            pa_log("Invalid properties"); +            pa_sink_new_data_done(&sink_new_data); +            goto fail; +        } +          u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL);          pa_sink_new_data_done(&sink_new_data); @@ -946,16 +973,18 @@ int pa__init(pa_module *m) {          u->sink->set_mute = sink_set_mute;          u->sink->refresh_volume = u->sink->refresh_muted = TRUE; -        u->sink->thread_info.max_request = u->buffer_size; -        u->min_request = pa_usec_to_bytes(PA_USEC_PER_SEC / MAX_RENDER_HZ, &ss); +        pa_sink_set_max_request(u->sink, u->buffer_size); +        pa_sink_set_max_rewind(u->sink, u->buffer_size);      } else          u->sink = NULL;      pa_assert(u->source || u->sink);      u->sig = pa_signal_new(SIGPOLL, sig_callback, u); -    pa_assert(u->sig); -    ioctl(u->fd, I_SETSIG, S_MSG); +    if (u->sig) +        ioctl(u->fd, I_SETSIG, S_MSG); +    else +        pa_log_warn("Could not register SIGPOLL handler");      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log("Failed to create thread."); @@ -1010,8 +1039,10 @@ void pa__done(pa_module *m) {      if (!(u = m->userdata))          return; -    ioctl(u->fd, I_SETSIG, 0); -    pa_signal_free(u->sig); +    if (u->sig) { +        ioctl(u->fd, I_SETSIG, 0); +        pa_signal_free(u->sig); +    }      if (u->sink)          pa_sink_unlink(u->sink); @@ -1044,9 +1075,6 @@ void pa__done(pa_module *m) {      if (u->fd >= 0)          close(u->fd); -    pa_mutex_free(u->written_bytes_lock); -    pa_mutex_free(u->sample_counter_lock); -      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 723b5d73..8c0bb6b0 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -30,12 +30,12 @@  #include <stdio.h>  #include <stdlib.h>  #include <ctype.h> -#include <gdbm.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> @@ -49,6 +49,7 @@  #include <pulsecore/protocol-native.h>  #include <pulsecore/pstream.h>  #include <pulsecore/pstream-util.h> +#include <pulsecore/database.h>  #include "module-stream-restore-symdef.h" @@ -59,15 +60,19 @@ PA_MODULE_LOAD_ONCE(TRUE);  PA_MODULE_USAGE(          "restore_device=<Save/restore sinks/sources?> "          "restore_volume=<Save/restore volumes?> " -        "restore_muted=<Save/restore muted states?>"); +        "restore_muted=<Save/restore muted states?> " +        "on_hotplug=<When new device becomes available, recheck streams?> " +        "on_rescue=<When device becomes unavailable, recheck streams?>"); -#define SAVE_INTERVAL 10 +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)  #define IDENTIFICATION_PROPERTY "module-stream-restore.id"  static const char* const valid_modargs[] = {      "restore_device",      "restore_volume",      "restore_muted", +    "on_hotplug", +    "on_rescue",      NULL  }; @@ -79,27 +84,32 @@ struct userdata {          *sink_input_new_hook_slot,          *sink_input_fixate_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; -    GDBM_FILE gdbm_file; +    pa_database* database;      pa_bool_t restore_device:1;      pa_bool_t restore_volume:1;      pa_bool_t restore_muted:1; +    pa_bool_t on_hotplug:1; +    pa_bool_t on_rescue:1;      pa_native_protocol *protocol;      pa_idxset *subscribed;  }; -#define ENTRY_VERSION 1 +#define ENTRY_VERSION 2  struct entry {      uint8_t version; -    pa_bool_t muted_valid:1, relative_volume_valid:1, absolute_volume_valid:1, device_valid:1; +    pa_bool_t muted_valid:1, volume_valid:1, device_valid:1;      pa_bool_t muted:1;      pa_channel_map channel_map; -    pa_cvolume relative_volume; -    pa_cvolume absolute_volume; +    pa_cvolume volume;      char device[PA_NAME_MAX];  } PA_GCC_PACKED; @@ -112,19 +122,18 @@ enum {      SUBCOMMAND_EVENT  }; -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +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(tv);      pa_assert(u);      pa_assert(e == u->save_time_event);      u->core->mainloop->time_free(u->save_time_event);      u->save_time_event = NULL; -    gdbm_sync(u->gdbm_file); +    pa_database_sync(u->database);      pa_log_info("Synced.");  } @@ -154,28 +163,28 @@ static char *get_name(pa_proplist *p, const char *prefix) {  }  static struct entry* read_entry(struct userdata *u, const char *name) { -    datum key, data; +    pa_datum key, data;      struct entry *e;      pa_assert(u);      pa_assert(name); -    key.dptr = (char*) name; -    key.dsize = (int) strlen(name); +    key.data = (char*) name; +    key.size = strlen(name); -    data = gdbm_fetch(u->gdbm_file, key); +    pa_zero(data); -    if (!data.dptr) +    if (!pa_database_get(u->database, &key, &data))          goto fail; -    if (data.dsize != sizeof(struct entry)) { +    if (data.size != sizeof(struct entry)) {          /* This is probably just a database upgrade, hence let's not           * consider this more than a debug message */ -        pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); +        pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry));          goto fail;      } -    e = (struct entry*) data.dptr; +    e = (struct entry*) data.data;      if (e->version != ENTRY_VERSION) {          pa_log_debug("Version of database entry for stream %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -192,13 +201,12 @@ static struct entry* read_entry(struct userdata *u, const char *name) {          goto fail;      } -    if ((e->relative_volume_valid || e->absolute_volume_valid) && !(pa_channel_map_valid(&e->channel_map))) { +    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;      } -    if ((e->relative_volume_valid && (!pa_cvolume_valid(&e->relative_volume) || e->relative_volume.channels != e->channel_map.channels)) || -        (e->absolute_volume_valid && (!pa_cvolume_valid(&e->absolute_volume) || e->absolute_volume.channels != e->channel_map.channels))) { +    if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) {          pa_log_warn("Invalid volume stored in database for stream %s", name);          goto fail;      } @@ -207,12 +215,11 @@ static struct entry* read_entry(struct userdata *u, const char *name) {  fail: -    pa_xfree(data.dptr); +    pa_datum_free(&data);      return NULL;  }  static void trigger_save(struct userdata *u) { -    struct timeval tv;      pa_native_connection *c;      uint32_t idx; @@ -232,9 +239,7 @@ static void trigger_save(struct userdata *u) {      if (u->save_time_event)          return; -    pa_gettimeofday(&tv); -    tv.tv_sec += SAVE_INTERVAL; -    u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); +    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) { @@ -251,14 +256,9 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {          (a->muted_valid && (a->muted != b->muted)))          return FALSE; -    t = b->relative_volume; -    if (a->relative_volume_valid != b->relative_volume_valid || -        (a->relative_volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->relative_volume))) -        return FALSE; - -    t = b->absolute_volume; -    if (a->absolute_volume_valid != b->absolute_volume_valid || -        (a->absolute_volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->absolute_volume))) +    t = b->volume; +    if (a->volume_valid != b->volume_valid || +        (a->volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->volume)))          return FALSE;      return TRUE; @@ -268,7 +268,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3      struct userdata *u = userdata;      struct entry entry, *old;      char *name; -    datum key, data; +    pa_datum key, data;      pa_assert(c);      pa_assert(u); @@ -279,7 +279,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))          return; -    memset(&entry, 0, sizeof(entry)); +    pa_zero(entry);      entry.version = ENTRY_VERSION;      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { @@ -291,24 +291,24 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          if (!(name = get_name(sink_input->proplist, "sink-input")))              return; -        entry.channel_map = sink_input->channel_map; - -        if (sink_input->sink->flags & PA_SINK_FLAT_VOLUME) { -            entry.absolute_volume = *pa_sink_input_get_volume(sink_input); -            entry.absolute_volume_valid = sink_input->save_volume; +        if ((old = read_entry(u, name))) +            entry = *old; -            pa_sw_cvolume_divide(&entry.relative_volume, &entry.absolute_volume, pa_sink_get_volume(sink_input->sink, FALSE)); -            entry.relative_volume_valid = sink_input->save_volume; -        } else { -            entry.relative_volume = *pa_sink_input_get_volume(sink_input); -            entry.relative_volume_valid = sink_input->save_volume; +        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;          } -        entry.muted = pa_sink_input_get_mute(sink_input); -        entry.muted_valid = sink_input->save_muted; +        if (sink_input->save_muted) { +            entry.muted = pa_sink_input_get_mute(sink_input); +            entry.muted_valid = TRUE; +        } -        pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device)); -        entry.device_valid = sink_input->save_sink; +        if (sink_input->save_sink) { +            pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device)); +            entry.device_valid = TRUE; +        }      } else {          pa_source_output *source_output; @@ -321,13 +321,16 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          if (!(name = get_name(source_output->proplist, "source-output")))              return; -        entry.channel_map = source_output->channel_map; +        if ((old = read_entry(u, name))) +            entry = *old; -        pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device)); -        entry.device_valid = source_output->save_source; +        if (source_output->save_source) { +            pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device)); +            entry.device_valid = source_output->save_source; +        }      } -    if ((old = read_entry(u, name))) { +    if (old) {          if (entries_equal(old, &entry)) {              pa_xfree(old); @@ -338,15 +341,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          pa_xfree(old);      } -    key.dptr = name; -    key.dsize = (int) strlen(name); +    key.data = name; +    key.size = strlen(name); -    data.dptr = (void*) &entry; -    data.dsize = sizeof(entry); +    data.data = &entry; +    data.size = sizeof(entry);      pa_log_info("Storing volume/mute/device for stream %s.", name); -    gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); +    pa_database_set(u->database, &key, &data, TRUE);      pa_xfree(name); @@ -357,18 +360,18 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n      char *name;      struct entry *e; +    pa_assert(c);      pa_assert(new_data); - -    if (!u->restore_device) -        return PA_HOOK_OK; +    pa_assert(u); +    pa_assert(u->restore_device);      if (!(name = get_name(new_data->proplist, "sink-input")))          return PA_HOOK_OK;      if ((e = read_entry(u, name))) { -        pa_sink *s;          if (e->device_valid) { +            pa_sink *s;              if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK))) {                  if (!new_data->sink) { @@ -376,7 +379,7 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n                      new_data->sink = s;                      new_data->save_sink = TRUE;                  } else -                    pa_log_info("Not restore device for stream %s, because already set.", name); +                    pa_log_debug("Not restoring device for stream %s, because already set.", name);              }          } @@ -392,52 +395,35 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu      char *name;      struct entry *e; +    pa_assert(c);      pa_assert(new_data); - -    if (!u->restore_volume && !u->restore_muted) -        return PA_HOOK_OK; +    pa_assert(u); +    pa_assert(u->restore_volume || u->restore_muted);      if (!(name = get_name(new_data->proplist, "sink-input")))          return PA_HOOK_OK;      if ((e = read_entry(u, name))) { -        if (u->restore_volume) { +        if (u->restore_volume && e->volume_valid) {              if (!new_data->volume_is_set) {                  pa_cvolume v; -                pa_cvolume_init(&v); - -                if (new_data->sink->flags & PA_SINK_FLAT_VOLUME) { - -                    /* We don't check for e->device_valid here because -                    that bit marks whether it is a good choice for -                    restoring, not just if the data is filled in. */ -                    if (e->absolute_volume_valid && -                        (e->device[0] == 0 || pa_streq(new_data->sink->name, e->device))) { - -                        v = e->absolute_volume; -                        new_data->volume_is_absolute = TRUE; -                    } else if (e->relative_volume_valid) { -                        v = e->relative_volume; -                        new_data->volume_is_absolute = FALSE; -                    } - -                } else if (e->relative_volume_valid) { -                    v = e->relative_volume; -                    new_data->volume_is_absolute = FALSE; -                } -                if (v.channels > 0) { -                    pa_log_info("Restoring volume for sink input %s.", name); -                    pa_sink_input_new_data_set_volume(new_data, pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map)); -                    new_data->save_volume = TRUE; -                } +                pa_log_info("Restoring volume for sink input %s.", name); + +                v = e->volume; +                pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); +                pa_sink_input_new_data_set_volume(new_data, &v); + +                new_data->volume_is_absolute = FALSE; +                new_data->save_volume = TRUE;              } else                  pa_log_debug("Not restoring volume for sink input %s, because already set.", name);          }          if (u->restore_muted && e->muted_valid) { +              if (!new_data->muted_is_set) {                  pa_log_info("Restoring mute state for sink input %s.", name);                  pa_sink_input_new_data_set_muted(new_data, e->muted); @@ -458,10 +444,10 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou      char *name;      struct entry *e; +    pa_assert(c);      pa_assert(new_data); - -    if (!u->restore_device) -        return PA_HOOK_OK; +    pa_assert(u); +    pa_assert(u->restore_device);      if (new_data->direct_on_input)          return PA_HOOK_OK; @@ -479,7 +465,7 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou                      new_data->source = s;                      new_data->save_source = TRUE;                  } else -                    pa_log_info("Not restoring device for stream %s, because already set", name); +                    pa_log_debug("Not restoring device for stream %s, because already set", name);              }          } @@ -491,27 +477,157 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou      return PA_HOOK_OK;  } -#define EXT_VERSION 1 +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { +    pa_sink_input *si; +    uint32_t idx; -static void clear_db(struct userdata *u) { -    datum key; +    pa_assert(c); +    pa_assert(sink); +    pa_assert(u); +    pa_assert(u->on_hotplug && u->restore_device); + +    PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { +        char *name; +        struct entry *e; + +        if (si->sink == sink) +            continue; + +        if (si->save_sink) +            continue; +        if (!(name = get_name(si->proplist, "sink-input"))) +            continue; + +        if ((e = read_entry(u, name))) { +            if (e->device_valid && pa_streq(e->device, sink->name)) +                pa_sink_input_move_to(si, sink, TRUE); + +            pa_xfree(e); +        } + +        pa_xfree(name); +    } + +    return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { +    pa_source_output *so; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(source);      pa_assert(u); +    pa_assert(u->on_hotplug && u->restore_device); + +    PA_IDXSET_FOREACH(so, c->source_outputs, idx) { +        char *name; +        struct entry *e; -    key = gdbm_firstkey(u->gdbm_file); -    while (key.dptr) { -        datum next_key; -        next_key = gdbm_nextkey(u->gdbm_file, key); +        if (so->source == source) +            continue; -        gdbm_delete(u->gdbm_file, key); -        pa_xfree(key.dptr); +        if (so->save_source) +            continue; -        key = next_key; +        if (so->direct_on_input) +            continue; + +        if (!(name = get_name(so->proplist, "source-input"))) +            continue; + +        if ((e = read_entry(u, name))) { +            if (e->device_valid && pa_streq(e->device, source->name)) +                pa_source_output_move_to(so, source, TRUE); + +            pa_xfree(e); +        } + +        pa_xfree(name);      } -    gdbm_reorganize(u->gdbm_file); +    return PA_HOOK_OK;  } +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { +    pa_sink_input *si; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(sink); +    pa_assert(u); +    pa_assert(u->on_rescue && u->restore_device); + +    /* There's no point in doing anything if the core is shut down anyway */ +    if (c->state == PA_CORE_SHUTDOWN) +        return PA_HOOK_OK; + +    PA_IDXSET_FOREACH(si, sink->inputs, idx) { +        char *name; +        struct entry *e; + +        if (!(name = get_name(si->proplist, "sink-input"))) +            continue; + +        if ((e = read_entry(u, name))) { + +            if (e->device_valid) { +                pa_sink *d; + +                if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && d != sink) +                    pa_sink_input_move_to(si, d, TRUE); +            } + +            pa_xfree(e); +        } + +        pa_xfree(name); +    } + +    return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { +    pa_source_output *so; +    uint32_t idx; + +    pa_assert(c); +    pa_assert(source); +    pa_assert(u); +    pa_assert(u->on_rescue && u->restore_device); + +    /* There's no point in doing anything if the core is shut down anyway */ +    if (c->state == PA_CORE_SHUTDOWN) +        return PA_HOOK_OK; + +    PA_IDXSET_FOREACH(so, source->outputs, idx) { +        char *name; +        struct entry *e; + +        if (!(name = get_name(so->proplist, "source-output"))) +            continue; + +        if ((e = read_entry(u, name))) { + +            if (e->device_valid) { +                pa_source *d; + +                if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && d != source) +                    pa_source_output_move_to(so, d, TRUE); +            } + +            pa_xfree(e); +        } + +        pa_xfree(name); +    } + +    return PA_HOOK_OK; +} + +#define EXT_VERSION 1 +  static void apply_entry(struct userdata *u, const char *name, struct entry *e) {      pa_sink_input *si;      pa_source_output *so; @@ -521,43 +637,29 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) {      pa_assert(name);      pa_assert(e); -    for (si = pa_idxset_first(u->core->sink_inputs, &idx); si; si = pa_idxset_next(u->core->sink_inputs, &idx)) { +    PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {          char *n;          pa_sink *s;          if (!(n = get_name(si->proplist, "sink-input")))              continue; -        if (strcmp(name, n)) { +        if (!pa_streq(name, n)) {              pa_xfree(n);              continue;          } -	pa_xfree(n); +        pa_xfree(n); -        if (u->restore_volume) { +        if (u->restore_volume && e->volume_valid) {              pa_cvolume v; -            pa_cvolume_init(&v); -            if (si->sink->flags & PA_SINK_FLAT_VOLUME) { - -                if (e->absolute_volume_valid && -                    (e->device[0] == 0 || pa_streq(e->device, si->sink->name))) -                    v = e->absolute_volume; -                else if (e->relative_volume_valid) { -                    pa_cvolume t = *pa_sink_get_volume(si->sink, FALSE); -                    pa_sw_cvolume_multiply(&v, &e->relative_volume, pa_cvolume_remap(&t, &si->sink->channel_map, &e->channel_map)); -                } -            } else if (e->relative_volume_valid) -                v = e->relative_volume; - -            if (v.channels > 0) { -                pa_log_info("Restoring volume for sink input %s.", name); -                pa_sink_input_set_volume(si, pa_cvolume_remap(&v, &e->channel_map, &si->channel_map), TRUE); -            } +            v = e->volume; +            pa_log_info("Restoring volume for sink input %s.", name); +            pa_cvolume_remap(&v, &e->channel_map, &si->channel_map); +            pa_sink_input_set_volume(si, &v, TRUE, FALSE);          } -        if (u->restore_muted && -            e->muted_valid) { +        if (u->restore_muted && e->muted_valid) {              pa_log_info("Restoring mute state for sink input %s.", name);              pa_sink_input_set_mute(si, e->muted, TRUE);          } @@ -571,18 +673,18 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) {          }      } -    for (so = pa_idxset_first(u->core->source_outputs, &idx); so; so = pa_idxset_next(u->core->source_outputs, &idx)) { +    PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {          char *n;          pa_source *s;          if (!(n = get_name(so->proplist, "source-output")))              continue; -        if (strcmp(name, n)) { +        if (!pa_streq(name, n)) {              pa_xfree(n);              continue;          } -	pa_xfree(n); +        pa_xfree(n);          if (u->restore_device &&              e->device_valid && @@ -596,26 +698,28 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) {  #if 0  static void dump_database(struct userdata *u) { -    datum key; +    pa_datum key; +    pa_bool_t done; + +    done = !pa_database_first(u->database, &key, NULL); -    key = gdbm_firstkey(u->gdbm_file); -    while (key.dptr) { -        datum next_key; +    while (!done) { +        pa_datum next_key;          struct entry *e;          char *name; -        next_key = gdbm_nextkey(u->gdbm_file, key); +        done = !pa_database_next(u->database, &key, &next_key, NULL); -        name = pa_xstrndup(key.dptr, key.dsize); -        pa_xfree(key.dptr); +        name = pa_xstrndup(key.data, key.size); +        pa_datum_free(&key);          if ((e = read_entry(u, name))) {              char t[256];              pa_log("name=%s", name); -            pa_log("device=%s", e->device); +            pa_log("device=%s %s", e->device, pa_yes_no(e->device_valid));              pa_log("channel_map=%s", pa_channel_map_snprint(t, sizeof(t), &e->channel_map)); -            pa_log("volume=%s", pa_cvolume_snprint(t, sizeof(t), &e->volume)); -            pa_log("mute=%s", pa_yes_no(e->muted)); +            pa_log("volume=%s %s", pa_cvolume_snprint(t, sizeof(t), &e->volume), pa_yes_no(e->volume_valid)); +            pa_log("mute=%s %s", pa_yes_no(e->muted), pa_yes_no(e->volume_valid));              pa_xfree(e);          } @@ -655,29 +759,31 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio          }          case SUBCOMMAND_READ: { -            datum key; +            pa_datum key; +            pa_bool_t done;              if (!pa_tagstruct_eof(t))                  goto fail; -            key = gdbm_firstkey(u->gdbm_file); -            while (key.dptr) { -                datum next_key; +            done = !pa_database_first(u->database, &key, NULL); + +            while (!done) { +                pa_datum next_key;                  struct entry *e;                  char *name; -                next_key = gdbm_nextkey(u->gdbm_file, key); +                done = !pa_database_next(u->database, &key, &next_key, NULL); -                name = pa_xstrndup(key.dptr, (size_t) key.dsize); -                pa_xfree(key.dptr); +                name = pa_xstrndup(key.data, key.size); +                pa_datum_free(&key);                  if ((e = read_entry(u, name))) {                      pa_cvolume r;                      pa_channel_map cm;                      pa_tagstruct_puts(reply, name); -                    pa_tagstruct_put_channel_map(reply, (e->relative_volume_valid || e->absolute_volume_valid) ? &e->channel_map : pa_channel_map_init(&cm)); -                    pa_tagstruct_put_cvolume(reply, e->absolute_volume_valid ? &e->absolute_volume : (e->relative_volume_valid ? &e->relative_volume : pa_cvolume_init(&r))); +                    pa_tagstruct_put_channel_map(reply, e->volume_valid ? &e->channel_map : pa_channel_map_init(&cm)); +                    pa_tagstruct_put_cvolume(reply, e->volume_valid ? &e->volume : pa_cvolume_init(&r));                      pa_tagstruct_puts(reply, e->device_valid ? e->device : NULL);                      pa_tagstruct_put_boolean(reply, e->muted_valid ? e->muted : FALSE); @@ -706,21 +812,20 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio                  goto fail;              if (mode == PA_UPDATE_SET) -                clear_db(u); +                pa_database_clear(u->database);              while (!pa_tagstruct_eof(t)) {                  const char *name, *device;                  pa_bool_t muted;                  struct entry entry; -                datum key, data; -                int k; +                pa_datum key, data; -                memset(&entry, 0, sizeof(entry)); +                pa_zero(entry);                  entry.version = ENTRY_VERSION;                  if (pa_tagstruct_gets(t, &name) < 0 ||                      pa_tagstruct_get_channel_map(t, &entry.channel_map) || -                    pa_tagstruct_get_cvolume(t, &entry.absolute_volume) < 0 || +                    pa_tagstruct_get_cvolume(t, &entry.volume) < 0 ||                      pa_tagstruct_gets(t, &device) < 0 ||                      pa_tagstruct_get_boolean(t, &muted) < 0)                      goto fail; @@ -728,11 +833,10 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio                  if (!name || !*name)                      goto fail; -                entry.relative_volume = entry.absolute_volume; -                entry.absolute_volume_valid = entry.relative_volume_valid = entry.relative_volume.channels > 0; +                entry.volume_valid = entry.volume.channels > 0; -                if (entry.relative_volume_valid) -                    if (!pa_cvolume_compatible_with_channel_map(&entry.relative_volume, &entry.channel_map)) +                if (entry.volume_valid) +                    if (!pa_cvolume_compatible_with_channel_map(&entry.volume, &entry.channel_map))                          goto fail;                  entry.muted = muted; @@ -746,13 +850,13 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio                      !pa_namereg_is_valid_name(entry.device))                      goto fail; -                key.dptr = (void*) name; -                key.dsize = (int) strlen(name); +                key.data = (char*) name; +                key.size = strlen(name); -                data.dptr = (void*) &entry; -                data.dsize = sizeof(entry); +                data.data = &entry; +                data.size = sizeof(entry); -                if ((k = gdbm_store(u->gdbm_file, key, data, mode == PA_UPDATE_REPLACE ? GDBM_REPLACE : GDBM_INSERT)) == 0) +                if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0)                      if (apply_immediately)                          apply_entry(u, name, &entry);              } @@ -766,15 +870,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio              while (!pa_tagstruct_eof(t)) {                  const char *name; -                datum key; +                pa_datum key;                  if (pa_tagstruct_gets(t, &name) < 0)                      goto fail; -                key.dptr = (void*) name; -                key.dsize = (int) strlen(name); +                key.data = (char*) name; +                key.size = strlen(name); -                gdbm_delete(u->gdbm_file, key); +                pa_database_unset(u->database, &key);              }              trigger_save(u); @@ -824,12 +928,11 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati  int pa__init(pa_module*m) {      pa_modargs *ma = NULL;      struct userdata *u; -    char *fname, *fn; +    char *fname;      pa_sink_input *si;      pa_source_output *so;      uint32_t idx; -    pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE; -    int gdbm_cache_size; +    pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE, on_hotplug = TRUE, on_rescue = TRUE;      pa_assert(m); @@ -840,22 +943,24 @@ int pa__init(pa_module*m) {      if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 ||          pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0 || -        pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0) { -        pa_log("restore_device=, restore_volume= and restore_muted= expect boolean arguments"); +        pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 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("restore_device=, restore_volume=, restore_muted=, on_hotplug= and on_rescue= expect boolean arguments");          goto fail;      }      if (!restore_muted && !restore_volume && !restore_device)          pa_log_warn("Neither restoring volume, nor restoring muted, nor restoring device enabled!"); -    m->userdata = u = pa_xnew(struct userdata, 1); +    m->userdata = u = pa_xnew0(struct userdata, 1);      u->core = m->core;      u->module = m; -    u->save_time_event = NULL;      u->restore_device = restore_device;      u->restore_volume = restore_volume;      u->restore_muted = restore_muted; -    u->gdbm_file = NULL; +    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); @@ -866,41 +971,42 @@ int pa__init(pa_module*m) {      u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);      if (restore_device) { +        /* A little bit earlier than module-intended-roles ... */          u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (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, (pa_hook_cb_t) source_output_new_hook_callback, u);      } -    if (restore_volume || restore_muted) -        u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); +    if (restore_device && on_hotplug) { +        /* A little bit earlier than module-intended-roles ... */ +        u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (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, (pa_hook_cb_t) source_put_hook_callback, u); +    } -    /* We include the host identifier in the file name because gdbm -     * files are CPU dependant, and we don't want things to go wrong -     * if we are on a multiarch system. */ +    if (restore_device && on_rescue) { +        /* 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, (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, (pa_hook_cb_t) source_unlink_hook_callback, u); +    } -    fn = pa_sprintf_malloc("stream-volumes."CANONICAL_HOST".gdbm"); -    fname = pa_state_path(fn, TRUE); -    pa_xfree(fn); +    if (restore_volume || restore_muted) +        u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); -    if (!fname) +    if (!(fname = pa_state_path("stream-volumes", TRUE)))          goto fail; -    if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { -        pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); +    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;      } -    /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ -    gdbm_cache_size = 10; -    gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); -      pa_log_info("Sucessfully opened database file '%s'.", fname);      pa_xfree(fname); -    for (si = pa_idxset_first(m->core->sink_inputs, &idx); si; si = pa_idxset_next(m->core->sink_inputs, &idx)) +    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); -    for (so = pa_idxset_first(m->core->source_outputs, &idx); so; so = pa_idxset_next(m->core->source_outputs, &idx)) +    PA_IDXSET_FOREACH(so, m->core->source_outputs, idx)          subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, so->index, u);      pa_modargs_free(ma); @@ -933,14 +1039,24 @@ void pa__done(pa_module*m) {      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->connection_unlink_hook_slot)          pa_hook_slot_free(u->connection_unlink_hook_slot);      if (u->save_time_event)          u->core->mainloop->time_free(u->save_time_event); -    if (u->gdbm_file) -        gdbm_close(u->gdbm_file); +    if (u->database) +        pa_database_close(u->database);      if (u->protocol) {          pa_native_protocol_remove_ext(u->protocol, m); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 7e17f8f7..70a7b049 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -25,8 +25,10 @@  #include <pulse/xmalloc.h>  #include <pulse/timeval.h> +#include <pulse/rtclock.h>  #include <pulsecore/core.h> +#include <pulsecore/core-util.h>  #include <pulsecore/sink-input.h>  #include <pulsecore/source-output.h>  #include <pulsecore/modargs.h> @@ -74,41 +76,48 @@ struct device_info {      struct userdata *userdata;      pa_sink *sink;      pa_source *source; -    struct timeval last_use; +    pa_usec_t last_use;      pa_time_event *time_event;  }; -static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {      struct device_info *d = userdata;      pa_assert(d);      d->userdata->core->mainloop->time_restart(d->time_event, NULL); -    if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { +    if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && !(d->sink->suspend_cause & PA_SUSPEND_IDLE)) {          pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); -        pa_sink_suspend(d->sink, TRUE); +        pa_sink_suspend(d->sink, TRUE, PA_SUSPEND_IDLE);      } -    if (d->source && pa_source_check_suspend(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { +    if (d->source && pa_source_check_suspend(d->source) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) {          pa_log_info("Source %s idle for too long, suspending ...", d->source->name); -        pa_source_suspend(d->source, TRUE); +        pa_source_suspend(d->source, TRUE, PA_SUSPEND_IDLE);      }  }  static void restart(struct device_info *d) { -    struct timeval tv; +    pa_usec_t now; +    const char *s; +    uint32_t timeout; +      pa_assert(d); +    pa_assert(d->sink || d->source); + +    d->last_use = now = pa_rtclock_now(); + +    s = pa_proplist_gets(d->sink ? d->sink->proplist : d->source->proplist, "module-suspend-on-idle.timeout"); +    if (!s || pa_atou(s, &timeout) < 0) +        timeout = d->userdata->timeout; -    pa_gettimeofday(&tv); -    d->last_use = tv; -    pa_timeval_add(&tv, d->userdata->timeout*1000000); -    d->userdata->core->mainloop->time_restart(d->time_event, &tv); +    pa_core_rttime_restart(d->userdata->core, d->time_event, now + timeout * PA_USEC_PER_SEC);      if (d->sink) -        pa_log_debug("Sink %s becomes idle.", d->sink->name); +        pa_log_debug("Sink %s becomes idle, timeout in %u seconds.", d->sink->name, timeout);      if (d->source) -        pa_log_debug("Source %s becomes idle.", d->source->name); +        pa_log_debug("Source %s becomes idle, timeout in %u seconds.", d->source->name, timeout);  }  static void resume(struct device_info *d) { @@ -117,13 +126,13 @@ static void resume(struct device_info *d) {      d->userdata->core->mainloop->time_restart(d->time_event, NULL);      if (d->sink) { -        pa_sink_suspend(d->sink, FALSE); +        pa_sink_suspend(d->sink, FALSE, PA_SUSPEND_IDLE);          pa_log_debug("Sink %s becomes busy.", d->sink->name);      }      if (d->source) { -        pa_source_suspend(d->source, FALSE); +        pa_source_suspend(d->source, FALSE, PA_SUSPEND_IDLE);          pa_log_debug("Source %s becomes busy.", d->source->name);      } @@ -328,7 +337,7 @@ static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct user      d->userdata = u;      d->source = source ? pa_source_ref(source) : NULL;      d->sink = sink ? pa_sink_ref(sink) : NULL; -    d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d); +    d->time_event = pa_core_rttime_new(c, PA_USEC_INVALID, timeout_cb, d);      pa_hashmap_put(u->device_infos, o, d);      if ((d->sink && pa_sink_check_suspend(d->sink) <= 0) || diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index c1488841..d1153829 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -31,6 +31,7 @@  #include <stdio.h>  #include <stdlib.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/util.h>  #include <pulse/version.h> @@ -50,7 +51,7 @@  #include <pulsecore/time-smoother.h>  #include <pulsecore/thread.h>  #include <pulsecore/thread-mq.h> -#include <pulsecore/rtclock.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-error.h>  #include <pulsecore/proplist-util.h>  #include <pulsecore/auth-cookie.h> @@ -64,24 +65,26 @@  #ifdef TUNNEL_SINK  PA_MODULE_DESCRIPTION("Tunnel module for sinks");  PA_MODULE_USAGE( +        "sink_name=<name for the local sink> " +        "sink_properties=<properties for the local sink> "          "server=<address> "          "sink=<remote sink name> "          "cookie=<filename> "          "format=<sample format> "          "channels=<number of channels> "          "rate=<sample rate> " -        "sink_name=<name for the local sink> "          "channel_map=<channel map>");  #else  PA_MODULE_DESCRIPTION("Tunnel module for sources");  PA_MODULE_USAGE( +        "source_name=<name for the local source> " +        "source_properties=<properties for the local source> "          "server=<address> "          "source=<remote source name> "          "cookie=<filename> "          "format=<sample format> "          "channels=<number of channels> "          "rate=<sample rate> " -        "source_name=<name for the local source> "          "channel_map=<channel map>");  #endif @@ -97,9 +100,11 @@ static const char* const valid_modargs[] = {      "rate",  #ifdef TUNNEL_SINK      "sink_name", +    "sink_properties",      "sink",  #else      "source_name", +    "source_properties",      "source",  #endif      "channel_map", @@ -108,7 +113,7 @@ static const char* const valid_modargs[] = {  #define DEFAULT_TIMEOUT 5 -#define LATENCY_INTERVAL 10 +#define LATENCY_INTERVAL (10*PA_USEC_PER_SEC)  #define MIN_NETWORK_LATENCY_USEC (8*PA_USEC_PER_MSEC) @@ -145,6 +150,8 @@ static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t t  static void command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static void command_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_stream_or_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_stream_buffer_attr_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {  #ifdef TUNNEL_SINK @@ -159,7 +166,12 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspended,      [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspended,      [PA_COMMAND_PLAYBACK_STREAM_MOVED] = command_moved, -    [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved +    [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved, +    [PA_COMMAND_PLAYBACK_STREAM_EVENT] = command_stream_or_client_event, +    [PA_COMMAND_RECORD_STREAM_EVENT] = command_stream_or_client_event, +    [PA_COMMAND_CLIENT_EVENT] = command_stream_or_client_event, +    [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = command_stream_buffer_attr_changed, +    [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = command_stream_buffer_attr_changed  };  struct userdata { @@ -196,8 +208,8 @@ struct userdata {      pa_bool_t remote_corked:1;      pa_bool_t remote_suspended:1; -    pa_usec_t transport_usec; -    pa_bool_t transport_usec_valid; +    pa_usec_t transport_usec; /* maintained in the main thread */ +    pa_usec_t thread_transport_usec; /* maintained in the IO thread */      uint32_t ignore_latency_before; @@ -222,6 +234,11 @@ struct userdata {  static void request_latency(struct userdata *u);  /* Called from main context */ +static void command_stream_or_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { +    pa_log_debug("Got stream or client event."); +} + +/* Called from main context */  static void command_stream_killed(pa_pdispatch *pd,  uint32_t command,  uint32_t tag, pa_tagstruct *t, void *userdata) {      struct userdata *u = userdata; @@ -261,11 +278,14 @@ static void command_suspended(pa_pdispatch *pd,  uint32_t command,  uint32_t tag      if (pa_tagstruct_getu32(t, &channel) < 0 ||          pa_tagstruct_get_boolean(t, &suspended) < 0 ||          !pa_tagstruct_eof(t)) { -        pa_log("Invalid packet"); + +        pa_log("Invalid packet.");          pa_module_unload_request(u->module, TRUE);          return;      } +    pa_log_debug("Server reports device suspend."); +  #ifdef TUNNEL_SINK      pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL);  #else @@ -278,13 +298,78 @@ static void command_suspended(pa_pdispatch *pd,  uint32_t command,  uint32_t tag  /* Called from main context */  static void command_moved(pa_pdispatch *pd,  uint32_t command,  uint32_t tag, pa_tagstruct *t, void *userdata) {      struct userdata *u = userdata; +    uint32_t channel, di; +    const char *dn; +    pa_bool_t suspended;      pa_assert(pd);      pa_assert(t);      pa_assert(u);      pa_assert(u->pdispatch == pd); +    if (pa_tagstruct_getu32(t, &channel) < 0 || +        pa_tagstruct_getu32(t, &di) < 0 || +        pa_tagstruct_gets(t, &dn) < 0 || +        pa_tagstruct_get_boolean(t, &suspended) < 0) { + +        pa_log_error("Invalid packet."); +        pa_module_unload_request(u->module, TRUE); +        return; +    } +      pa_log_debug("Server reports a stream move."); + +#ifdef TUNNEL_SINK +    pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); +#else +    pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); +#endif + +    request_latency(u); +} + +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; +    pa_usec_t usec; + +    pa_assert(pd); +    pa_assert(t); +    pa_assert(u); +    pa_assert(u->pdispatch == pd); + +    if (pa_tagstruct_getu32(t, &channel) < 0 || +        pa_tagstruct_getu32(t, &maxlength) < 0) { + +        pa_log_error("Invalid packet."); +        pa_module_unload_request(u->module, TRUE); +        return; +    } + +    if (command == PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED) { +        if (pa_tagstruct_getu32(t, &fragsize) < 0 || +            pa_tagstruct_get_usec(t, &usec) < 0) { + +            pa_log_error("Invalid packet."); +            pa_module_unload_request(u->module, TRUE); +            return; +        } +    } else { +        if (pa_tagstruct_getu32(t, &tlength) < 0 || +            pa_tagstruct_getu32(t, &prebuf) < 0 || +            pa_tagstruct_getu32(t, &minreq) < 0 || +            pa_tagstruct_get_usec(t, &usec) < 0) { + +            pa_log_error("Invalid packet."); +            pa_module_unload_request(u->module, TRUE); +            return; +        } +    } + +#ifdef TUNNEL_SINK +    pa_log_debug("Server reports buffer attrs changed. tlength now at %lu, before %lu.", (unsigned long) tlength, (unsigned long) u->tlength); +#endif +      request_latency(u);  } @@ -306,26 +391,37 @@ static void command_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa  #endif  /* Called from IO thread context */ -static void stream_cork_within_thread(struct userdata *u, pa_bool_t cork) { +static void check_smoother_status(struct userdata *u, pa_bool_t past)  {      pa_usec_t x; +      pa_assert(u); -    if (u->remote_corked == cork) -        return; +    x = pa_rtclock_now(); -    u->remote_corked = cork; -    x = pa_rtclock_usec(); +    /* Correct by the time the requested issued needs to travel to the +     * other side.  This is a valid thread-safe access, because the +     * main thread is waiting for us */ -    /* Correct by the time this needs to travel to the other side. -     * This is a valid thread-safe access, because the main thread is -     * waiting for us */ -    if (u->transport_usec_valid) -        x += u->transport_usec; +    if (past) +        x -= u->thread_transport_usec; +    else +        x += u->thread_transport_usec;      if (u->remote_suspended || u->remote_corked)          pa_smoother_pause(u->smoother, x);      else -        pa_smoother_resume(u->smoother, x); +        pa_smoother_resume(u->smoother, x, TRUE); +} + +/* Called from IO thread context */ +static void stream_cork_within_thread(struct userdata *u, pa_bool_t cork) { +    pa_assert(u); + +    if (u->remote_corked == cork) +        return; + +    u->remote_corked = cork; +    check_smoother_status(u, FALSE);  }  /* Called from main context */ @@ -352,26 +448,13 @@ static void stream_cork(struct userdata *u, pa_bool_t cork) {  /* Called from IO thread context */  static void stream_suspend_within_thread(struct userdata *u, pa_bool_t suspend) { -    pa_usec_t x;      pa_assert(u);      if (u->remote_suspended == suspend)          return;      u->remote_suspended = suspend; - -    x = pa_rtclock_usec(); - -    /* Correct by the time this needed to travel from the other side. -     * This is a valid thread-safe access, because the main thread is -     * waiting for us */ -    if (u->transport_usec_valid) -        x -= u->transport_usec; - -    if (u->remote_suspended || u->remote_corked) -        pa_smoother_pause(u->smoother, x); -    else -        pa_smoother_resume(u->smoother, x); +    check_smoother_status(u, TRUE);  }  #ifdef TUNNEL_SINK @@ -418,7 +501,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse              pa_usec_t yl, yr, *usec = data;              yl = pa_bytes_to_usec((uint64_t) u->counter, &u->sink->sample_spec); -            yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); +            yr = pa_smoother_get(u->smoother, pa_rtclock_now());              *usec = yl > yr ? yl - yr : 0;              return 0; @@ -446,12 +529,15 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse              y = pa_bytes_to_usec((uint64_t) u->counter, &u->sink->sample_spec); -            if (y > (pa_usec_t) offset || offset < 0) +            if (y > (pa_usec_t) offset)                  y -= (pa_usec_t) offset;              else                  y = 0; -            pa_smoother_put(u->smoother, pa_rtclock_usec(), y); +            pa_smoother_put(u->smoother, pa_rtclock_now(), y); + +            /* We can access this freely here, since the main thread is waiting for us */ +            u->thread_transport_usec = u->transport_usec;              return 0;          } @@ -522,7 +608,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off              pa_usec_t yr, yl, *usec = data;              yl = pa_bytes_to_usec((uint64_t) u->counter, &PA_SOURCE(o)->sample_spec); -            yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); +            yr = pa_smoother_get(u->smoother, pa_rtclock_now());              *usec = yr > yl ? yr - yl : 0;              return 0; @@ -546,13 +632,12 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off              pa_usec_t y;              y = pa_bytes_to_usec((uint64_t) u->counter, &u->source->sample_spec); +            y += (pa_usec_t) offset; -            if (offset >= 0 || y > (pa_usec_t) -offset) -                y += (pa_usec_t) offset; -            else -                y = 0; +            pa_smoother_put(u->smoother, pa_rtclock_now(), y); -            pa_smoother_put(u->smoother, pa_rtclock_usec(), y); +            /* We can access this freely here, since the main thread is waiting for us */ +            u->thread_transport_usec = u->transport_usec;              return 0;          } @@ -599,14 +684,13 @@ static void thread_func(void *userdata) {      pa_log_debug("Thread starting up");      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          int ret;  #ifdef TUNNEL_SINK          if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) -            if (u->sink->thread_info.rewind_requested) +            if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))                  pa_sink_process_rewind(u->sink, 0);  #endif @@ -646,7 +730,7 @@ static void command_request(pa_pdispatch *pd, uint32_t command,  uint32_t tag, p      }      if (channel != u->channel) { -        pa_log("Recieved data for invalid channel"); +        pa_log("Received data for invalid channel");          goto fail;      } @@ -662,7 +746,7 @@ fail:  /* Called from main context */  static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      struct userdata *u = userdata; -    pa_usec_t sink_usec, source_usec, transport_usec = 0; +    pa_usec_t sink_usec, source_usec;      pa_bool_t playing;      int64_t write_index, read_index;      struct timeval local, remote, now; @@ -709,7 +793,6 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint      }      if (tag < u->ignore_latency_before) { -        request_latency(u);          return;      } @@ -725,7 +808,6 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint  #endif      } else          u->transport_usec = pa_timeval_diff(&now, &local)/2; -    u->transport_usec_valid = TRUE;      /* First, take the device's delay */  #ifdef TUNNEL_SINK @@ -745,9 +827,9 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint      /* Our measurements are already out of date, hence correct by the     *       * transport latency */  #ifdef TUNNEL_SINK -    delay -= (int64_t) transport_usec; +    delay -= (int64_t) u->transport_usec;  #else -    delay += (int64_t) transport_usec; +    delay += (int64_t) u->transport_usec;  #endif      /* Now correct by what we have have read/written since we requested the update */ @@ -786,8 +868,7 @@ static void request_latency(struct userdata *u) {      pa_tagstruct_putu32(t, tag = u->ctag++);      pa_tagstruct_putu32(t, u->channel); -    pa_gettimeofday(&now); -    pa_tagstruct_put_timeval(t, &now); +    pa_tagstruct_put_timeval(t, pa_gettimeofday(&now));      pa_pstream_send_tagstruct(u->pstream, t);      pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u, NULL); @@ -797,9 +878,8 @@ static void request_latency(struct userdata *u) {  }  /* Called from main context */ -static void timeout_callback(pa_mainloop_api *m, pa_time_event*e,  const struct timeval *tv, void *userdata) { +static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      struct userdata *u = userdata; -    struct timeval ntv;      pa_assert(m);      pa_assert(e); @@ -807,9 +887,7 @@ static void timeout_callback(pa_mainloop_api *m, pa_time_event*e,  const struct      request_latency(u); -    pa_gettimeofday(&ntv); -    ntv.tv_sec += LATENCY_INTERVAL; -    m->time_restart(e, &ntv); +    pa_core_rttime_restart(u->core, e, pa_rtclock_now() + LATENCY_INTERVAL);  }  /* Called from main context */ @@ -861,6 +939,7 @@ static void update_description(struct userdata *u) {  static void server_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag, pa_tagstruct *t, void *userdata) {      struct userdata *u = userdata;      pa_sample_spec ss; +    pa_channel_map cm;      const char *server_name, *server_version, *user_name, *host_name, *default_sink_name, *default_source_name;      uint32_t cookie; @@ -882,7 +961,9 @@ static void server_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag, pa          pa_tagstruct_get_sample_spec(t, &ss) < 0 ||          pa_tagstruct_gets(t, &default_sink_name) < 0 ||          pa_tagstruct_gets(t, &default_source_name) < 0 || -        pa_tagstruct_getu32(t, &cookie) < 0) { +        pa_tagstruct_getu32(t, &cookie) < 0 || +        (u->version >= 15 && +         pa_tagstruct_get_channel_map(t, &cm) < 0)) {          pa_log("Parse failure");          goto fail; @@ -963,6 +1044,20 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag, pa_t          }      } +    if (u->version >= 15) { +        pa_volume_t base_volume; +        uint32_t state, n_volume_steps, card; + +        if (pa_tagstruct_get_volume(t, &base_volume) < 0 || +            pa_tagstruct_getu32(t, &state) < 0 || +            pa_tagstruct_getu32(t, &n_volume_steps) < 0 || +            pa_tagstruct_getu32(t, &card) < 0) { + +            pa_log("Parse failure"); +            goto fail; +        } +    } +      if (!pa_tagstruct_eof(t)) {          pa_log("Packet too long");          goto fail; @@ -1059,12 +1154,11 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag          pa_cvolume_equal(&volume, &u->sink->virtual_volume))          return; -    memcpy(&u->sink->virtual_volume, &volume, sizeof(pa_cvolume)); +    pa_sink_volume_changed(u->sink, &volume, FALSE);      if (u->version >= 11) -        u->sink->muted = !!mute; +        pa_sink_mute_changed(u->sink, mute, FALSE); -    pa_subscription_post(u->sink->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, u->sink->index);      return;  fail: @@ -1126,6 +1220,20 @@ static void source_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag, pa          }      } +    if (u->version >= 15) { +        pa_volume_t base_volume; +        uint32_t state, n_volume_steps, card; + +        if (pa_tagstruct_get_volume(t, &base_volume) < 0 || +            pa_tagstruct_getu32(t, &state) < 0 || +            pa_tagstruct_getu32(t, &n_volume_steps) < 0 || +            pa_tagstruct_getu32(t, &card) < 0) { + +            pa_log("Parse failure"); +            goto fail; +        } +    } +      if (!pa_tagstruct_eof(t)) {          pa_log("Packet too long");          goto fail; @@ -1246,7 +1354,6 @@ static void start_subscribe(struct userdata *u) {  /* Called from main context */  static void create_stream_callback(pa_pdispatch *pd, uint32_t command,  uint32_t tag, pa_tagstruct *t, void *userdata) {      struct userdata *u = userdata; -    struct timeval ntv;  #ifdef TUNNEL_SINK      uint32_t bytes;  #endif @@ -1314,11 +1421,11 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command,  uint32_t          if (pa_tagstruct_get_usec(t, &usec) < 0)              goto parse_error; -#ifdef TUNNEL_SINK -        pa_sink_set_latency_range(u->sink, usec + MIN_NETWORK_LATENCY_USEC, 0); -#else -        pa_source_set_latency_range(u->source, usec + MIN_NETWORK_LATENCY_USEC, 0); -#endif +/* #ifdef TUNNEL_SINK */ +/*         pa_sink_set_latency_range(u->sink, usec + MIN_NETWORK_LATENCY_USEC, 0); */ +/* #else */ +/*         pa_source_set_latency_range(u->source, usec + MIN_NETWORK_LATENCY_USEC, 0); */ +/* #endif */      }      if (!pa_tagstruct_eof(t)) @@ -1328,9 +1435,7 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command,  uint32_t      request_info(u);      pa_assert(!u->time_event); -    pa_gettimeofday(&ntv); -    ntv.tv_sec += LATENCY_INTERVAL; -    u->time_event = u->core->mainloop->time_new(u->core->mainloop, &ntv, timeout_callback, u); +    u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + LATENCY_INTERVAL, timeout_callback, u);      request_latency(u); @@ -1391,11 +1496,17 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t      pa_log_debug("Protocol version: remote %u, local %u", u->version, PA_PROTOCOL_VERSION);  #ifdef TUNNEL_SINK +    pa_proplist_setf(u->sink->proplist, "tunnel.remote_version", "%u", u->version); +    pa_sink_update_proplist(u->sink, 0, NULL); +      pa_snprintf(name, sizeof(name), "%s for %s@%s",                  u->sink_name,                  pa_get_user_name(un, sizeof(un)),                  pa_get_host_name(hn, sizeof(hn)));  #else +    pa_proplist_setf(u->source->proplist, "tunnel.remote_version", "%u", u->version); +    pa_source_update_proplist(u->source, 0, NULL); +      pa_snprintf(name, sizeof(name), "%s for %s@%s",                  u->source_name,                  pa_get_user_name(un, sizeof(un)), @@ -1409,9 +1520,9 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t      if (u->version >= 13) {          pa_proplist *pl;          pl = pa_proplist_new(); -        pa_init_proplist(pl);          pa_proplist_sets(pl, PA_PROP_APPLICATION_ID, "org.PulseAudio.PulseAudio");          pa_proplist_sets(pl, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION); +        pa_init_proplist(pl);          pa_tagstruct_put_proplist(reply, pl);          pa_proplist_free(pl);      } else @@ -1503,6 +1614,14 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t          pa_tagstruct_put_boolean(reply, TRUE); /* early rquests */      } +    if (u->version >= 15) { +#ifdef TUNNEL_SINK +        pa_tagstruct_put_boolean(reply, FALSE); /* muted_set */ +#endif +        pa_tagstruct_put_boolean(reply, FALSE); /* don't inhibit auto suspend */ +        pa_tagstruct_put_boolean(reply, FALSE); /* fail on suspend */ +    } +      pa_pstream_send_tagstruct(u->pstream, reply);      pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); @@ -1550,7 +1669,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o      pa_assert(u);      if (channel != u->channel) { -        pa_log("Recieved memory block on bad channel."); +        pa_log("Received memory block on bad channel.");          pa_module_unload_request(u->module, TRUE);          return;      } @@ -1559,7 +1678,6 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o      u->counter_delta += (int64_t) chunk->length;  } -  #endif  /* Called from main context */ @@ -1582,7 +1700,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata      }      u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->mempool); -    u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX); +    u->pdispatch = pa_pdispatch_new(u->core->mainloop, TRUE, command_table, PA_COMMAND_MAX);      pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u);      pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u); @@ -1695,13 +1813,19 @@ int pa__init(pa_module*m) {      u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));;      u->source = NULL;  #endif -    u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); +    u->smoother = pa_smoother_new( +            PA_USEC_PER_SEC, +            PA_USEC_PER_SEC*2, +            TRUE, +            TRUE, +            10, +            pa_rtclock_now(), +            FALSE);      u->ctag = 1;      u->device_index = u->channel = PA_INVALID_INDEX;      u->time_event = NULL;      u->ignore_latency_before = 0; -    u->transport_usec = 0; -    u->transport_usec_valid = FALSE; +    u->transport_usec = u->thread_transport_usec = 0;      u->remote_suspended = u->remote_corked = FALSE;      u->counter = u->counter_delta = 0; @@ -1723,7 +1847,7 @@ int pa__init(pa_module*m) {          goto fail;      } -    if (!(u->client = pa_socket_client_new_string(m->core->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) { +    if (!(u->client = pa_socket_client_new_string(m->core->mainloop, TRUE, u->server_name, PA_NATIVE_DEFAULT_PORT))) {          pa_log("Failed to connect to server '%s'", u->server_name);          goto fail;      } @@ -1747,7 +1871,13 @@ int pa__init(pa_module*m) {      if (u->sink_name)          pa_proplist_sets(data.proplist, "tunnel.remote.sink", u->sink_name); -    u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL); +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } + +    u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL);      pa_sink_new_data_done(&data);      if (!u->sink) { @@ -1763,7 +1893,7 @@ int pa__init(pa_module*m) {      u->sink->refresh_volume = u->sink->refresh_muted = FALSE; -    pa_sink_set_latency_range(u->sink, MIN_NETWORK_LATENCY_USEC, 0); +/*     pa_sink_set_latency_range(u->sink, MIN_NETWORK_LATENCY_USEC, 0); */      pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);      pa_sink_set_rtpoll(u->sink, u->rtpoll); @@ -1785,6 +1915,12 @@ int pa__init(pa_module*m) {      if (u->source_name)          pa_proplist_sets(data.proplist, "tunnel.remote.source", u->source_name); +    if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_source_new_data_done(&data); +        goto fail; +    } +      u->source = pa_source_new(m->core, &data, PA_SOURCE_NETWORK|PA_SOURCE_LATENCY);      pa_source_new_data_done(&data); @@ -1797,7 +1933,7 @@ int pa__init(pa_module*m) {      u->source->set_state = source_set_state;      u->source->userdata = u; -    pa_source_set_latency_range(u->source, MIN_NETWORK_LATENCY_USEC, 0); +/*     pa_source_set_latency_range(u->source, MIN_NETWORK_LATENCY_USEC, 0); */      pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);      pa_source_set_rtpoll(u->source, u->rtpoll); @@ -1807,15 +1943,13 @@ int pa__init(pa_module*m) {      u->time_event = NULL; -    u->maxlength = 0; +    u->maxlength = (uint32_t) -1;  #ifdef TUNNEL_SINK -    u->tlength = u->minreq = u->prebuf = 0; +    u->tlength = u->minreq = u->prebuf = (uint32_t) -1;  #else -    u->fragsize = 0; +    u->fragsize = (uint32_t) -1;  #endif -    pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); -      if (!(u->thread = pa_thread_new(thread_func, u))) {          pa_log("Failed to create thread.");          goto fail; diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c new file mode 100644 index 00000000..1ad6fa2d --- /dev/null +++ b/src/modules/module-udev-detect.c @@ -0,0 +1,457 @@ +/*** +  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 <errno.h> +#include <limits.h> +#include <sys/inotify.h> +#include <libudev.h> + +#include <pulsecore/modargs.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> + +#include "module-udev-detect-symdef.h" + +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); + +struct device { +    char *path; +    pa_bool_t accessible; +    char *card_name; +    uint32_t module; +}; + +struct userdata { +    pa_core *core; +    pa_hashmap *devices; +    pa_bool_t use_tsched; + +    struct udev* udev; +    struct udev_monitor *monitor; +    pa_io_event *udev_io; + +    int inotify_fd; +    pa_io_event *inotify_io; +}; + +static const char* const valid_modargs[] = { +    "tsched", +    NULL +}; + +static void device_free(struct device *d) { +    pa_assert(d); + +    pa_xfree(d->path); +    pa_xfree(d->card_name); +    pa_xfree(d); +} + +static const char *path_get_card_id(const char *path) { +    const char *e; + +    if (!path) +        return NULL; + +    if (!(e = strrchr(path, '/'))) +        return NULL; + +    if (!pa_startswith(e, "/card")) +        return NULL; + +    return e + 5; +} + +static void verify_access(struct userdata *u, struct device *d) { +    char *cd; +    pa_card *card; + +    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)); +    pa_xfree(cd); + +    pa_card_suspend(card, !d->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); +    pa_assert(dev); + +    path = udev_device_get_devpath(dev); + +    if ((d = pa_hashmap_get(u->devices, path))) { +        verify_access(u, d); +        return; +    } + +    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); + +    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_xfree(n); +} + +static void remove_card(struct userdata *u, struct udev_device *dev) { +    struct device *d; + +    pa_assert(u); +    pa_assert(dev); + +    if (!(d = pa_hashmap_remove(u->devices, udev_device_get_devpath(dev)))) +        return; + +    pa_log_info("Card %s removed.", d->path); +    pa_module_unload_request_by_index(u->core, d->module, TRUE); +    device_free(d); +} + +static void process_device(struct userdata *u, struct udev_device *dev) { +    const char *action, *ff; + +    pa_assert(u); +    pa_assert(dev); + +    if (udev_device_get_property_value(dev, "PULSE_IGNORE")) { +        pa_log_debug("Ignoring %s, because marked so.", udev_device_get_devpath(dev)); +        return; +    } + +    if ((ff = udev_device_get_property_value(dev, "SOUND_FORM_FACTOR")) && +        pa_streq(ff, "modem")) { +        pa_log_debug("Ignoring %s, because it is a modem.", udev_device_get_devpath(dev)); +        return; +    } + +    action = udev_device_get_action(dev); + +    if (action && pa_streq(action, "remove")) +        remove_card(u, dev); +    else if ((!action || pa_streq(action, "change")) && +             udev_device_get_property_value(dev, "SOUND_INITIALIZED")) +        card_changed(u, dev); + +    /* For an explanation why we don't look for 'add' events here +     * have a look into /lib/udev/rules.d/78-sound-card.rules! */ +} + +static void process_path(struct userdata *u, const char *path) { +    struct udev_device *dev; + +    if (!path_get_card_id(path)) +        return; + +    if (!(dev = udev_device_new_from_syspath(u->udev, path))) { +        pa_log("Failed to get udev device object from udev."); +        return; +    } + +    process_device(u, dev); +    udev_device_unref(dev); +} + +static void monitor_cb( +        pa_mainloop_api*a, +        pa_io_event* e, +        int fd, +        pa_io_event_flags_t events, +        void *userdata) { + +    struct userdata *u = userdata; +    struct udev_device *dev; + +    pa_assert(a); + +    if (!(dev = udev_monitor_receive_device(u->monitor))) { +        pa_log("Failed to get udev device object from monitor."); +        goto fail; +    } + +    if (!path_get_card_id(udev_device_get_devpath(dev))) +        return; + +    process_device(u, dev); +    udev_device_unref(dev); +    return; + +fail: +    a->io_free(u->udev_io); +    u->udev_io = NULL; +} + +static void inotify_cb( +        pa_mainloop_api*a, +        pa_io_event* e, +        int fd, +        pa_io_event_flags_t events, +        void *userdata) { + +    struct { +        struct inotify_event e; +        char name[NAME_MAX]; +    } buf; +    struct userdata *u = userdata; +    static int type = 0; +    pa_bool_t verify = FALSE; + +    for (;;) { +        ssize_t r; + +        pa_zero(buf); +        if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) { + +            if (r < 0 && errno == EAGAIN) +                break; + +            pa_log("read() from inotify failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); +            goto fail; +        } + +        if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC")) +            verify = TRUE; +    } + +    if (verify) { +        struct device *d; +        void *state; + +        pa_log_debug("Verifying access."); + +        PA_HASHMAP_FOREACH(d, u->devices, state) +            verify_access(u, d); +    } + +    return; + +fail: +    a->io_free(u->inotify_io); +    u->inotify_io = NULL; + +    if (u->inotify_fd >= 0) { +        pa_close(u->inotify_fd); +        u->inotify_fd = -1; +    } +} + +static int setup_inotify(struct userdata *u) { +    char *dev_snd; +    int r; + +    if ((u->inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { +        pa_log("inotify_init1() failed: %s", pa_cstrerror(errno)); +        return -1; +    } + +    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); +    pa_xfree(dev_snd); + +    if (r < 0) { +        pa_log("inotify_add_watch() failed: %s", pa_cstrerror(errno)); +        return -1; +    } + +    pa_assert_se(u->inotify_io = u->core->mainloop->io_new(u->core->mainloop, u->inotify_fd, PA_IO_EVENT_INPUT, inotify_cb, u)); + +    return 0; +} + +int pa__init(pa_module *m) { +    struct userdata *u = NULL; +    pa_modargs *ma; +    struct udev_enumerate *enumerate = NULL; +    struct udev_list_entry *item = NULL, *first = NULL; +    int fd; + +    pa_assert(m); + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("Failed to parse module arguments"); +        goto fail; +    } + +    m->userdata = u = pa_xnew0(struct userdata, 1); +    u->core = m->core; +    u->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."); +        goto fail; +    } + +    if (!(u->udev = udev_new())) { +        pa_log("Failed to initialize udev library."); +        goto fail; +    } + +    if (setup_inotify(u) < 0) +        goto fail; + +    if (!(u->monitor = udev_monitor_new_from_netlink(u->udev, "udev"))) { +        pa_log("Failed to initialize monitor."); +        goto fail; +    } + +    errno = 0; +    if (udev_monitor_enable_receiving(u->monitor) < 0) { +        pa_log("Failed to enable monitor: %s", pa_cstrerror(errno)); +        if (errno == EPERM) +            pa_log_info("Most likely your kernel is simply too old and " +                        "allows only priviliged processes to listen to device events. " +                        "Please upgrade your kernel to at least 2.6.30."); +        goto fail; +    } + +    if ((fd = udev_monitor_get_fd(u->monitor)) < 0) { +        pa_log("Failed to get udev monitor fd."); +        goto fail; +    } + +    pa_assert_se(u->udev_io = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, monitor_cb, u)); + +    if (!(enumerate = udev_enumerate_new(u->udev))) { +        pa_log("Failed to initialize udev enumerator."); +        goto fail; +    } + +    if (udev_enumerate_add_match_subsystem(enumerate, "sound") < 0) { +        pa_log("Failed to match to subsystem."); +        goto fail; +    } + +    if (udev_enumerate_scan_devices(enumerate) < 0) { +        pa_log("Failed to scan for devices."); +        goto fail; +    } + +    first = udev_enumerate_get_list_entry(enumerate); +    udev_list_entry_foreach(item, first) +        process_path(u, udev_list_entry_get_name(item)); + +    udev_enumerate_unref(enumerate); + +    pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices)); + +    pa_modargs_free(ma); + +    return 0; + +fail: + +    if (enumerate) +        udev_enumerate_unref(enumerate); + +    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; + +    if (u->udev_io) +        m->core->mainloop->io_free(u->udev_io); + +    if (u->monitor) +        udev_monitor_unref(u->monitor); + +    if (u->udev) +        udev_unref(u->udev); + +    if (u->inotify_io) +        m->core->mainloop->io_free(u->inotify_io); + +    if (u->inotify_fd >= 0) +        pa_close(u->inotify_fd); + +    if (u->devices) { +        struct device *d; + +        while ((d = pa_hashmap_steal_first(u->devices))) +            device_free(d); + +        pa_hashmap_free(u->devices, NULL, NULL); +    } + +    pa_xfree(u); +} diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 61858afa..91da598e 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -36,6 +36,7 @@ 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-stream-restore instead of module-volume-restore!");  static const char* const valid_modargs[] = {      "table", @@ -62,7 +63,7 @@ int pa__init(pa_module*m) {          goto fail;      } -    pa_log_warn("module-volume-restore is obsolete. It has been replaced by module-stream-restore. We will now load the latter but please make sure to remove module-volume-restore from your configuration."); +    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); diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c index 2d35828d..d1b9f2ff 100644 --- a/src/modules/module-waveout.c +++ b/src/modules/module-waveout.c @@ -256,7 +256,7 @@ static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *t      pa_gettimeofday(&ntv);      pa_timeval_add(&ntv, u->poll_timeout); -    a->time_restart(e, &ntv); +    a->rtclock_time_restart(e, &ntv);  }  static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { @@ -549,7 +549,7 @@ int pa__init(pa_core *c, pa_module*m) {      pa_gettimeofday(&tv);      pa_timeval_add(&tv, u->poll_timeout); -    u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u); +    u->event = c->mainloop->rtclock_time_new(c->mainloop, &tv, poll_cb, u);      assert(u->event);      u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u); diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 3da946e0..1fdc1f46 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -279,7 +279,7 @@ static void browser_cb(                  pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));          /* We ignore the returned resolver object here, since the we don't -         * need to attach any special data to it, and we can still destory +         * need to attach any special data to it, and we can still destroy           * it from the callback */      } else if (event == AVAHI_BROWSER_REMOVE) { diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 692ffe91..d72d2647 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -37,6 +37,7 @@  #include <pulse/xmalloc.h>  #include <pulse/util.h> +#include <pulsecore/parseaddr.h>  #include <pulsecore/sink.h>  #include <pulsecore/source.h>  #include <pulsecore/native-common.h> @@ -47,6 +48,7 @@  #include <pulsecore/modargs.h>  #include <pulsecore/avahi-wrap.h>  #include <pulsecore/endianmacros.h> +#include <pulsecore/protocol-native.h>  #include "module-zeroconf-publish-symdef.h" @@ -54,7 +56,6 @@ PA_MODULE_AUTHOR("Lennart Poettering");  PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE("port=<IP port number>");  #define SERVICE_TYPE_SINK "_pulse-sink._tcp"  #define SERVICE_TYPE_SOURCE "_pulse-source._tcp" @@ -67,7 +68,6 @@ PA_MODULE_USAGE("port=<IP port number>");  #define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE  static const char* const valid_modargs[] = { -    "port",      NULL  }; @@ -88,6 +88,7 @@ struct service {  struct userdata {      pa_core *core;      pa_module *module; +      AvahiPoll *avahi_poll;      AvahiClient *client; @@ -96,15 +97,15 @@ struct userdata {      AvahiEntryGroup *main_entry_group; -    uint16_t port; -      pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot; + +    pa_native_protocol *native;  }; -static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description, enum service_subtype *ret_subtype) { +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) {      pa_assert(s);      pa_assert(ret_ss); -    pa_assert(ret_description); +    pa_assert(ret_proplist);      pa_assert(ret_subtype);      if (pa_sink_isinstance(s->device)) { @@ -113,7 +114,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann          *ret_ss = sink->sample_spec;          *ret_map = sink->channel_map;          *ret_name = sink->name; -        *ret_description = pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); +        *ret_proplist = sink->proplist;          *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;      } else if (pa_source_isinstance(s->device)) { @@ -122,7 +123,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann          *ret_ss = source->sample_spec;          *ret_map = source->channel_map;          *ret_name = source->name; -        *ret_description = pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); +        *ret_proplist = source->proplist;          *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);      } else @@ -131,11 +132,24 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann  static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {      char s[128]; +    char *t;      pa_assert(c);      l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION); -    l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s))); + +    t = pa_get_user_name_malloc(); +    l = avahi_string_list_add_pair(l, "user-name", t); +    pa_xfree(t); + +    t = pa_machine_id(); +    l = avahi_string_list_add_pair(l, "machine-id", t); +    pa_xfree(t); + +    t = pa_uname_string(); +    l = avahi_string_list_add_pair(l, "uname", t); +    pa_xfree(t); +      l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));      l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie); @@ -184,10 +198,35 @@ static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupStat  static void service_free(struct service *s); +static uint16_t compute_port(struct userdata *u) { +    pa_strlist *i; + +    pa_assert(u); + +    for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) { +        pa_parsed_address a; + +        if (pa_parse_address(pa_strlist_data(i), &a) >= 0 && +            (a.type == PA_PARSED_ADDRESS_TCP4 || +             a.type == PA_PARSED_ADDRESS_TCP6 || +             a.type == PA_PARSED_ADDRESS_TCP_AUTO) && +            a.port > 0) { + +            pa_xfree(a.path_or_host); +            return a.port; +        } + +        pa_xfree(a.path_or_host); +    } + +    return PA_NATIVE_DEFAULT_PORT; +} +  static int publish_service(struct service *s) {      int r = -1;      AvahiStringList *txt = NULL; -    const char *description = NULL, *name = NULL; +    const char *name = NULL, *t; +    pa_proplist *proplist = NULL;      pa_sample_spec ss;      pa_channel_map map;      char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; @@ -214,7 +253,7 @@ static int publish_service(struct service *s) {      txt = txt_record_server_data(s->userdata->core, txt); -    get_service_data(s, &ss, &map, &name, &description, &subtype); +    get_service_data(s, &ss, &map, &name, &proplist, &subtype);      txt = avahi_string_list_add_pair(txt, "device", name);      txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);      txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels); @@ -222,6 +261,19 @@ static int publish_service(struct service *s) {      txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map));      txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[subtype]); +    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION))) +        txt = avahi_string_list_add_pair(txt, "description", t); +    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME))) +        txt = avahi_string_list_add_pair(txt, "icon-name", t); +    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME))) +        txt = avahi_string_list_add_pair(txt, "vendor-name", t); +    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME))) +        txt = avahi_string_list_add_pair(txt, "product-name", t); +    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS))) +        txt = avahi_string_list_add_pair(txt, "class", t); +    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR))) +        txt = avahi_string_list_add_pair(txt, "form-factor", t); +      if (avahi_entry_group_add_service_strlst(                  s->entry_group,                  AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, @@ -230,7 +282,7 @@ static int publish_service(struct service *s) {                  pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,                  NULL,                  NULL, -                s->userdata->port, +                compute_port(s->userdata),                  txt) < 0) {          pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); @@ -287,7 +339,7 @@ finish:  static struct service *get_service(struct userdata *u, pa_object *device) {      struct service *s; -    char hn[64], un[64]; +    char *hn, *un;      const char *n;      pa_assert(u); @@ -309,11 +361,13 @@ static struct service *get_service(struct userdata *u, pa_object *device) {              n = PA_SOURCE(device)->name;      } -    s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", -                                                         pa_get_user_name(un, sizeof(un)), -                                                         pa_get_host_name(hn, sizeof(hn)), -                                                         n), -                                       AVAHI_LABEL_MAX-1); +    hn = pa_get_host_name_malloc(); +    un = pa_get_user_name_malloc(); + +    s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), AVAHI_LABEL_MAX-1); + +    pa_xfree(un); +    pa_xfree(hn);      pa_hashmap_put(u->services, s->device, s); @@ -430,7 +484,7 @@ static int publish_main_service(struct userdata *u) {                  SERVICE_TYPE_SERVER,                  NULL,                  NULL, -                u->port, +                compute_port(u),                  txt) < 0) {          pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client))); @@ -552,9 +606,8 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda  int pa__init(pa_module*m) {      struct userdata *u; -    uint32_t port = PA_NATIVE_DEFAULT_PORT;      pa_modargs *ma = NULL; -    char hn[256], un[256]; +    char *hn, *un;      int error;      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -562,15 +615,10 @@ int pa__init(pa_module*m) {          goto fail;      } -    if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) { -        pa_log("Invalid port specified."); -        goto fail; -    } -      m->userdata = u = pa_xnew(struct userdata, 1);      u->core = m->core;      u->module = m; -    u->port = (uint16_t) port; +    u->native = pa_native_protocol_get(u->core);      u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); @@ -585,7 +633,11 @@ int pa__init(pa_module*m) {      u->main_entry_group = NULL; -    u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX); +    un = pa_get_user_name_malloc(); +    hn = pa_get_host_name_malloc(); +    u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), AVAHI_LABEL_MAX-1); +    pa_xfree(un); +    pa_xfree(hn);      if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {          pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); @@ -643,6 +695,9 @@ void pa__done(pa_module*m) {      if (u->avahi_poll)          pa_avahi_poll_free(u->avahi_poll); +    if (u->native) +        pa_native_protocol_unref(u->native); +      pa_xfree(u->service_name);      pa_xfree(u);  } diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index 7bce8d00..c44b882b 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -85,17 +85,22 @@ PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE(          "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> "          "source_name=<name for the source> " +        "source_properties=<properties for the source> "          "device=<OSS device> "          "record=<enable source?> "          "playback=<enable sink?> "          "format=<sample format> " -        "channels=<number of channels> "          "rate=<sample rate> " +        "channels=<number of channels> " +        "channel_map=<channel map> "          "fragments=<number of fragments> "          "fragment_size=<fragment size> " -        "channel_map=<channel map> "          "mmap=<enable memory mapping?>"); +#ifdef __linux__ +PA_MODULE_DEPRECATED("Please use module-alsa-card instead of module-oss!"); +#endif  #define DEFAULT_DEVICE "/dev/dsp" @@ -140,7 +145,9 @@ struct userdata {  static const char* const valid_modargs[] = {      "sink_name", +    "sink_properties",      "source_name", +    "source_properties",      "device",      "record",      "playback", @@ -477,6 +484,7 @@ static void build_pollfd(struct userdata *u) {      pollfd->revents = 0;  } +/* Called from IO context */  static int suspend(struct userdata *u) {      pa_assert(u);      pa_assert(u->fd >= 0); @@ -526,6 +534,7 @@ static int suspend(struct userdata *u) {      return 0;  } +/* Called from IO context */  static int unsuspend(struct userdata *u) {      int m;      pa_sample_spec ss, *ss_original; @@ -616,10 +625,10 @@ static int unsuspend(struct userdata *u) {      build_pollfd(u); -    if (u->sink) -        pa_sink_get_volume(u->sink, TRUE); -    if (u->source) -        pa_source_get_volume(u->source, TRUE); +    if (u->sink && u->sink->get_volume) +        u->sink->get_volume(u->sink); +    if (u->source && u->source->get_volume) +        u->source->get_volume(u->source);      pa_log_info("Resumed successfully..."); @@ -631,6 +640,7 @@ fail:      return -1;  } +/* Called from IO context */  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;      int ret; @@ -879,7 +889,6 @@ static void thread_func(void *userdata) {          pa_make_realtime(u->core->realtime_priority);      pa_thread_mq_install(&u->thread_mq); -    pa_rtpoll_install(u->rtpoll);      for (;;) {          int ret; @@ -1311,6 +1320,12 @@ int pa__init(pa_module*m) {          pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->in_hwbuf_size));          pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->in_fragment_size)); +        if (pa_modargs_get_proplist(ma, "source_properties", source_new_data.proplist, PA_UPDATE_REPLACE) < 0) { +            pa_log("Invalid properties"); +            pa_source_new_data_done(&source_new_data); +            goto fail; +        } +          u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);          pa_source_new_data_done(&source_new_data);          pa_xfree(name_buf); @@ -1325,6 +1340,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->in_hwbuf_size, &u->source->sample_spec));          u->source->refresh_volume = TRUE;          if (use_mmap) @@ -1372,6 +1388,12 @@ int pa__init(pa_module*m) {          pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->out_hwbuf_size));          pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->out_fragment_size)); +        if (pa_modargs_get_proplist(ma, "sink_properties", sink_new_data.proplist, PA_UPDATE_REPLACE) < 0) { +            pa_log("Invalid properties"); +            pa_sink_new_data_done(&sink_new_data); +            goto fail; +        } +          u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY);          pa_sink_new_data_done(&sink_new_data);          pa_xfree(name_buf); @@ -1386,9 +1408,10 @@ 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->out_hwbuf_size, &u->sink->sample_spec));          u->sink->refresh_volume = TRUE; -        u->sink->thread_info.max_request = u->out_hwbuf_size; +        pa_sink_set_max_request(u->sink, u->out_hwbuf_size);          if (use_mmap)              u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags); diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c new file mode 100644 index 00000000..eaeb77fc --- /dev/null +++ b/src/modules/raop/module-raop-discover.c @@ -0,0 +1,397 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 Lennart Poettering +  Copyright 2008 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/alternative.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/native-common.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/avahi-wrap.h> + +#include "module-raop-discover-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SERVICE_TYPE_SINK "_raop._tcp" + +static const char* const valid_modargs[] = { +    NULL +}; + +struct tunnel { +    AvahiIfIndex interface; +    AvahiProtocol protocol; +    char *name, *type, *domain; +    uint32_t module_index; +}; + +struct userdata { +    pa_core *core; +    pa_module *module; +    AvahiPoll *avahi_poll; +    AvahiClient *client; +    AvahiServiceBrowser *sink_browser; + +    pa_hashmap *tunnels; +}; + +static unsigned tunnel_hash(const void *p) { +    const struct tunnel *t = p; + +    return +        (unsigned) t->interface + +        (unsigned) t->protocol + +        pa_idxset_string_hash_func(t->name) + +        pa_idxset_string_hash_func(t->type) + +        pa_idxset_string_hash_func(t->domain); +} + +static int tunnel_compare(const void *a, const void *b) { +    const struct tunnel *ta = a, *tb = b; +    int r; + +    if (ta->interface != tb->interface) +        return 1; +    if (ta->protocol != tb->protocol) +        return 1; +    if ((r = strcmp(ta->name, tb->name))) +        return r; +    if ((r = strcmp(ta->type, tb->type))) +        return r; +    if ((r = strcmp(ta->domain, tb->domain))) +        return r; + +    return 0; +} + +static struct tunnel *tunnel_new( +        AvahiIfIndex interface, AvahiProtocol protocol, +        const char *name, const char *type, const char *domain) { + +    struct tunnel *t; +    t = pa_xnew(struct tunnel, 1); +    t->interface = interface; +    t->protocol = protocol; +    t->name = pa_xstrdup(name); +    t->type = pa_xstrdup(type); +    t->domain = pa_xstrdup(domain); +    t->module_index = PA_IDXSET_INVALID; +    return t; +} + +static void tunnel_free(struct tunnel *t) { +    pa_assert(t); +    pa_xfree(t->name); +    pa_xfree(t->type); +    pa_xfree(t->domain); +    pa_xfree(t); +} + +static void resolver_cb( +        AvahiServiceResolver *r, +        AvahiIfIndex interface, AvahiProtocol protocol, +        AvahiResolverEvent event, +        const char *name, const char *type, const char *domain, +        const char *host_name, const AvahiAddress *a, uint16_t port, +        AvahiStringList *txt, +        AvahiLookupResultFlags flags, +        void *userdata) { + +    struct userdata *u = userdata; +    struct tunnel *tnl; + +    pa_assert(u); + +    tnl = tunnel_new(interface, protocol, name, type, domain); + +    if (event != AVAHI_RESOLVER_FOUND) +        pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); +    else { +        char *device = NULL, *nicename, *dname, *vname, *args; +        char at[AVAHI_ADDRESS_STR_MAX]; +        AvahiStringList *l; +        pa_module *m; + +        if ((nicename = strstr(name, "@"))) { +            ++nicename; +            if (strlen(nicename) > 0) { +                pa_log_debug("Found RAOP: %s", nicename); +            } +        } + +        for (l = txt; l; l = l->next) { +            char *key, *value; +            pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); + +            pa_log_debug("Found key: '%s' with value: '%s'", key, value); +            if (strcmp(key, "device") == 0) { +                pa_xfree(device); +                device = value; +                value = NULL; +            } +            avahi_free(key); +            avahi_free(value); +        } + +        if (device) +            dname = pa_sprintf_malloc("raop.%s.%s", host_name, device); +        else +            dname = pa_sprintf_malloc("raop.%s", host_name); + +        if (!(vname = pa_namereg_make_valid_name(dname))) { +            pa_log("Cannot construct valid device name from '%s'.", dname); +            avahi_free(device); +            pa_xfree(dname); +            goto finish; +        } +        pa_xfree(dname); + +        /* +         TODO: allow this syntax of server name in things.... +        args = pa_sprintf_malloc("server=[%s]:%u " +                                 "sink_name=%s", +                                 avahi_address_snprint(at, sizeof(at), a), port, +                                 vname);*/ +        if (nicename) { +            args = pa_sprintf_malloc("server=%s " +                                     "sink_name=%s " +                                     "description=\"%s\"", +                                     avahi_address_snprint(at, sizeof(at), a), +                                     vname, +                                     nicename); + +        } else { +            args = pa_sprintf_malloc("server=%s " +                                     "sink_name=%s", +                                     avahi_address_snprint(at, sizeof(at), a), +                                     vname); +        } + +        pa_log_debug("Loading module-raop-sink with arguments '%s'", args); + +        if ((m = pa_module_load(u->core, "module-raop-sink", args))) { +            tnl->module_index = m->index; +            pa_hashmap_put(u->tunnels, tnl, tnl); +            tnl = NULL; +        } + +        pa_xfree(vname); +        pa_xfree(args); +        avahi_free(device); +    } + +finish: + +    avahi_service_resolver_free(r); + +    if (tnl) +        tunnel_free(tnl); +} + +static void browser_cb( +        AvahiServiceBrowser *b, +        AvahiIfIndex interface, AvahiProtocol protocol, +        AvahiBrowserEvent event, +        const char *name, const char *type, const char *domain, +        AvahiLookupResultFlags flags, +        void *userdata) { + +    struct userdata *u = userdata; +    struct tunnel *t; + +    pa_assert(u); + +    if (flags & AVAHI_LOOKUP_RESULT_LOCAL) +        return; + +    t = tunnel_new(interface, protocol, name, type, domain); + +    if (event == AVAHI_BROWSER_NEW) { + +        if (!pa_hashmap_get(u->tunnels, t)) +            if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u))) +                pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + +        /* We ignore the returned resolver object here, since the we don't +         * need to attach any special data to it, and we can still destroy +         * it from the callback */ + +    } else if (event == AVAHI_BROWSER_REMOVE) { +        struct tunnel *t2; + +        if ((t2 = pa_hashmap_get(u->tunnels, t))) { +            pa_module_unload_by_index(u->core, t2->module_index, TRUE); +            pa_hashmap_remove(u->tunnels, t2); +            tunnel_free(t2); +        } +    } + +    tunnel_free(t); +} + +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { +    struct userdata *u = userdata; + +    pa_assert(c); +    pa_assert(u); + +    u->client = c; + +    switch (state) { +        case AVAHI_CLIENT_S_REGISTERING: +        case AVAHI_CLIENT_S_RUNNING: +        case AVAHI_CLIENT_S_COLLISION: + +            if (!u->sink_browser) { + +                if (!(u->sink_browser = avahi_service_browser_new( +                              c, +                              AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, +                              SERVICE_TYPE_SINK, +                              NULL, +                              0, +                              browser_cb, u))) { + +                    pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); +                    pa_module_unload_request(u->module, TRUE); +                } +            } + +            break; + +        case AVAHI_CLIENT_FAILURE: +            if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { +                int error; + +                pa_log_debug("Avahi daemon disconnected."); + +                if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { +                    pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); +                    pa_module_unload_request(u->module, TRUE); +                } +            } + +            /* Fall through */ + +        case AVAHI_CLIENT_CONNECTING: + +            if (u->sink_browser) { +                avahi_service_browser_free(u->sink_browser); +                u->sink_browser = NULL; +            } + +            break; + +        default: ; +    } +} + +int pa__init(pa_module*m) { + +    struct userdata *u; +    pa_modargs *ma = NULL; +    int error; + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("Failed to parse module arguments."); +        goto fail; +    } + +    m->userdata = u = pa_xnew(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->sink_browser = NULL; + +    u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); + +    u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); + +    if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { +        pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); +        goto fail; +    } + +    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->client) +        avahi_client_free(u->client); + +    if (u->avahi_poll) +        pa_avahi_poll_free(u->avahi_poll); + +    if (u->tunnels) { +        struct tunnel *t; + +        while ((t = pa_hashmap_steal_first(u->tunnels))) { +            pa_module_unload_by_index(u->core, t->module_index, TRUE); +            tunnel_free(t); +        } + +        pa_hashmap_free(u->tunnels, NULL, NULL); +    } + +    pa_xfree(u); +} diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c new file mode 100644 index 00000000..9699132d --- /dev/null +++ b/src/modules/raop/module-raop-sink.c @@ -0,0 +1,697 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 Lennart Poettering +  Copyright 2008 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 <stdlib.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <poll.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/ioctl.h> + +#ifdef HAVE_LINUX_SOCKIOS_H +#include <linux/sockios.h> +#endif + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/socket-client.h> +#include <pulsecore/authkey.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/thread.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/socket-util.h> + +#include "module-raop-sink-symdef.h" +#include "rtp.h" +#include "sdp.h" +#include "sap.h" +#include "raop_client.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("RAOP Sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( +        "sink_name=<name for the sink> " +        "sink_properties=<properties for the sink> " +        "server=<address>  " +        "format=<sample format> " +        "rate=<sample rate> " +        "channels=<number of channels>"); + +#define DEFAULT_SINK_NAME "raop" + +struct userdata { +    pa_core *core; +    pa_module *module; +    pa_sink *sink; + +    pa_thread_mq thread_mq; +    pa_rtpoll *rtpoll; +    pa_rtpoll_item *rtpoll_item; +    pa_thread *thread; + +    pa_memchunk raw_memchunk; +    pa_memchunk encoded_memchunk; + +    void *write_data; +    size_t write_length, write_index; + +    void *read_data; +    size_t read_length, read_index; + +    pa_usec_t latency; + +    /*esd_format_t format;*/ +    int32_t rate; + +    pa_smoother *smoother; +    int fd; + +    int64_t offset; +    int64_t encoding_overhead; +    int32_t next_encoding_overhead; +    double encoding_ratio; + +    pa_raop_client *raop; + +    size_t block_size; +}; + +static const char* const valid_modargs[] = { +    "sink_name", +    "sink_properties", +    "server", +    "format", +    "rate", +    "channels", +    "description", /* supported for compatibility reasons, made redundant by sink_properties= */ +    NULL +}; + +enum { +    SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX, +    SINK_MESSAGE_RIP_SOCKET +}; + +/* Forward declaration */ +static void sink_set_volume_cb(pa_sink *); + +static void on_connection(int fd, void*userdata) { +    int so_sndbuf = 0; +    socklen_t sl = sizeof(int); +    struct userdata *u = userdata; +    pa_assert(u); + +    pa_assert(u->fd < 0); +    u->fd = fd; + +    if (getsockopt(u->fd, SOL_SOCKET, SO_SNDBUF, &so_sndbuf, &sl) < 0) +        pa_log_warn("getsockopt(SO_SNDBUF) failed: %s", pa_cstrerror(errno)); +    else { +        pa_log_debug("SO_SNDBUF is %zu.", (size_t) so_sndbuf); +        pa_sink_set_max_request(u->sink, PA_MAX((size_t) so_sndbuf, u->block_size)); +    } + +    /* Set the initial volume */ +    sink_set_volume_cb(u->sink); + +    pa_log_debug("Connection authenticated, handing fd to IO thread..."); + +    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); +} + +static void on_close(void*userdata) { +    struct userdata *u = userdata; +    pa_assert(u); + +    pa_log_debug("Connection closed, informing IO thread..."); + +    pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RIP_SOCKET, NULL, 0, NULL, NULL); +} + +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; + +    switch (code) { + +        case PA_SINK_MESSAGE_SET_STATE: + +            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + +                case PA_SINK_SUSPENDED: +                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + +                    pa_smoother_pause(u->smoother, pa_rtclock_now()); + +                    /* Issue a FLUSH if we are connected */ +                    if (u->fd >= 0) { +                        pa_raop_flush(u->raop); +                    } +                    break; + +                case PA_SINK_IDLE: +                case PA_SINK_RUNNING: + +                    if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { +                        pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); + +                        /* The connection can be closed when idle, so check to +                           see if we need to reestablish it */ +                        if (u->fd < 0) +                            pa_raop_connect(u->raop); +                        else +                            pa_raop_flush(u->raop); +                    } + +                    break; + +                case PA_SINK_UNLINKED: +                case PA_SINK_INIT: +                case PA_SINK_INVALID_STATE: +                    ; +            } + +            break; + +        case PA_SINK_MESSAGE_GET_LATENCY: { +            pa_usec_t w, r; + +            r = pa_smoother_get(u->smoother, pa_rtclock_now()); +            w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec); + +            *((pa_usec_t*) data) = w > r ? w - r : 0; +            return 0; +        } + +        case SINK_MESSAGE_PASS_SOCKET: { +            struct pollfd *pollfd; + +            pa_assert(!u->rtpoll_item); + +            u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); +            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); +            pollfd->fd = u->fd; +            pollfd->events = POLLOUT; +            /*pollfd->events = */pollfd->revents = 0; + +            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { +                /* Our stream has been suspended so we just flush it.... */ +                pa_raop_flush(u->raop); +            } +            return 0; +        } + +        case SINK_MESSAGE_RIP_SOCKET: { +            pa_assert(u->fd >= 0); + +            pa_close(u->fd); +            u->fd = -1; + +            if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + +                pa_log_debug("RTSP control connection closed, but we're suspended so let's not worry about it... we'll open it again later"); + +                if (u->rtpoll_item) +                    pa_rtpoll_item_free(u->rtpoll_item); +                u->rtpoll_item = NULL; +            } else { +                /* Quesiton: is this valid here: or should we do some sort of: +                   return pa_sink_process_msg(PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL); +                   ?? */ +                pa_module_unload_request(u->module, TRUE); +            } +            return 0; +        } +    } + +    return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void sink_set_volume_cb(pa_sink *s) { +    struct userdata *u = s->userdata; +    pa_cvolume hw; +    pa_volume_t v; +    char t[PA_CVOLUME_SNPRINT_MAX]; + +    pa_assert(u); + +    /* If we're muted we don't need to do anything */ +    if (s->muted) +        return; + +    /* 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); + +    /* 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_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_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)); + +    /* Any necessary software volume manipulateion is done so set +       our hw volume (or v as a single value) on the device */ +    pa_raop_client_set_volume(u->raop, v); +} + +static void sink_set_mute_cb(pa_sink *s) { +    struct userdata *u = s->userdata; + +    pa_assert(u); + +    if (s->muted) { +        pa_raop_client_set_volume(u->raop, PA_VOLUME_MUTED); +    } else { +        sink_set_volume_cb(s); +    } +} + +static void thread_func(void *userdata) { +    struct userdata *u = userdata; +    int write_type = 0; +    pa_memchunk silence; +    uint32_t silence_overhead = 0; +    double silence_ratio = 0; + +    pa_assert(u); + +    pa_log_debug("Thread starting up"); + +    pa_thread_mq_install(&u->thread_mq); + +    pa_smoother_set_time_offset(u->smoother, pa_rtclock_now()); + +    /* Create a chunk of memory that is our encoded silence sample. */ +    pa_memchunk_reset(&silence); + +    for (;;) { +        int ret; + +        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) +            if (u->sink->thread_info.rewind_requested) +                pa_sink_process_rewind(u->sink, 0); + +        if (u->rtpoll_item) { +            struct pollfd *pollfd; +            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + +            /* Render some data and write it to the fifo */ +            if (/*PA_SINK_IS_OPENED(u->sink->thread_info.state) && */pollfd->revents) { +                pa_usec_t usec; +                int64_t n; +                void *p; + +                if (!silence.memblock) { +                    pa_memchunk silence_tmp; + +                    pa_memchunk_reset(&silence_tmp); +                    silence_tmp.memblock = pa_memblock_new(u->core->mempool, 4096); +                    silence_tmp.length = 4096; +                    p = pa_memblock_acquire(silence_tmp.memblock); +                      memset(p, 0, 4096); +                    pa_memblock_release(silence_tmp.memblock); +                    pa_raop_client_encode_sample(u->raop, &silence_tmp, &silence); +                    pa_assert(0 == silence_tmp.length); +                    silence_overhead = silence_tmp.length - 4096; +                    silence_ratio = silence_tmp.length / 4096; +                    pa_memblock_unref(silence_tmp.memblock); +                } + +                for (;;) { +                    ssize_t l; + +                    if (u->encoded_memchunk.length <= 0) { +                        if (u->encoded_memchunk.memblock) +                            pa_memblock_unref(u->encoded_memchunk.memblock); +                        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { +                            size_t rl; + +                            /* We render real data */ +                            if (u->raw_memchunk.length <= 0) { +                                if (u->raw_memchunk.memblock) +                                    pa_memblock_unref(u->raw_memchunk.memblock); +                                pa_memchunk_reset(&u->raw_memchunk); + +                                /* Grab unencoded data */ +                                pa_sink_render(u->sink, u->block_size, &u->raw_memchunk); +                            } +                            pa_assert(u->raw_memchunk.length > 0); + +                            /* Encode it */ +                            rl = u->raw_memchunk.length; +                            u->encoding_overhead += u->next_encoding_overhead; +                            pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk); +                            u->next_encoding_overhead = (u->encoded_memchunk.length - (rl - u->raw_memchunk.length)); +                            u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length); +                        } else { +                            /* We render some silence into our memchunk */ +                            memcpy(&u->encoded_memchunk, &silence, sizeof(pa_memchunk)); +                            pa_memblock_ref(silence.memblock); + +                            /* Calculate/store some values to be used with the smoother */ +                            u->next_encoding_overhead = silence_overhead; +                            u->encoding_ratio = silence_ratio; +                        } +                    } +                    pa_assert(u->encoded_memchunk.length > 0); + +                    p = pa_memblock_acquire(u->encoded_memchunk.memblock); +                    l = pa_write(u->fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type); +                    pa_memblock_release(u->encoded_memchunk.memblock); + +                    pa_assert(l != 0); + +                    if (l < 0) { + +                        if (errno == EINTR) +                            continue; +                        else if (errno == EAGAIN) { + +                            /* OK, we filled all socket buffers up +                             * now. */ +                            goto filled_up; + +                        } else { +                            pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); +                            goto fail; +                        } + +                    } else { +                        u->offset += l; + +                        u->encoded_memchunk.index += l; +                        u->encoded_memchunk.length -= l; + +                        pollfd->revents = 0; + +                        if (u->encoded_memchunk.length > 0) { +                            /* we've completely written the encoded data, so update our overhead */ +                            u->encoding_overhead += u->next_encoding_overhead; + +                            /* OK, we wrote less that we asked for, +                             * hence we can assume that the socket +                             * buffers are full now */ +                            goto filled_up; +                        } +                    } +                } + +            filled_up: + +                /* At this spot we know that the socket buffers are +                 * fully filled up. This is the best time to estimate +                 * the playback position of the server */ + +                n = u->offset - u->encoding_overhead; + +#ifdef SIOCOUTQ +                { +                    int l; +                    if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) +                        n -= (l / u->encoding_ratio); +                } +#endif + +                usec = pa_bytes_to_usec(n, &u->sink->sample_spec); + +                if (usec > u->latency) +                    usec -= u->latency; +                else +                    usec = 0; + +                pa_smoother_put(u->smoother, pa_rtclock_now(), usec); +            } + +            /* Hmm, nothing to do. Let's sleep */ +            pollfd->events = POLLOUT; /*PA_SINK_IS_OPENED(u->sink->thread_info.state)  ? POLLOUT : 0;*/ +        } + +        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) +            goto fail; + +        if (ret == 0) +            goto finish; + +        if (u->rtpoll_item) { +            struct pollfd* pollfd; + +            pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + +            if (pollfd->revents & ~POLLOUT) { +                if (u->sink->thread_info.state != PA_SINK_SUSPENDED) { +                    pa_log("FIFO shutdown."); +                    goto fail; +                } + +                /* We expect this to happen on occasion if we are not sending data. +                   It's perfectly natural and normal and natural */ +                if (u->rtpoll_item) +                    pa_rtpoll_item_free(u->rtpoll_item); +                u->rtpoll_item = NULL; +            } +        } +    } + +fail: +    /* If this was no regular exit from the loop we have to continue +     * processing messages until we received PA_MESSAGE_SHUTDOWN */ +    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); +    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: +    if (silence.memblock) +        pa_memblock_unref(silence.memblock); +    pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { +    struct userdata *u = NULL; +    pa_sample_spec ss; +    pa_modargs *ma = NULL; +    const char *server, *desc; +    pa_sink_new_data data; + +    pa_assert(m); + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("failed to parse module arguments"); +        goto fail; +    } + +    ss = m->core->default_sample_spec; +    if (pa_modargs_get_sample_spec(ma, &ss) < 0) { +        pa_log("invalid sample format specification"); +        goto fail; +    } + +    if ((/*ss.format != PA_SAMPLE_U8 &&*/ ss.format != PA_SAMPLE_S16NE) || +        (ss.channels > 2)) { +        pa_log("sample type support is limited to mono/stereo and U8 or S16NE sample data"); +        goto fail; +    } + +    u = pa_xnew0(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    m->userdata = u; +    u->fd = -1; +    u->smoother = pa_smoother_new( +            PA_USEC_PER_SEC, +            PA_USEC_PER_SEC*2, +            TRUE, +            TRUE, +            10, +            0, +            FALSE); +    pa_memchunk_reset(&u->raw_memchunk); +    pa_memchunk_reset(&u->encoded_memchunk); +    u->offset = 0; +    u->encoding_overhead = 0; +    u->next_encoding_overhead = 0; +    u->encoding_ratio = 1.0; + +    u->rtpoll = pa_rtpoll_new(); +    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); +    u->rtpoll_item = NULL; + +    /*u->format = +        (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) | +        (ss.channels == 2 ? ESD_STEREO : ESD_MONO);*/ +    u->rate = ss.rate; +    u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss); + +    u->read_data = u->write_data = NULL; +    u->read_index = u->write_index = u->read_length = u->write_length = 0; + +    /*u->state = STATE_AUTH;*/ +    u->latency = 0; + +    if (!(server = pa_modargs_get_value(ma, "server", NULL))) { +        pa_log("No server argument given."); +        goto fail; +    } + +    pa_sink_new_data_init(&data); +    data.driver = __FILE__; +    data.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_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server); +    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music"); +    if ((desc = pa_modargs_get_value(ma, "description", NULL))) +        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, desc); +    else +        pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server); + +    if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { +        pa_log("Invalid properties"); +        pa_sink_new_data_done(&data); +        goto fail; +    } + +    u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); +    pa_sink_new_data_done(&data); + +    if (!u->sink) { +        pa_log("Failed to create sink."); +        goto fail; +    } + +    u->sink->parent.process_msg = sink_process_msg; +    u->sink->userdata = u; +    u->sink->set_volume = sink_set_volume_cb; +    u->sink->set_mute = sink_set_mute_cb; +    u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK|PA_SINK_HW_VOLUME_CTRL; + +    pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); +    pa_sink_set_rtpoll(u->sink, u->rtpoll); + +    if (!(u->raop = pa_raop_client_new(u->core, server))) { +        pa_log("Failed to connect to server."); +        goto fail; +    } + +    pa_raop_client_set_callback(u->raop, on_connection, u); +    pa_raop_client_set_closed_callback(u->raop, on_close, u); + +    if (!(u->thread = pa_thread_new(thread_func, u))) { +        pa_log("Failed to create thread."); +        goto fail; +    } + +    pa_sink_put(u->sink); + +    pa_modargs_free(ma); + +    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; + +    if (u->sink) +        pa_sink_unlink(u->sink); + +    if (u->thread) { +        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); +        pa_thread_free(u->thread); +    } + +    pa_thread_mq_done(&u->thread_mq); + +    if (u->sink) +        pa_sink_unref(u->sink); + +    if (u->rtpoll_item) +        pa_rtpoll_item_free(u->rtpoll_item); + +    if (u->rtpoll) +        pa_rtpoll_free(u->rtpoll); + +    if (u->raw_memchunk.memblock) +        pa_memblock_unref(u->raw_memchunk.memblock); + +    if (u->encoded_memchunk.memblock) +        pa_memblock_unref(u->encoded_memchunk.memblock); + +    if (u->raop) +        pa_raop_client_free(u->raop); + +    pa_xfree(u->read_data); +    pa_xfree(u->write_data); + +    if (u->smoother) +        pa_smoother_free(u->smoother); + +    if (u->fd >= 0) +        pa_close(u->fd); + +    pa_xfree(u); +} diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c index b3f243c3..c4b02371 100644 --- a/src/modules/raop/raop_client.c +++ b/src/modules/raop/raop_client.c @@ -331,7 +331,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he              uint32_t port = pa_rtsp_serverport(c->rtsp);              pa_log_debug("RAOP: RECORDED"); -            if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { +            if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, TRUE, c->host, port))) {                  pa_log("failed to connect to server '%s:%d'", c->host, port);                  return;              } diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c new file mode 100644 index 00000000..13ecde2b --- /dev/null +++ b/src/modules/reserve-monitor.c @@ -0,0 +1,269 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ + +/*** +  Copyright 2009 Lennart Poettering + +  Permission is hereby granted, free of charge, to any person +  obtaining a copy of this software and associated documentation files +  (the "Software"), to deal in the Software without restriction, +  including without limitation the rights to use, copy, modify, merge, +  publish, distribute, sublicense, and/or sell copies of the Software, +  and to permit persons to whom the Software is furnished to do so, +  subject to the following conditions: + +  The above copyright notice and this permission notice shall be +  included in all copies or substantial portions of the Software. + +  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +  SOFTWARE. +***/ + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include "reserve-monitor.h" + +struct rm_monitor { +	int ref; + +	char *device_name; +	char *service_name; + +	DBusConnection *connection; + +	unsigned busy:1; +	unsigned filtering:1; +	unsigned matching:1; + +	rm_change_cb_t change_cb; +	void *userdata; +}; + +#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." + +static DBusHandlerResult filter_handler( +	DBusConnection *c, +	DBusMessage *s, +	void *userdata) { + +	DBusMessage *reply; +	rm_monitor *m; +	DBusError error; + +	dbus_error_init(&error); + +	m = userdata; +	assert(m->ref >= 1); + +	if (dbus_message_is_signal(s, "org.freedesktop.DBus", "NameOwnerChanged")) { +		const char *name, *old, *new; + +		if (!dbus_message_get_args( +			    s, +			    &error, +			    DBUS_TYPE_STRING, &name, +			    DBUS_TYPE_STRING, &old, +			    DBUS_TYPE_STRING, &new, +			    DBUS_TYPE_INVALID)) +			goto invalid; + +		if (strcmp(name, m->service_name) == 0) { +			m->busy = !!(new && *new); + +			/* If we ourselves own the device, then don't consider this 'busy' */ +			if (m->busy) { +				const char *un; + +				if ((un = dbus_bus_get_unique_name(c))) +					if (strcmp(new, un) == 0) +						m->busy = FALSE; +			} + +			if (m->change_cb) { +				m->ref++; +				m->change_cb(m); +				rm_release(m); +			} +		} +	} + +	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; +} + +int rm_watch( +	rm_monitor **_m, +	DBusConnection *connection, +	const char*device_name, +	rm_change_cb_t change_cb, +	DBusError *error)  { + +	rm_monitor *m = NULL; +	int r; +	DBusError _error; + +	if (!error) +		error = &_error; + +	dbus_error_init(error); + +	if (!_m) +		return -EINVAL; + +	if (!connection) +		return -EINVAL; + +	if (!device_name) +		return -EINVAL; + +	if (!(m = calloc(sizeof(rm_monitor), 1))) +		return -ENOMEM; + +	m->ref = 1; + +	if (!(m->device_name = strdup(device_name))) { +		r = -ENOMEM; +		goto fail; +	} + +	m->connection = dbus_connection_ref(connection); +	m->change_cb = change_cb; + +	if (!(m->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) { +		r = -ENOMEM; +		goto fail; +	} +	sprintf(m->service_name, SERVICE_PREFIX "%s", m->device_name); + +	if (!(dbus_connection_add_filter(m->connection, filter_handler, m, NULL))) { +		r = -ENOMEM; +		goto fail; +	} + +	m->filtering = 1; + +	dbus_bus_add_match(m->connection, +			   "type='signal'," +			   "sender='" DBUS_SERVICE_DBUS "'," +			   "interface='" DBUS_INTERFACE_DBUS "'," +			   "member='NameOwnerChanged'", error); + +	if (dbus_error_is_set(error)) { +		r = -EIO; +		goto fail; +	} + +	m->matching = 1; + +	m->busy = dbus_bus_name_has_owner(m->connection, m->service_name, error); + +	if (dbus_error_is_set(error)) { +		r = -EIO; +		goto fail; +	} + +	*_m = m; +	return 0; + +fail: +	if (&_error == error) +		dbus_error_free(&_error); + +	if (m) +		rm_release(m); + +	return r; +} + +void rm_release(rm_monitor *m) { +	if (!m) +		return; + +	assert(m->ref > 0); + +	if (--m->ref > 0) +		return; + +	if (m->matching) +		dbus_bus_remove_match( +			m->connection, +			"type='signal'," +			"sender='" DBUS_SERVICE_DBUS "'," +			"interface='" DBUS_INTERFACE_DBUS "'," +			"member='NameOwnerChanged'", NULL); + +	if (m->filtering) +		dbus_connection_remove_filter( +			m->connection, +			filter_handler, +			m); + +	free(m->device_name); +	free(m->service_name); + +	if (m->connection) +		dbus_connection_unref(m->connection); + +	free(m); +} + +int rm_busy(rm_monitor *m) { +	if (!m) +		return -EINVAL; + +	assert(m->ref > 0); + +	return m->busy; +} + +void rm_set_userdata(rm_monitor *m, void *userdata) { + +	if (!m) +		return; + +	assert(m->ref > 0); +	m->userdata = userdata; +} + +void* rm_get_userdata(rm_monitor *m) { + +	if (!m) +		return NULL; + +	assert(m->ref > 0); + +	return m->userdata; +} diff --git a/src/modules/reserve-monitor.h b/src/modules/reserve-monitor.h new file mode 100644 index 00000000..421a52e0 --- /dev/null +++ b/src/modules/reserve-monitor.h @@ -0,0 +1,72 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ + +#ifndef fooreservemonitorhfoo +#define fooreservemonitorhfoo + +/*** +  Copyright 2009 Lennart Poettering + +  Permission is hereby granted, free of charge, to any person +  obtaining a copy of this software and associated documentation files +  (the "Software"), to deal in the Software without restriction, +  including without limitation the rights to use, copy, modify, merge, +  publish, distribute, sublicense, and/or sell copies of the Software, +  and to permit persons to whom the Software is furnished to do so, +  subject to the following conditions: + +  The above copyright notice and this permission notice shall be +  included in all copies or substantial portions of the Software. + +  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +  SOFTWARE. +***/ + +#include <dbus/dbus.h> +#include <inttypes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct rm_monitor rm_monitor; + +/* Prototype for a function that is called whenever the reservation + * device of a device changes. Use rm_monitor_busy() to find out the + * new state.*/ +typedef void (*rm_change_cb_t)(rm_monitor *m); + +/* Creates a monitor for watching the lock status of a device. Returns + * 0 on success, a negative errno style return value on error.  The + * DBus error might be set as well if the error was caused D-Bus. */ +int rm_watch( +	rm_monitor **m,              /* On success a pointer to the newly allocated rm_device object will be filled in here */ +	DBusConnection *connection,  /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ +	const char *device_name,     /* The device to monitor, e.g. "Audio0" */ +	rm_change_cb_t change_cb,    /* Will be called whenever the lock status changes. May be NULL */ +	DBusError *error);           /* If we fail due to a D-Bus related issue the error will be filled in here. May be NULL. */ + +/* Free a rm_monitor object */ +void rm_release(rm_monitor *m); + +/* Checks whether the device is currently reserved, and returns 1 + * then, 0 if not, negative errno style error code value on error. */ +int rm_busy(rm_monitor *m); + +/* Attach a userdata pointer to an rm_monitor */ +void rm_set_userdata(rm_monitor *m, void *userdata); + +/* Query the userdata pointer from an rm_monitor. Returns NULL if no + * userdata was set. */ +void* rm_get_userdata(rm_monitor *m); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c index 7d339270..6086fc99 100644 --- a/src/modules/reserve-wrap.c +++ b/src/modules/reserve-wrap.c @@ -23,6 +23,8 @@  #include <config.h>  #endif +#include <errno.h> +  #include <pulse/xmalloc.h>  #include <pulse/i18n.h> @@ -30,30 +32,48 @@  #include <pulsecore/core-util.h>  #include <pulsecore/shared.h> -#include <modules/dbus-util.h> - +#ifdef HAVE_DBUS +#include <pulsecore/dbus-shared.h>  #include "reserve.h" +#include "reserve-monitor.h" +#endif +  #include "reserve-wrap.h"  struct pa_reserve_wrapper {      PA_REFCNT_DECLARE;      pa_core *core; -    pa_dbus_connection *connection;      pa_hook hook; +    char *shared_name; +#ifdef HAVE_DBUS +    pa_dbus_connection *connection;      struct rd_device *device; +#endif +}; + +struct pa_reserve_monitor_wrapper { +    PA_REFCNT_DECLARE; +    pa_core *core; +    pa_hook hook;      char *shared_name; +#ifdef HAVE_DBUS +    pa_dbus_connection *connection; +    struct rm_monitor *monitor; +#endif  };  static void reserve_wrapper_free(pa_reserve_wrapper *r) {      pa_assert(r); +#ifdef HAVE_DBUS      if (r->device)          rd_release(r->device); -    pa_hook_done(&r->hook); -      if (r->connection)          pa_dbus_connection_unref(r->connection); +#endif + +    pa_hook_done(&r->hook);      if (r->shared_name) {          pa_assert_se(pa_shared_remove(r->core, r->shared_name) >= 0); @@ -63,6 +83,7 @@ static void reserve_wrapper_free(pa_reserve_wrapper *r) {      pa_xfree(r);  } +#ifdef HAVE_DBUS  static int request_cb(rd_device *d, int forced) {      pa_reserve_wrapper *r;      int k; @@ -74,20 +95,23 @@ static int request_cb(rd_device *d, int forced) {      PA_REFCNT_INC(r);      k = pa_hook_fire(&r->hook, PA_INT_TO_PTR(forced)); -    pa_log_debug("Device unlock has been requested and %s.", k < 0 ? "failed" : "succeeded"); +    pa_log_debug("Device unlock of %s has been requested and %s.", r->shared_name, k < 0 ? "failed" : "succeeded");      pa_reserve_wrapper_unref(r);      return k < 0 ? -1 : 1;  } +#endif  pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) {      pa_reserve_wrapper *r; -    DBusError error;      int k;      char *t; +#ifdef HAVE_DBUS +    DBusError error;      dbus_error_init(&error); +#endif      pa_assert(c);      pa_assert(device_name); @@ -111,9 +135,13 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name)      pa_assert_se(pa_shared_set(c, r->shared_name, r) >= 0); +#ifdef HAVE_DBUS      if (!(r->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { -        pa_log_error("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); -        goto fail; +        pa_log_warn("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. */ +        return r;      }      if ((k = rd_acquire( @@ -125,8 +153,13 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name)                   request_cb,                   NULL)) < 0) { -        pa_log_error("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); -        goto fail; +        if (k == -EBUSY) { +            pa_log_error("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)); +            return r; +        }      }      pa_log_debug("Successfully acquired reservation lock on device '%s'", device_name); @@ -134,13 +167,15 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name)      rd_set_userdata(r->device, r);      return r; -  fail:      dbus_error_free(&error);      reserve_wrapper_free(r);      return NULL; +#else +    return r; +#endif  }  void pa_reserve_wrapper_unref(pa_reserve_wrapper *r) { @@ -164,5 +199,146 @@ void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const      pa_assert(r);      pa_assert(PA_REFCNT_VALUE(r) >= 1); +#ifdef HAVE_DBUS      rd_set_application_device_name(r->device, name); +#endif +} + +static void reserve_monitor_wrapper_free(pa_reserve_monitor_wrapper *w) { +    pa_assert(w); + +#ifdef HAVE_DBUS +    if (w->monitor) +        rm_release(w->monitor); + +    if (w->connection) +        pa_dbus_connection_unref(w->connection); +#endif + +    pa_hook_done(&w->hook); + +    if (w->shared_name) { +        pa_assert_se(pa_shared_remove(w->core, w->shared_name) >= 0); +        pa_xfree(w->shared_name); +    } + +    pa_xfree(w); +} + +#ifdef HAVE_DBUS +static void change_cb(rm_monitor *m) { +    pa_reserve_monitor_wrapper *w; +    int k; + +    pa_assert(m); +    pa_assert_se(w = rm_get_userdata(m)); +    pa_assert(PA_REFCNT_VALUE(w) >= 1); + +    PA_REFCNT_INC(w); + +    if ((k = rm_busy(w->monitor)) < 0) +        return; + +    pa_hook_fire(&w->hook, PA_INT_TO_PTR(!!k)); +    pa_log_debug("Device lock status of %s changed: %s", w->shared_name, k ? "busy" : "not busy"); + +    pa_reserve_monitor_wrapper_unref(w); +} +#endif + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name) { +    pa_reserve_monitor_wrapper *w; +    int k; +    char *t; +#ifdef HAVE_DBUS +    DBusError error; + +    dbus_error_init(&error); +#endif + +    pa_assert(c); +    pa_assert(device_name); + +    t = pa_sprintf_malloc("reserve-monitor-wrapper@%s", device_name); + +    if ((w = pa_shared_get(c, t))) { +        pa_xfree(t); + +        pa_assert(PA_REFCNT_VALUE(w) >= 1); +        PA_REFCNT_INC(w); + +        return w; +    } + +    w = pa_xnew0(pa_reserve_monitor_wrapper, 1); +    PA_REFCNT_INIT(w); +    w->core = c; +    pa_hook_init(&w->hook, w); +    w->shared_name = t; + +    pa_assert_se(pa_shared_set(c, w->shared_name, w) >= 0); + +#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); + +        /* We don't treat this as error here because we want allow PA +         * to run even when no session bus is available. */ +        return w; +    } + +    if ((k = rm_watch( +                 &w->monitor, +                 pa_dbus_connection_get(w->connection), +                 device_name, +                 change_cb, +                 NULL)) < 0) { + +        pa_log_warn("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k)); +        goto fail; +    } + +    pa_log_debug("Successfully create reservation lock monitor for device '%s'", device_name); + +    rm_set_userdata(w->monitor, w); +    return w; + +fail: +    dbus_error_free(&error); + +    reserve_monitor_wrapper_free(w); + +    return NULL; +#else +    return w; +#endif +} + +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *w) { +    pa_assert(w); +    pa_assert(PA_REFCNT_VALUE(w) >= 1); + +    if (PA_REFCNT_DEC(w) > 0) +        return; + +    reserve_monitor_wrapper_free(w); +} + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *w) { +    pa_assert(w); +    pa_assert(PA_REFCNT_VALUE(w) >= 1); + +    return &w->hook; +} + +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *w) { +    pa_assert(w); + +    pa_assert(PA_REFCNT_VALUE(w) >= 1); + +#ifdef HAVE_DBUS +    return rm_busy(w->monitor) > 0; +#else +    return FALSE; +#endif  } diff --git a/src/modules/reserve-wrap.h b/src/modules/reserve-wrap.h index 2b97c91c..2de6c093 100644 --- a/src/modules/reserve-wrap.h +++ b/src/modules/reserve-wrap.h @@ -28,11 +28,18 @@  typedef struct pa_reserve_wrapper pa_reserve_wrapper;  pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name); -  void pa_reserve_wrapper_unref(pa_reserve_wrapper *r);  pa_hook* pa_reserve_wrapper_hook(pa_reserve_wrapper *r);  void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const char *name); +typedef struct pa_reserve_monitor_wrapper pa_reserve_monitor_wrapper; + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name); +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *m); + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *m); +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *m); +  #endif diff --git a/src/modules/reserve.c b/src/modules/reserve.c index 9a9591d2..5597f177 100644 --- a/src/modules/reserve.c +++ b/src/modules/reserve.c @@ -1,3 +1,5 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ +  /***    Copyright 2009 Lennart Poettering @@ -43,16 +45,15 @@ struct rd_device {  	DBusConnection *connection; -	int owning:1; -	int registered:1; -	int filtering:1; -	int gave_up:1; +	unsigned owning:1; +	unsigned registered:1; +	unsigned filtering:1; +	unsigned gave_up:1;  	rd_request_cb_t request_cb;  	void *userdata;  }; -  #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."  #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/" @@ -297,6 +298,7 @@ static DBusHandlerResult filter_handler(  	dbus_error_init(&error);  	d = userdata; +	assert(d->ref >= 1);  	if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {  		const char *name; @@ -560,7 +562,7 @@ void rd_release(  	assert(d->ref > 0); -	if (--d->ref) +	if (--d->ref > 0)  		return; @@ -575,17 +577,11 @@ void rd_release(  			d->connection,  			d->object_path); -	if (d->owning) { -		DBusError error; -		dbus_error_init(&error); - +	if (d->owning)  		dbus_bus_release_name(  			d->connection,  			d->service_name, -			&error); - -		dbus_error_free(&error); -	} +			NULL);  	free(d->device_name);  	free(d->application_name); diff --git a/src/modules/reserve.h b/src/modules/reserve.h index b315a08c..9ae49cf5 100644 --- a/src/modules/reserve.h +++ b/src/modules/reserve.h @@ -1,3 +1,5 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ +  #ifndef fooreservehfoo  #define fooreservehfoo @@ -28,6 +30,10 @@  #include <dbus/dbus.h>  #include <inttypes.h> +#ifdef __cplusplus +extern "C" { +#endif +  typedef struct rd_device rd_device;  /* Prototype for a function that is called whenever someone else wants @@ -45,7 +51,7 @@ typedef int (*rd_request_cb_t)(   * the error was caused D-Bus. */  int rd_acquire(  	rd_device **d,                /* On success a pointer to the newly allocated rd_device object will be filled in here */ -	DBusConnection *connection, +	DBusConnection *connection,   /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */  	const char *device_name,      /* The device to lock, e.g. "Audio0" */  	const char *application_name, /* A human readable name of the application, e.g. "PulseAudio Sound Server" */  	int32_t priority,             /* The priority for this application. If unsure use 0 */ @@ -66,4 +72,8 @@ void rd_set_userdata(rd_device *d, void *userdata);   * userdata was set. */  void* rd_get_userdata(rd_device *d); +#ifdef __cplusplus +} +#endif +  #endif diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 33e23af2..5caf8272 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -33,6 +33,7 @@  #include <unistd.h>  #include <poll.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h> @@ -43,15 +44,17 @@  #include <pulsecore/sink-input.h>  #include <pulsecore/memblockq.h>  #include <pulsecore/log.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/modargs.h>  #include <pulsecore/namereg.h>  #include <pulsecore/sample-util.h>  #include <pulsecore/macro.h>  #include <pulsecore/atomic.h> -#include <pulsecore/rtclock.h>  #include <pulsecore/atomic.h>  #include <pulsecore/time-smoother.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/once.h>  #include "module-rtp-recv-symdef.h" @@ -60,7 +63,7 @@  #include "sap.h"  PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP"); +PA_MODULE_DESCRIPTION("Receive data from a network via RTP/SAP/SDP");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE( @@ -110,6 +113,7 @@ struct session {  struct userdata {      pa_module *module; +    pa_core *core;      pa_sap_context sap_context;      pa_io_event* sap_event; @@ -165,7 +169,7 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {      pa_memblockq_rewind(s->memblockq, nbytes);  } -/* Called from thread context */ +/* Called from I/O thread context */  static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {      struct session *s; @@ -184,11 +188,24 @@ static void sink_input_kill(pa_sink_input* i) {      session_free(s);  } +/* Called from IO context */ +static void sink_input_suspend_within_thread(pa_sink_input* i, pa_bool_t b) { +    struct session *s; +    pa_sink_input_assert_ref(i); +    pa_assert_se(s = i->userdata); + +    if (b) { +        pa_smoother_pause(s->smoother, pa_rtclock_now()); +        pa_memblockq_flush_read(s->memblockq); +    } else +        s->first_packet = FALSE; +} +  /* Called from I/O thread context */  static int rtpoll_work_cb(pa_rtpoll_item *i) {      pa_memchunk chunk;      int64_t k, j, delta; -    struct timeval now; +    struct timeval now = { 0, 0 };      struct session *s;      struct pollfd *p; @@ -206,10 +223,11 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {      p->revents = 0; -    if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool) < 0) +    if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool, &now) < 0)          return 0; -    if (s->sdp_info.payload != s->rtp_context.payload) { +    if (s->sdp_info.payload != s->rtp_context.payload || +        !PA_SINK_IS_OPENED(s->sink_input->sink->thread_info.state)) {          pa_memblock_unref(chunk.memblock);          return 0;      } @@ -229,7 +247,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {          }      } -    /* Check wheter there was a timestamp overflow */ +    /* Check whether there was a timestamp overflow */      k = (int64_t) s->rtp_context.timestamp - (int64_t) s->offset;      j = (int64_t) 0x100000000LL - (int64_t) s->offset + (int64_t) s->rtp_context.timestamp; @@ -238,15 +256,24 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {      else          delta = j; -    pa_memblockq_seek(s->memblockq, delta * (int64_t) s->rtp_context.frame_size, PA_SEEK_RELATIVE); +    pa_memblockq_seek(s->memblockq, delta * (int64_t) s->rtp_context.frame_size, PA_SEEK_RELATIVE, TRUE); -    pa_rtclock_get(&now); +    if (now.tv_sec == 0) { +        PA_ONCE_BEGIN { +            pa_log_warn("Using artificial time instead of timestamp"); +        } PA_ONCE_END; +        pa_rtclock_get(&now); +    } else +        pa_rtclock_from_wallclock(&now);      pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); +    /* Tell the smoother that we are rolling now, in case it is still paused */ +    pa_smoother_resume(s->smoother, pa_timeval_load(&now), TRUE); +      if (pa_memblockq_push(s->memblockq, &chunk) < 0) {          pa_log_warn("Queue overrun"); -        pa_memblockq_seek(s->memblockq, (int64_t) chunk.length, PA_SEEK_RELATIVE); +        pa_memblockq_seek(s->memblockq, (int64_t) chunk.length, PA_SEEK_RELATIVE, TRUE);      }  /*     pa_log("blocks in q: %u", pa_memblockq_get_nblocks(s->memblockq)); */ @@ -262,14 +289,14 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {          pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix;          unsigned fix_samples; -        pa_log("Updating sample rate"); +        pa_log_debug("Updating sample rate");          wi = pa_smoother_get(s->smoother, pa_timeval_load(&now));          ri = pa_bytes_to_usec((uint64_t) pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); -        if (PA_MSGOBJECT(s->sink_input->sink)->process_msg(PA_MSGOBJECT(s->sink_input->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_delay, 0, NULL) < 0) -            sink_delay = 0; +        pa_log_debug("wi=%lu ri=%lu", (unsigned long) wi, (unsigned long) ri); +        sink_delay = pa_sink_get_latency_within_thread(s->sink_input->sink);          render_delay = pa_bytes_to_usec(pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq), &s->sink_input->sink->sample_spec);          if (ri > render_delay+sink_delay) @@ -294,14 +321,20 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {          fix_samples = (unsigned) (fix * (pa_usec_t) s->sink_input->thread_info.sample_spec.rate / (pa_usec_t) RATE_UPDATE_INTERVAL);          /* Check if deviation is in bounds */ -        if (fix_samples > s->sink_input->sample_spec.rate*.20) +        if (fix_samples > s->sink_input->sample_spec.rate*.50)              pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples); +        else { +            /* Fix up rate */ +            if (latency < s->intended_latency) +                s->sink_input->sample_spec.rate -= fix_samples; +            else +                s->sink_input->sample_spec.rate += fix_samples; + +            if (s->sink_input->sample_spec.rate > PA_RATE_MAX) +                s->sink_input->sample_spec.rate = PA_RATE_MAX; +        } -        /* Fix up rate */ -        if (latency < s->intended_latency) -            s->sink_input->sample_spec.rate -= fix_samples; -        else -            s->sink_input->sample_spec.rate += fix_samples; +        pa_assert(pa_sample_spec_valid(&s->sink_input->sample_spec));          pa_resampler_set_input_rate(s->sink_input->thread_info.resampler, s->sink_input->sample_spec.rate); @@ -362,6 +395,14 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) {          goto fail;      } +    pa_make_udp_socket_low_delay(fd); + +    one = 1; +    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) { +        pa_log("SO_TIMESTAMP failed: %s", pa_cstrerror(errno)); +        goto fail; +    } +      one = 1;      if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {          pa_log("SO_REUSEADDR failed: %s", pa_cstrerror(errno)); @@ -430,8 +471,14 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in      s->sdp_info = *sdp_info;      s->rtpoll_item = NULL;      s->intended_latency = LATENCY_USEC; -    s->smoother = pa_smoother_new(PA_USEC_PER_SEC*5, PA_USEC_PER_SEC*2, TRUE, 10); -    pa_smoother_set_time_offset(s->smoother, pa_timeval_load(&now)); +    s->smoother = pa_smoother_new( +            PA_USEC_PER_SEC*5, +            PA_USEC_PER_SEC*2, +            TRUE, +            TRUE, +            10, +            pa_timeval_load(&now), +            TRUE);      s->last_rate_update = pa_timeval_load(&now);      pa_atomic_store(&s->timestamp, (int) now.tv_sec); @@ -472,6 +519,7 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in      s->sink_input->kill = sink_input_kill;      s->sink_input->attach = sink_input_attach;      s->sink_input->detach = sink_input_detach; +    s->sink_input->suspend_within_thread = sink_input_suspend_within_thread;      pa_sink_input_get_silence(s->sink_input, &silence); @@ -575,15 +623,13 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event      }  } -static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *ptv, void *userdata) { +static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) {      struct session *s, *n;      struct userdata *u = userdata;      struct timeval now; -    struct timeval tv;      pa_assert(m);      pa_assert(t); -    pa_assert(ptv);      pa_assert(u);      pa_rtclock_get(&now); @@ -601,9 +647,7 @@ static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const str      }      /* Restart timer */ -    pa_gettimeofday(&tv); -    pa_timeval_add(&tv, DEATH_TIMEOUT*PA_USEC_PER_SEC); -    m->time_restart(t, &tv); +    pa_core_rttime_restart(u->module->core, t, pa_rtclock_now() + DEATH_TIMEOUT * PA_USEC_PER_SEC);  }  int pa__init(pa_module*m) { @@ -617,7 +661,6 @@ int pa__init(pa_module*m) {      socklen_t salen;      const char *sap_address;      int fd = -1; -    struct timeval tv;      pa_assert(m); @@ -648,9 +691,9 @@ int pa__init(pa_module*m) {      if ((fd = mcast_socket(sa, salen)) < 0)          goto fail; -    u = pa_xnew(struct userdata, 1); -    m->userdata = u; +    m->userdata = u = pa_xnew(struct userdata, 1);      u->module = m; +    u->core = m->core;      u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));      u->sap_event = m->core->mainloop->io_new(m->core->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u); @@ -660,9 +703,7 @@ int pa__init(pa_module*m) {      u->n_sessions = 0;      u->by_origin = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); -    pa_gettimeofday(&tv); -    pa_timeval_add(&tv, DEATH_TIMEOUT * PA_USEC_PER_SEC); -    u->check_death_event = m->core->mainloop->time_new(m->core->mainloop, &tv, check_death_event_cb, u); +    u->check_death_event = pa_core_rttime_new(m->core, pa_rtclock_now() + DEATH_TIMEOUT * PA_USEC_PER_SEC, check_death_event_cb, u);      pa_modargs_free(ma); diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index 722d12bd..f147364d 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -31,6 +31,7 @@  #include <string.h>  #include <unistd.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/util.h>  #include <pulse/xmalloc.h> @@ -77,7 +78,7 @@ PA_MODULE_USAGE(  #define DEFAULT_DESTINATION "224.0.0.56"  #define MEMBLOCKQ_MAXLENGTH (1024*170)  #define DEFAULT_MTU 1280 -#define SAP_INTERVAL 5 +#define SAP_INTERVAL (5*PA_USEC_PER_SEC)  static const char* const valid_modargs[] = {      "source", @@ -151,18 +152,14 @@ static void source_output_kill(pa_source_output* o) {  static void sap_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) {      struct userdata *u = userdata; -    struct timeval next;      pa_assert(m);      pa_assert(t); -    pa_assert(tv);      pa_assert(u);      pa_sap_send(&u->sap_context, 0); -    pa_gettimeofday(&next); -    pa_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC); -    m->time_restart(t, &next); +    pa_core_rttime_restart(u->module->core, t, pa_rtclock_now() + SAP_INTERVAL);  }  int pa__init(pa_module*m) { @@ -186,7 +183,6 @@ int pa__init(pa_module*m) {      char *p;      int r, j;      socklen_t k; -    struct timeval tv;      char hn[128], *n;      pa_bool_t loop = FALSE;      pa_source_output_new_data data; @@ -347,10 +343,10 @@ int pa__init(pa_module*m) {      o->push = source_output_push;      o->kill = source_output_kill; -    u = pa_xnew(struct userdata, 1); -    m->userdata = u; -    o->userdata = u; +    pa_log_info("Configured source latency of %llu ms.", +                (unsigned long long) pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); +    m->userdata = o->userdata = u = pa_xnew(struct userdata, 1);      u->module = m;      u->source_output = o; @@ -395,9 +391,7 @@ int pa__init(pa_module*m) {      pa_sap_send(&u->sap_context, 0); -    pa_gettimeofday(&tv); -    pa_timeval_add(&tv, SAP_INTERVAL * PA_USEC_PER_SEC); -    u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u); +    u->sap_event = pa_core_rttime_new(m->core, pa_rtclock_now() + SAP_INTERVAL, sap_event_cb, u);      pa_source_output_put(u->source_output); diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 7537c1f5..6706a10f 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -162,13 +162,16 @@ pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame      return c;  } -int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { +int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool, struct timeval *tstamp) {      int size;      struct msghdr m; +    struct cmsghdr *cm;      struct iovec iov;      uint32_t header;      unsigned cc;      ssize_t r; +    uint8_t aux[1024]; +    pa_bool_t found_tstamp = FALSE;      pa_assert(c);      pa_assert(chunk); @@ -208,8 +211,8 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {      m.msg_namelen = 0;      m.msg_iov = &iov;      m.msg_iovlen = 1; -    m.msg_control = NULL; -    m.msg_controllen = 0; +    m.msg_control = aux; +    m.msg_controllen = sizeof(aux);      m.msg_flags = 0;      r = recvmsg(c->fd, &m, 0); @@ -275,6 +278,18 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {          pa_memchunk_reset(&c->memchunk);      } +    for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) { +        if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) +            memcpy(tstamp, CMSG_DATA(cm), sizeof(struct timeval)); +            found_tstamp = TRUE; +            break; +        } + +    if (!found_tstamp) { +        pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); +        memset(tstamp, 0, sizeof(tstamp)); +    } +      return 0;  fail: diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h index eff5e75b..b197e82f 100644 --- a/src/modules/rtp/rtp.h +++ b/src/modules/rtp/rtp.h @@ -43,7 +43,7 @@ pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssr  int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q);  pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size); -int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool); +int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool, struct timeval *tstamp);  void pa_rtp_context_destroy(pa_rtp_context *c); diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 98db05dd..72d304e8 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -30,6 +30,7 @@  #include <arpa/inet.h>  #include <unistd.h>  #include <sys/ioctl.h> +#include <netinet/in.h>  #ifdef HAVE_SYS_FILIO_H  #include <sys/filio.h> @@ -211,7 +212,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {      }      if (!strlen(s2)) {          /* End of headers */ -        /* We will have a header left from our looping itteration, so add it in :) */ +        /* We will have a header left from our looping iteration, so add it in :) */          if (c->last_header) {              /* 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)); @@ -332,7 +333,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) {      pa_xfree(c->session);      c->session = NULL; -    if (!(c->sc = pa_socket_client_new_string(c->mainloop, c->hostname, c->port))) { +    if (!(c->sc = pa_socket_client_new_string(c->mainloop, TRUE, c->hostname, c->port))) {          pa_log("failed to connect to server '%s:%d'", c->hostname, c->port);          return -1;      } @@ -488,7 +489,7 @@ int pa_rtsp_record(pa_rtsp_client* c, uint16_t* seq, uint32_t* rtptime) {      pa_assert(c);      if (!c->session) { -        /* No seesion in progres */ +        /* No session in progress */          return -1;      } diff --git a/src/modules/udev-util.c b/src/modules/udev-util.c index 8ffb76a8..cc824465 100644 --- a/src/modules/udev-util.c +++ b/src/modules/udev-util.c @@ -58,15 +58,14 @@ static int read_id(struct udev_device *d, const char *n) {      return u;  } -int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { +int pa_udev_get_info(int card_idx, pa_proplist *p) {      int r = -1;      struct udev *udev; -    struct udev_device *card; +    struct udev_device *card = NULL;      char *t;      const char *v;      int id; -    pa_assert(core);      pa_assert(p);      pa_assert(card_idx >= 0); @@ -84,12 +83,25 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) {          goto finish;      } +    if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH)) +        if (((v = udev_device_get_property_value(card, "ID_PATH")) && *v) || +            (v = udev_device_get_devpath(card))) +            pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v); + +    if (!pa_proplist_contains(p, "sysfs.path")) +        if ((v = udev_device_get_devpath(card))) +            pa_proplist_sets(p, "sysfs.path", v); + +    if (!pa_proplist_contains(p, "udev.id")) +        if ((v = udev_device_get_property_value(card, "ID_ID")) && *v) +            pa_proplist_sets(p, "udev.id", v); +      if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS))          if ((v = udev_device_get_property_value(card, "ID_BUS")) && *v)              pa_proplist_sets(p, PA_PROP_DEVICE_BUS, v);      if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_ID)) -        if ((id = read_id(card, "ID_VENDOR_ID")) > 0 && *v) +        if ((id = read_id(card, "ID_VENDOR_ID")) > 0)              pa_proplist_setf(p, PA_PROP_DEVICE_VENDOR_ID, "%04x", id);      if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_NAME)) { @@ -114,15 +126,15 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) {          if ((v = udev_device_get_property_value(card, "ID_SERIAL")) && *v)              pa_proplist_sets(p, PA_PROP_DEVICE_SERIAL, v); +    if (!pa_proplist_contains(p, PA_PROP_DEVICE_CLASS)) +        if ((v = udev_device_get_property_value(card, "SOUND_CLASS")) && *v) +            pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, v); +      if (!pa_proplist_contains(p, PA_PROP_DEVICE_FORM_FACTOR))          if ((v = udev_device_get_property_value(card, "SOUND_FORM_FACTOR")) && *v)              pa_proplist_sets(p, PA_PROP_DEVICE_FORM_FACTOR, v); -    if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH)) -        if ((v = udev_device_get_devpath(card))) -            pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v); - -    /* This is normaly not set by th udev rules but may be useful to +    /* This is normaly not set by the udev rules but may be useful to       * allow administrators to overwrite the device description.*/      if (!pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))          if ((v = udev_device_get_property_value(card, "SOUND_DESCRIPTION")) && *v) @@ -140,3 +152,40 @@ finish:      return r;  } + +char* pa_udev_get_property(int card_idx, const char *name) { +    struct udev *udev; +    struct udev_device *card = NULL; +    char *t, *r = NULL; +    const char *v; + +    pa_assert(card_idx >= 0); +    pa_assert(name); + +    if (!(udev = udev_new())) { +        pa_log_error("Failed to allocate udev context."); +        goto finish; +    } + +    t = pa_sprintf_malloc("%s/class/sound/card%i", udev_get_sys_path(udev), card_idx); +    card = udev_device_new_from_syspath(udev, t); +    pa_xfree(t); + +    if (!card) { +        pa_log_error("Failed to get card object."); +        goto finish; +    } + +    if ((v = udev_device_get_property_value(card, name)) && *v) +        r = pa_xstrdup(v); + +finish: + +    if (card) +        udev_device_unref(card); + +    if (udev) +        udev_unref(udev); + +    return r; +} diff --git a/src/modules/udev-util.h b/src/modules/udev-util.h index 5120abdd..8523bc4d 100644 --- a/src/modules/udev-util.h +++ b/src/modules/udev-util.h @@ -25,6 +25,7 @@  #include <pulsecore/core.h> -int pa_udev_get_info(pa_core *core, pa_proplist *p, int card); +int pa_udev_get_info(int card_idx, pa_proplist *p); +char* pa_udev_get_property(int card_idx, const char *name);  #endif diff --git a/src/modules/x11/module-x11-bell.c b/src/modules/x11/module-x11-bell.c new file mode 100644 index 00000000..ac303c3b --- /dev/null +++ b/src/modules/x11/module-x11-bell.c @@ -0,0 +1,190 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <X11/Xlib.h> +#include <X11/XKBlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/x11wrap.h> + +#include "module-x11-bell-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 bell interceptor"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>"); + +static const char* const valid_modargs[] = { +    "sink", +    "sample", +    "display", +    NULL +}; + +struct userdata { +    pa_core *core; +    pa_module *module; + +    int xkb_event_base; + +    char *sink_name; +    char *scache_item; + +    pa_x11_wrapper *x11_wrapper; +    pa_x11_client *x11_client; +}; + +static int x11_event_cb(pa_x11_wrapper *w, XEvent *e, void *userdata) { +    XkbBellNotifyEvent *bne; +    struct userdata *u = userdata; + +    pa_assert(w); +    pa_assert(e); +    pa_assert(u); +    pa_assert(u->x11_wrapper == w); + +    if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify) +        return 0; + +    bne = (XkbBellNotifyEvent*) e; + +    if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, ((pa_volume_t) bne->percent*PA_VOLUME_NORM)/100U, NULL, NULL) < 0) { +        pa_log_info("Ringing bell failed, reverting to X11 device bell."); +        XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent); +    } + +    return 1; +} + +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { +    struct userdata *u = userdata; + +    pa_assert(w); +    pa_assert(u); +    pa_assert(u->x11_wrapper == w); + +    if (u->x11_client) +        pa_x11_client_free(u->x11_client); + +    if (u->x11_wrapper) +        pa_x11_wrapper_unref(u->x11_wrapper); + +    u->x11_client = NULL; +    u->x11_wrapper = NULL; + +    pa_module_unload_request(u->module, TRUE); +} + +int pa__init(pa_module*m) { + +    struct userdata *u = NULL; +    pa_modargs *ma = NULL; +    int major, minor; +    unsigned int auto_ctrls, auto_values; + +    pa_assert(m); + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("Failed to parse module arguments"); +        goto fail; +    } + +    m->userdata = u = pa_xnew(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "bell-window-system")); +    u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); +    u->x11_client = NULL; + +    if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) +        goto fail; + +    major = XkbMajorVersion; +    minor = XkbMinorVersion; + +    if (!XkbLibraryVersion(&major, &minor)) { +        pa_log("XkbLibraryVersion() failed"); +        goto fail; +    } + +    major = XkbMajorVersion; +    minor = XkbMinorVersion; + +    if (!XkbQueryExtension(pa_x11_wrapper_get_display(u->x11_wrapper), NULL, &u->xkb_event_base, NULL, &major, &minor)) { +        pa_log("XkbQueryExtension() failed"); +        goto fail; +    } + +    XkbSelectEvents(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask); +    auto_ctrls = auto_values = XkbAudibleBellMask; +    XkbSetAutoResetControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbAudibleBellMask, &auto_ctrls, &auto_values); +    XkbChangeEnabledControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbAudibleBellMask, 0); + +    u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_cb, x11_kill_cb, 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; + +    pa_xfree(u->scache_item); +    pa_xfree(u->sink_name); + +    if (u->x11_client) +        pa_x11_client_free(u->x11_client); + +    if (u->x11_wrapper) +        pa_x11_wrapper_unref(u->x11_wrapper); + +    pa_xfree(u); +} diff --git a/src/modules/x11/module-x11-cork-request.c b/src/modules/x11/module-x11-cork-request.c new file mode 100644 index 00000000..c1380c27 --- /dev/null +++ b/src/modules/x11/module-x11-cork-request.c @@ -0,0 +1,189 @@ +/*** +  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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <X11/Xlib.h> +#include <X11/extensions/XTest.h> +#include <X11/XF86keysym.h> +#include <X11/keysym.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/x11wrap.h> +#include <pulsecore/core-util.h> + +#include "module-x11-cork-request-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Synthesize X11 media key events when cork/uncork is requested"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("display=<X11 display>"); + +static const char* const valid_modargs[] = { +    "display", +    NULL +}; + +struct userdata { +    pa_module *module; + +    pa_x11_wrapper *x11_wrapper; +    pa_x11_client *x11_client; + +    pa_hook_slot *hook_slot; +}; + +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { +    struct userdata *u = userdata; + +    pa_assert(w); +    pa_assert(u); +    pa_assert(u->x11_wrapper == w); + +    if (u->x11_client) { +        pa_x11_client_free(u->x11_client); +        u->x11_client = NULL; +    } + +    if (u->x11_wrapper) { +        pa_x11_wrapper_unref(u->x11_wrapper); +        u->x11_wrapper = NULL; +    } + +    pa_module_unload_request(u->module, TRUE); +} + +static pa_hook_result_t sink_input_send_event_hook_cb( +        pa_core *c, +        pa_sink_input_send_event_hook_data *data, +        struct userdata *u) { + +    KeySym sym; +    KeyCode code; +    Display *display; + +    pa_assert(c); +    pa_assert(data); +    pa_assert(u); + +    if (pa_streq(data->event, PA_STREAM_EVENT_REQUEST_CORK)) +        sym = XF86XK_AudioPause; +    else if (pa_streq(data->event, PA_STREAM_EVENT_REQUEST_UNCORK)) +        sym = XF86XK_AudioPlay; +    else +        return PA_HOOK_OK; + +    pa_log_debug("Triggering X11 keysym: %s", XKeysymToString(sym)); + +    display = pa_x11_wrapper_get_display(u->x11_wrapper); +    code = XKeysymToKeycode(display, sym); + +    XTestFakeKeyEvent(display, code, True, CurrentTime); +    XSync(display, False); + +    XTestFakeKeyEvent(display, code, False, CurrentTime); +    XSync(display, False); + +    return PA_HOOK_OK; +} + +int pa__init(pa_module *m) { +    struct userdata *u; +    pa_modargs *ma; +    int xtest_event_base, xtest_error_base; +    int major_version, minor_version; + +    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; + +    if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) +        goto fail; + +    if (!XTestQueryExtension( +                pa_x11_wrapper_get_display(u->x11_wrapper), +                &xtest_event_base, &xtest_error_base, +                &major_version, &minor_version)) { + +        pa_log("XTest extension not supported."); +        goto fail; +    } + +    pa_log_debug("XTest %i.%i supported.", major_version, minor_version); + +    u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u); + +    u->hook_slot = pa_hook_connect( +            &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT], +            PA_HOOK_NORMAL, +            (pa_hook_cb_t) sink_input_send_event_hook_cb, 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; + +    if (u->x11_client) +        pa_x11_client_free(u->x11_client); + +    if (u->x11_wrapper) +        pa_x11_wrapper_unref(u->x11_wrapper); + +    if (u->hook_slot) +        pa_hook_slot_free(u->hook_slot); + +    pa_xfree(u); +} diff --git a/src/modules/x11/module-x11-publish.c b/src/modules/x11/module-x11-publish.c new file mode 100644 index 00000000..2c7fdc12 --- /dev/null +++ b/src/modules/x11/module-x11-publish.c @@ -0,0 +1,245 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/x11wrap.h> +#include <pulsecore/core-util.h> +#include <pulsecore/native-common.h> +#include <pulsecore/auth-cookie.h> +#include <pulsecore/x11prop.h> +#include <pulsecore/strlist.h> +#include <pulsecore/shared.h> +#include <pulsecore/protocol-native.h> + +#include "module-x11-publish-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 credential publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( +        "display=<X11 display> " +        "sink=<Sink to publish> " +        "source=<Source to publish> " +        "cookie=<Cookie file to publish> "); + +static const char* const valid_modargs[] = { +    "display", +    "sink", +    "source", +    "cookie", +    NULL +}; + +struct userdata { +    pa_core *core; +    pa_module *module; +    pa_native_protocol *protocol; + +    char *id; +    pa_auth_cookie *auth_cookie; + +    pa_x11_wrapper *x11_wrapper; +    pa_x11_client *x11_client; + +    pa_hook_slot *hook_slot; +}; + +static void publish_servers(struct userdata *u, pa_strlist *l) { + +    if (l) { +        char *s; + +        l = pa_strlist_reverse(l); +        s = pa_strlist_tostring(l); +        l = pa_strlist_reverse(l); + +        pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s); +        pa_xfree(s); +    } else +        pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER"); +} + +static pa_hook_result_t servers_changed_cb(void *hook_data, void *call_data, void *slot_data) { +    pa_strlist *servers = call_data; +    struct userdata *u = slot_data; +    char t[256]; + +    pa_assert(u); + +    if (!pa_x11_get_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) { +        pa_log_warn("PulseAudio information vanished from X11!"); +        return PA_HOOK_OK; +    } + +    publish_servers(u, servers); +    return PA_HOOK_OK; +} + +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { +    struct userdata *u = userdata; + +    pa_assert(w); +    pa_assert(u); +    pa_assert(u->x11_wrapper == w); + +    if (u->x11_client) +        pa_x11_client_free(u->x11_client); + +    if (u->x11_wrapper) +        pa_x11_wrapper_unref(u->x11_wrapper); + +    u->x11_client = NULL; +    u->x11_wrapper = NULL; + +    pa_module_unload_request(u->module, TRUE); +} + +int pa__init(pa_module*m) { +    struct userdata *u; +    pa_modargs *ma = NULL; +    char *mid, *sid; +    char hx[PA_NATIVE_COOKIE_LENGTH*2+1]; +    const char *t; + +    pa_assert(m); + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("failed to parse module arguments"); +        goto fail; +    } + +    m->userdata = u = pa_xnew(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->protocol = pa_native_protocol_get(m->core); +    u->id = NULL; +    u->auth_cookie = NULL; +    u->x11_client = NULL; +    u->x11_wrapper = NULL; + +    u->hook_slot = pa_hook_connect(&pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_SERVERS_CHANGED], PA_HOOK_NORMAL, servers_changed_cb, u); + +    if (!(u->auth_cookie = pa_auth_cookie_get(m->core, pa_modargs_get_value(ma, "cookie", PA_NATIVE_COOKIE_FILE), PA_NATIVE_COOKIE_LENGTH))) +        goto fail; + +    if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) +        goto fail; + +    mid = pa_machine_id(); +    u->id = pa_sprintf_malloc("%lu@%s/%lu", (unsigned long) getuid(), mid, (unsigned long) getpid()); +    pa_xfree(mid); + +    pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", u->id); + +    if ((sid = pa_session_id())) { +        pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SESSION_ID", sid); +        pa_xfree(sid); +    } + +    publish_servers(u, pa_native_protocol_servers(u->protocol)); + +    if ((t = pa_modargs_get_value(ma, "source", NULL))) +        pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE", t); + +    if ((t = pa_modargs_get_value(ma, "sink", NULL))) +        pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK", t); + +    pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE", +                    pa_hexstr(pa_auth_cookie_read(u->auth_cookie, PA_NATIVE_COOKIE_LENGTH), PA_NATIVE_COOKIE_LENGTH, hx, sizeof(hx))); + +    u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, 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; + +    if (u->x11_client) +        pa_x11_client_free(u->x11_client); + +    if (u->x11_wrapper) { +        char t[256]; + +        /* Yes, here is a race condition */ +        if (!pa_x11_get_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) +            pa_log_warn("PulseAudio information vanished from X11!"); +        else { +            pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID"); +            pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER"); +            pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK"); +            pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE"); +            pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE"); +            pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SESSION_ID"); +            XSync(pa_x11_wrapper_get_display(u->x11_wrapper), False); +        } + +        pa_x11_wrapper_unref(u->x11_wrapper); +    } + +    if (u->auth_cookie) +        pa_auth_cookie_unref(u->auth_cookie); + +    if (u->hook_slot) +        pa_hook_slot_free(u->hook_slot); + +    if (u->protocol) +        pa_native_protocol_unref(u->protocol); + +    pa_xfree(u->id); +    pa_xfree(u); +} diff --git a/src/modules/x11/module-x11-xsmp.c b/src/modules/x11/module-x11-xsmp.c new file mode 100644 index 00000000..28fd373a --- /dev/null +++ b/src/modules/x11/module-x11-xsmp.c @@ -0,0 +1,254 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <X11/Xlib.h> +#include <X11/SM/SMlib.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/x11wrap.h> + +#include "module-x11-xsmp-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 session management"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("session_manager=<session manager string> display=<X11 display>"); + +static pa_bool_t ice_in_use = FALSE; + +static const char* const valid_modargs[] = { +    "session_manager", +    "display", +    NULL +}; + +struct userdata { +    pa_core *core; +    pa_module *module; +    pa_client *client; +    SmcConn connection; +    pa_x11_wrapper *x11; +}; + +static void die_cb(SmcConn connection, SmPointer client_data){ +    struct userdata *u = client_data; +    pa_assert(u); + +    pa_log_debug("Got die message from XSMP."); + +    pa_x11_wrapper_kill(u->x11); + +    pa_x11_wrapper_unref(u->x11); +    u->x11 = NULL; + +    pa_module_unload_request(u->module, TRUE); +} + +static void save_complete_cb(SmcConn connection, SmPointer client_data) { +} + +static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) { +    SmcSaveYourselfDone(connection, True); +} + +static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) { +    SmcSaveYourselfDone(connection, True); +} + +static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { +    IceConn connection = userdata; + +    if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) { +        IceSetShutdownNegotiation(connection, False); +        IceCloseConnection(connection); +    } +} + +static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) { +    struct pa_core *c = client_data; + +    if (opening) +        *watch_data = c->mainloop->io_new( +                c->mainloop, +                IceConnectionNumber(connection), +                PA_IO_EVENT_INPUT, +                ice_io_cb, +                connection); +    else +        c->mainloop->io_free(*watch_data); +} + +int pa__init(pa_module*m) { + +    pa_modargs *ma = NULL; +    char t[256], *vendor, *client_id; +    SmcCallbacks callbacks; +    SmProp prop_program, prop_user; +    SmProp *prop_list[2]; +    SmPropValue val_program, val_user; +    struct userdata *u; +    const char *e; +    pa_client_new_data data; + +    pa_assert(m); + +    if (ice_in_use) { +        pa_log("module-x11-xsmp may no be loaded twice."); +        return -1; +    } + +    IceAddConnectionWatch(new_ice_connection, m->core); +    ice_in_use = TRUE; + +    m->userdata = u = pa_xnew(struct userdata, 1); +    u->core = m->core; +    u->module = m; +    u->client = NULL; +    u->connection = NULL; +    u->x11 = NULL; + +    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { +        pa_log("Failed to parse module arguments"); +        goto fail; +    } + +    if (!(u->x11 = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) +        goto fail; + +    e = pa_modargs_get_value(ma, "session_manager", NULL); + +    if (!e && !getenv("SESSION_MANAGER")) { +        pa_log("X11 session manager not running."); +        goto fail; +    } + +    memset(&callbacks, 0, sizeof(callbacks)); +    callbacks.die.callback = die_cb; +    callbacks.die.client_data = u; +    callbacks.save_yourself.callback = save_yourself_cb; +    callbacks.save_yourself.client_data = m->core; +    callbacks.save_complete.callback = save_complete_cb; +    callbacks.save_complete.client_data = m->core; +    callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb; +    callbacks.shutdown_cancelled.client_data = m->core; + +    if (!(u->connection = SmcOpenConnection( +                  (char*) e, m->core, +                  SmProtoMajor, SmProtoMinor, +                  SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, +                  &callbacks, NULL, &client_id, +                  sizeof(t), t))) { + +        pa_log("Failed to open connection to session manager: %s", t); +        goto fail; +    } + +    prop_program.name = (char*) SmProgram; +    prop_program.type = (char*) SmARRAY8; +    val_program.value = (char*) PACKAGE_NAME; +    val_program.length = (int) strlen(val_program.value); +    prop_program.num_vals = 1; +    prop_program.vals = &val_program; +    prop_list[0] = &prop_program; + +    prop_user.name = (char*) SmUserID; +    prop_user.type = (char*) SmARRAY8; +    pa_get_user_name(t, sizeof(t)); +    val_user.value = t; +    val_user.length = (int) strlen(val_user.value); +    prop_user.num_vals = 1; +    prop_user.vals = &val_user; +    prop_list[1] = &prop_user; + +    SmcSetProperties(u->connection, PA_ELEMENTSOF(prop_list), prop_list); + +    pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(u->connection), client_id); + +    pa_client_new_data_init(&data); +    data.module = m; +    data.driver = __FILE__; +    pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "XSMP Session on %s as %s", vendor, client_id); +    pa_proplist_sets(data.proplist, "xsmp.vendor", vendor); +    pa_proplist_sets(data.proplist, "xsmp.client.id", client_id); +    u->client = pa_client_new(u->core, &data); +    pa_client_new_data_done(&data); + +    free(vendor); +    free(client_id); + +    if (!u->client) +        goto fail; + +    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)) { + +        if (u->connection) +            SmcCloseConnection(u->connection, 0, NULL); + +        if (u->client) +            pa_client_free(u->client); + +        if (u->x11) +            pa_x11_wrapper_unref(u->x11); + +        pa_xfree(u); +    } + +    if (ice_in_use) { +        IceRemoveConnectionWatch(new_ice_connection, m->core); +        ice_in_use = FALSE; +    } +} diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c index ce7dadc9..88823012 100644 --- a/src/pulse/channelmap.c +++ b/src/pulse/channelmap.c @@ -30,9 +30,11 @@  #include <pulse/xmalloc.h>  #include <pulse/i18n.h> +  #include <pulsecore/core-util.h>  #include <pulsecore/macro.h>  #include <pulsecore/bitset.h> +#include <pulsecore/sample-util.h>  #include "channelmap.h" @@ -491,6 +493,27 @@ char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) {      return s;  } +pa_channel_position_t pa_channel_position_from_string(const char *p) { +    pa_channel_position_t i; +    pa_assert(p); + +    /* Some special aliases */ +    if (pa_streq(p, "left")) +        return PA_CHANNEL_POSITION_LEFT; +    else if (pa_streq(p, "right")) +        return PA_CHANNEL_POSITION_RIGHT; +    else if (pa_streq(p, "center")) +        return PA_CHANNEL_POSITION_CENTER; +    else if (pa_streq(p, "subwoofer")) +        return PA_CHANNEL_POSITION_SUBWOOFER; + +    for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++) +        if (pa_streq(p, table[i])) +            return i; + +    return PA_CHANNEL_POSITION_INVALID; +} +  pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) {      const char *state;      pa_channel_map map; @@ -559,36 +582,19 @@ pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) {      map.channels = 0;      while ((p = pa_split(s, ",", &state))) { +        pa_channel_position_t f;          if (map.channels >= PA_CHANNELS_MAX) {              pa_xfree(p);              return NULL;          } -        /* Some special aliases */ -        if (pa_streq(p, "left")) -            map.map[map.channels++] = PA_CHANNEL_POSITION_LEFT; -        else if (pa_streq(p, "right")) -            map.map[map.channels++] = PA_CHANNEL_POSITION_RIGHT; -        else if (pa_streq(p, "center")) -            map.map[map.channels++] = PA_CHANNEL_POSITION_CENTER; -        else if (pa_streq(p, "subwoofer")) -            map.map[map.channels++] = PA_CHANNEL_POSITION_SUBWOOFER; -        else { -            pa_channel_position_t i; - -            for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++) -                if (strcmp(p, table[i]) == 0) { -                    map.map[map.channels++] = i; -                    break; -                } - -            if (i >= PA_CHANNEL_POSITION_MAX) { -                pa_xfree(p); -                return NULL; -            } +        if ((f = pa_channel_position_from_string(p)) == PA_CHANNEL_POSITION_INVALID) { +            pa_xfree(p); +            return NULL;          } +        map.map[map.channels++] = f;          pa_xfree(p);      } @@ -627,8 +633,7 @@ int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *s  }  int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { -    pa_bitset_t in_a[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)]; -    unsigned i; +    pa_channel_position_mask_t am, bm;      pa_assert(a);      pa_assert(b); @@ -636,98 +641,36 @@ int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) {      pa_return_val_if_fail(pa_channel_map_valid(a), 0);      pa_return_val_if_fail(pa_channel_map_valid(b), 0); -    memset(in_a, 0, sizeof(in_a)); +    am = pa_channel_map_mask(a); +    bm = pa_channel_map_mask(b); -    for (i = 0; i < a->channels; i++) -        pa_bitset_set(in_a, a->map[i], TRUE); - -    for (i = 0; i < b->channels; i++) -        if (!pa_bitset_get(in_a, b->map[i])) -            return 0; - -    return 1; +    return (bm & am) == bm;  }  int pa_channel_map_can_balance(const pa_channel_map *map) { -    unsigned c; -    pa_bool_t left = FALSE, right = FALSE; +    pa_channel_position_mask_t m;      pa_assert(map); -      pa_return_val_if_fail(pa_channel_map_valid(map), 0); -    for (c = 0; c < map->channels; c++) { - -        switch (map->map[c]) { -            case PA_CHANNEL_POSITION_LEFT: -            case PA_CHANNEL_POSITION_REAR_LEFT: -            case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: -            case PA_CHANNEL_POSITION_SIDE_LEFT: -            case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: -            case PA_CHANNEL_POSITION_TOP_REAR_LEFT: -                left = TRUE; -                break; - -            case PA_CHANNEL_POSITION_RIGHT: -            case PA_CHANNEL_POSITION_REAR_RIGHT: -            case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: -            case PA_CHANNEL_POSITION_SIDE_RIGHT: -            case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: -            case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: -                right = TRUE; -                break; - -            default: -                ; -        } - -        if (left && right) -            return 1; -    } +    m = pa_channel_map_mask(map); -    return 0; +    return +        (PA_CHANNEL_POSITION_MASK_LEFT & m) && +        (PA_CHANNEL_POSITION_MASK_RIGHT & m);  }  int pa_channel_map_can_fade(const pa_channel_map *map) { -    unsigned c; -    pa_bool_t front = FALSE, rear = FALSE; +    pa_channel_position_mask_t m;      pa_assert(map); -      pa_return_val_if_fail(pa_channel_map_valid(map), 0); -    for (c = 0; c < map->channels; c++) { - -        switch (map->map[c]) { -            case PA_CHANNEL_POSITION_FRONT_LEFT: -            case PA_CHANNEL_POSITION_FRONT_RIGHT: -            case PA_CHANNEL_POSITION_FRONT_CENTER: -            case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: -            case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: -            case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: -            case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: -            case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: -                front = TRUE; -                break; - -            case PA_CHANNEL_POSITION_REAR_LEFT: -            case PA_CHANNEL_POSITION_REAR_RIGHT: -            case PA_CHANNEL_POSITION_REAR_CENTER: -            case PA_CHANNEL_POSITION_TOP_REAR_LEFT: -            case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: -            case PA_CHANNEL_POSITION_TOP_REAR_CENTER: -                rear = TRUE; -                break; - -            default: -                ; -        } - -        if (front && rear) -            return 1; -    } +    m = pa_channel_map_mask(map); -    return 0; +    return +        (PA_CHANNEL_POSITION_MASK_FRONT & m) && +        (PA_CHANNEL_POSITION_MASK_REAR & m);  }  const char* pa_channel_map_to_name(const pa_channel_map *map) { @@ -839,3 +782,28 @@ const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) {      return NULL;  } + +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) { +    unsigned c; + +    pa_return_val_if_fail(pa_channel_map_valid(map), 0); +    pa_return_val_if_fail(p < PA_CHANNEL_POSITION_MAX, 0); + +    for (c = 0; c < map->channels; c++) +        if (map->map[c] == p) +            return 1; + +    return 0; +} + +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) { +    unsigned c; +    pa_channel_position_mask_t r = 0; + +    pa_return_val_if_fail(pa_channel_map_valid(map), 0); + +    for (c = 0; c < map->channels; c++) +        r |= PA_CHANNEL_POSITION_MASK(map->map[c]); + +    return r; +} diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h index eef0ac17..d7901ac2 100644 --- a/src/pulse/channelmap.h +++ b/src/pulse/channelmap.h @@ -50,7 +50,7 @@   * \li pa_channel_map_init_stereo() - Create a standard stereo mapping.   * \li pa_channel_map_init_auto() - Create a standard channel map for a specific number of channels   * \li pa_channel_map_init_extend() - Similar to - * pa_channel_map_init_auto() but synthesize a channel map if noone + * pa_channel_map_init_auto() but synthesize a channel map if no   * predefined one is known for the specified number of channels.   *   * \section conv_sec Convenience Functions @@ -74,26 +74,30 @@ typedef enum pa_channel_position {      PA_CHANNEL_POSITION_INVALID = -1,      PA_CHANNEL_POSITION_MONO = 0, -    PA_CHANNEL_POSITION_LEFT, -    PA_CHANNEL_POSITION_RIGHT, -    PA_CHANNEL_POSITION_CENTER, +    PA_CHANNEL_POSITION_FRONT_LEFT,               /* Apple calls this 'Left' */ +    PA_CHANNEL_POSITION_FRONT_RIGHT,              /* Apple calls this 'Right' */ +    PA_CHANNEL_POSITION_FRONT_CENTER,             /* Apple calls this 'Center' */ -    PA_CHANNEL_POSITION_FRONT_LEFT = PA_CHANNEL_POSITION_LEFT, -    PA_CHANNEL_POSITION_FRONT_RIGHT = PA_CHANNEL_POSITION_RIGHT, -    PA_CHANNEL_POSITION_FRONT_CENTER = PA_CHANNEL_POSITION_CENTER, +/** \cond fulldocs */ +    PA_CHANNEL_POSITION_LEFT = PA_CHANNEL_POSITION_FRONT_LEFT, +    PA_CHANNEL_POSITION_RIGHT = PA_CHANNEL_POSITION_FRONT_RIGHT, +    PA_CHANNEL_POSITION_CENTER = PA_CHANNEL_POSITION_FRONT_CENTER, +/** \endcond */ -    PA_CHANNEL_POSITION_REAR_CENTER, -    PA_CHANNEL_POSITION_REAR_LEFT, -    PA_CHANNEL_POSITION_REAR_RIGHT, +    PA_CHANNEL_POSITION_REAR_CENTER,              /* Microsoft calls this 'Back Center', Apple calls this 'Center Surround' */ +    PA_CHANNEL_POSITION_REAR_LEFT,                /* Microsoft calls this 'Back Left', Apple calls this 'Left Surround' */ +    PA_CHANNEL_POSITION_REAR_RIGHT,               /* Microsoft calls this 'Back Right', Apple calls this 'Right Surround' */ -    PA_CHANNEL_POSITION_LFE, +    PA_CHANNEL_POSITION_LFE,                      /* Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */ +/** \cond fulldocs */      PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE, +/** \endcond */ -    PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, -    PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, +    PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,     /* Apple calls this 'Left Center' */ +    PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,    /* Apple calls this 'Right Center */ -    PA_CHANNEL_POSITION_SIDE_LEFT, -    PA_CHANNEL_POSITION_SIDE_RIGHT, +    PA_CHANNEL_POSITION_SIDE_LEFT,                /* Apple calls this 'Left Surround Direct' */ +    PA_CHANNEL_POSITION_SIDE_RIGHT,               /* Apple calls this 'Right Surround Direct' */      PA_CHANNEL_POSITION_AUX0,      PA_CHANNEL_POSITION_AUX1, @@ -128,15 +132,15 @@ typedef enum pa_channel_position {      PA_CHANNEL_POSITION_AUX30,      PA_CHANNEL_POSITION_AUX31, -    PA_CHANNEL_POSITION_TOP_CENTER, +    PA_CHANNEL_POSITION_TOP_CENTER,               /* Apple calls this 'Top Center Surround' */ -    PA_CHANNEL_POSITION_TOP_FRONT_LEFT, -    PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, -    PA_CHANNEL_POSITION_TOP_FRONT_CENTER, +    PA_CHANNEL_POSITION_TOP_FRONT_LEFT,           /* Apple calls this 'Vertical Height Left' */ +    PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,          /* Apple calls this 'Vertical Height Right' */ +    PA_CHANNEL_POSITION_TOP_FRONT_CENTER,         /* Apple calls this 'Vertical Height Center' */ -    PA_CHANNEL_POSITION_TOP_REAR_LEFT, -    PA_CHANNEL_POSITION_TOP_REAR_RIGHT, -    PA_CHANNEL_POSITION_TOP_REAR_CENTER, +    PA_CHANNEL_POSITION_TOP_REAR_LEFT,            /* Microsoft and Apple call this 'Top Back Left' */ +    PA_CHANNEL_POSITION_TOP_REAR_RIGHT,           /* Microsoft and Apple call this 'Top Back Right' */ +    PA_CHANNEL_POSITION_TOP_REAR_CENTER,          /* Microsoft and Apple call this 'Top Back Center' */      PA_CHANNEL_POSITION_MAX  } pa_channel_position_t; @@ -201,6 +205,12 @@ typedef enum pa_channel_position {  #define PA_CHANNEL_POSITION_MAX PA_CHANNEL_POSITION_MAX  /** \endcond */ +/** A mask of channel positions. \since 0.9.16 */ +typedef uint64_t pa_channel_position_mask_t; + +/** Makes a bit mask from a channel position. \since 0.9.16 */ +#define PA_CHANNEL_POSITION_MASK(f) ((pa_channel_position_mask_t) (1ULL << (f))) +  /** A list of channel mapping definitions for pa_channel_map_init_auto() */  typedef enum pa_channel_map_def {      PA_CHANNEL_MAP_AIFF, @@ -251,7 +261,7 @@ typedef struct pa_channel_map {   * pa_channel_map_valid() will fail for it. */  pa_channel_map* pa_channel_map_init(pa_channel_map *m); -/** Initialize the specified channel map for monoaural audio and return a pointer to it */ +/** Initialize the specified channel map for monaural audio and return a pointer to it */  pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m);  /** Initialize the specified channel map for stereophonic audio and return a pointer to it */ @@ -272,6 +282,9 @@ 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 */ +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 */  const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos); @@ -282,7 +295,7 @@ const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos);   * it might become part of an ABI. */  #define PA_CHANNEL_MAP_SNPRINT_MAX 336 -/** Make a humand readable string from the specified channel map */ +/** Make a human readable string from the specified channel map */  char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map);  /** Parse a channel position list or well-known mapping name into a @@ -325,6 +338,13 @@ mapping. I.e. "Stereo", "Surround 7.1" and so on. If the channel  mapping is unknown NULL will be returned. \since 0.9.15 */  const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) PA_GCC_PURE; +/** Returns TRUE if the specified channel position is available at + * least once in the channel map. \since 0.9.16 */ +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) PA_GCC_PURE; + +/** Generates a bit mask from a channel map. \since 0.9.16 */ +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) PA_GCC_PURE; +  PA_C_DECL_END  #endif diff --git a/src/pulse/client-conf-x11.c b/src/pulse/client-conf-x11.c index 3bec742f..4970363b 100644 --- a/src/pulse/client-conf-x11.c +++ b/src/pulse/client-conf-x11.c @@ -57,8 +57,23 @@ int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) {      }      if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) { +        pa_bool_t disable_autospawn = TRUE; +          pa_xfree(c->default_server);          c->default_server = pa_xstrdup(t); + +        if (pa_x11_get_prop(d, "PULSE_SESSION_ID", t, sizeof(t))) { +            char *id; + +            if ((id = pa_session_id())) { +                if (pa_streq(t, id)) +                    disable_autospawn = FALSE; +                pa_xfree(id); +            } +        } + +        if (disable_autospawn) +            c->autospawn = FALSE;      }      if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t))) { diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index 58bc3f90..940d0b67 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -92,28 +92,18 @@ 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,  NULL, NULL }, -        { "extra-arguments",        pa_config_parse_string,  NULL, NULL }, -        { "default-sink",           pa_config_parse_string,  NULL, NULL }, -        { "default-source",         pa_config_parse_string,  NULL, NULL }, -        { "default-server",         pa_config_parse_string,  NULL, NULL }, -        { "autospawn",              pa_config_parse_bool,    NULL, NULL }, -        { "cookie-file",            pa_config_parse_string,  NULL, NULL }, -        { "disable-shm",            pa_config_parse_bool,    NULL, NULL }, -        { "shm-size-bytes",         pa_config_parse_size,    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 }, +        { "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 },      }; -    table[0].data = &c->daemon_binary; -    table[1].data = &c->extra_arguments; -    table[2].data = &c->default_sink; -    table[3].data = &c->default_source; -    table[4].data = &c->default_server; -    table[5].data = &c->autospawn; -    table[6].data = &c->cookie_file; -    table[7].data = &c->disable_shm; -    table[8].data = &c->shm_size; -      if (filename) {          if (!(f = fopen(filename, "r"))) { @@ -160,6 +150,9 @@ int pa_client_conf_env(pa_client_conf *c) {      if ((e = getenv(ENV_DEFAULT_SERVER))) {          pa_xfree(c->default_server);          c->default_server = pa_xstrdup(e); + +        /* We disable autospawning automatically if a specific server was set */ +        c->autospawn = FALSE;      }      if ((e = getenv(ENV_DAEMON_BINARY))) { diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h index 78844a12..ab97dc6a 100644 --- a/src/pulse/client-conf.h +++ b/src/pulse/client-conf.h @@ -38,7 +38,7 @@ typedef struct pa_client_conf {  pa_client_conf *pa_client_conf_new(void);  void pa_client_conf_free(pa_client_conf *c); -/* Load the configuration data from the speicified file, overwriting +/* Load the configuration data from the specified file, overwriting   * the current settings in *c. When the filename is NULL, the   * default client configuration file name is used. */  int pa_client_conf_load(pa_client_conf *c, const char *filename); diff --git a/src/pulse/context.c b/src/pulse/context.c index 00dffc25..4ded5565 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -54,6 +54,8 @@  #include <pulse/utf8.h>  #include <pulse/util.h>  #include <pulse/i18n.h> +#include <pulse/mainloop.h> +#include <pulse/timeval.h>  #include <pulsecore/winsock.h>  #include <pulsecore/core-error.h> @@ -64,6 +66,7 @@  #include <pulsecore/dynarray.h>  #include <pulsecore/socket-client.h>  #include <pulsecore/pstream-util.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/log.h>  #include <pulsecore/socket-util.h> @@ -99,10 +102,16 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_EXTENSION] = pa_command_extension,      [PA_COMMAND_PLAYBACK_STREAM_EVENT] = pa_command_stream_event,      [PA_COMMAND_RECORD_STREAM_EVENT] = pa_command_stream_event, -    [PA_COMMAND_CLIENT_EVENT] = pa_command_client_event +    [PA_COMMAND_CLIENT_EVENT] = pa_command_client_event, +    [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = pa_command_stream_buffer_attr, +    [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = pa_command_stream_buffer_attr  };  static void context_free(pa_context *c); +#ifdef HAVE_DBUS +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata); +#endif +  pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) {      return pa_context_new_with_proplist(mainloop, name, NULL);  } @@ -141,6 +150,9 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *      if (name)          pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name); +#ifdef HAVE_DBUS +    c->system_bus = c->session_bus = NULL; +#endif      c->mainloop = mainloop;      c->client = NULL;      c->pstream = NULL; @@ -148,6 +160,7 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *      c->playback_streams = pa_dynarray_new();      c->record_streams = pa_dynarray_new();      c->client_index = PA_INVALID_INDEX; +    c->use_rtclock = pa_mainloop_is_our_api(mainloop);      PA_LLIST_HEAD_INIT(pa_stream, c->streams);      PA_LLIST_HEAD_INIT(pa_operation, c->operations); @@ -165,6 +178,8 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *      c->do_shm = FALSE; +    c->server_specified = FALSE; +    c->no_fail = FALSE;      c->do_autospawn = FALSE;      memset(&c->spawn_api, 0, sizeof(c->spawn_api)); @@ -175,10 +190,10 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *  #endif      c->conf = pa_client_conf_new(); +    pa_client_conf_load(c->conf, NULL);  #ifdef HAVE_X11      pa_client_conf_from_x11(c->conf, NULL);  #endif -    pa_client_conf_load(c->conf, NULL);      pa_client_conf_env(c->conf);      if (!(c->mempool = pa_mempool_new(!c->conf->disable_shm, c->conf->shm_size))) { @@ -235,6 +250,18 @@ static void context_free(pa_context *c) {      context_unlink(c); +#ifdef HAVE_DBUS +    if (c->system_bus) { +        dbus_connection_remove_filter(pa_dbus_wrap_connection_get(c->system_bus), filter_cb, c); +        pa_dbus_wrap_connection_free(c->system_bus); +    } + +    if (c->session_bus) { +        dbus_connection_remove_filter(pa_dbus_wrap_connection_get(c->session_bus), filter_cb, c); +        pa_dbus_wrap_connection_free(c->session_bus); +    } +#endif +      if (c->record_streams)          pa_dynarray_free(c->record_streams, NULL, NULL);      if (c->playback_streams) @@ -348,10 +375,10 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o      if ((s = pa_dynarray_get(c->record_streams, channel))) {          if (chunk->memblock) { -            pa_memblockq_seek(s->record_memblockq, offset, seek); +            pa_memblockq_seek(s->record_memblockq, offset, seek, TRUE);              pa_memblockq_push_align(s->record_memblockq, chunk);          } else -            pa_memblockq_seek(s->record_memblockq, offset+chunk->length, seek); +            pa_memblockq_seek(s->record_memblockq, offset+chunk->length, seek, TRUE);          if (s->read_callback) {              size_t l; @@ -517,7 +544,7 @@ static void setup_context(pa_context *c, pa_iochannel *io) {      pa_pstream_set_recieve_memblock_callback(c->pstream, pstream_memblock_callback, c);      pa_assert(!c->pdispatch); -    c->pdispatch = pa_pdispatch_new(c->mainloop, command_table, PA_COMMAND_MAX); +    c->pdispatch = pa_pdispatch_new(c->mainloop, c->use_rtclock, command_table, PA_COMMAND_MAX);      if (!c->conf->cookie_valid)          pa_log_info(_("No cookie loaded. Attempting to connect without.")); @@ -726,6 +753,45 @@ fail:  static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata); +#ifdef HAVE_DBUS +static void track_pulseaudio_on_dbus(pa_context *c, DBusBusType type, pa_dbus_wrap_connection **conn) { +    DBusError error; + +    pa_assert(c); +    pa_assert(conn); + +    dbus_error_init(&error); + +    if (!(*conn = pa_dbus_wrap_connection_new(c->mainloop, c->use_rtclock, type, &error)) || dbus_error_is_set(&error)) { +        pa_log_warn("Unable to contact DBUS: %s: %s", error.name, error.message); +        goto fail; +    } + +    if (!dbus_connection_add_filter(pa_dbus_wrap_connection_get(*conn), filter_cb, c, NULL)) { +        pa_log_warn("Failed to add filter function"); +        goto fail; +    } + +    if (pa_dbus_add_matches( +                pa_dbus_wrap_connection_get(*conn), &error, +                "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',arg0='org.pulseaudio.Server',arg1=''", NULL) < 0) { + +        pa_log_warn("Unable to track org.pulseaudio.Server: %s: %s", error.name, error.message); +        goto fail; +    } + +    return; + +fail: +    if (*conn) { +        pa_dbus_wrap_connection_free(*conn); +        *conn = NULL; +    } + +    dbus_error_free(&error); +} +#endif +  static int try_next_connection(pa_context *c) {      char *u = NULL;      int r = -1; @@ -758,7 +824,16 @@ static int try_next_connection(pa_context *c) {              }  #endif -            pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); +#ifdef HAVE_DBUS +            if (c->no_fail && !c->server_specified) { +                if (!c->session_bus) +                    track_pulseaudio_on_dbus(c, DBUS_BUS_SESSION, &c->session_bus); +                if (!c->system_bus) +                    track_pulseaudio_on_dbus(c, DBUS_BUS_SYSTEM, &c->system_bus); +            } else +#endif +                pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); +              goto finish;          } @@ -767,7 +842,7 @@ static int try_next_connection(pa_context *c) {          pa_xfree(c->server);          c->server = pa_xstrdup(u); -        if (!(c->client = pa_socket_client_new_string(c->mainloop, u, PA_NATIVE_DEFAULT_PORT))) +        if (!(c->client = pa_socket_client_new_string(c->mainloop, c->use_rtclock, u, PA_NATIVE_DEFAULT_PORT)))              continue;          c->is_local = !!pa_socket_client_is_local(c->client); @@ -797,7 +872,7 @@ static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userd      c->client = NULL;      if (!io) { -        /* Try the item in the list */ +        /* Try the next item in the list */          if (saved_errno == ECONNREFUSED ||              saved_errno == ETIMEDOUT ||              saved_errno == EHOSTUNREACH) { @@ -815,6 +890,41 @@ finish:      pa_context_unref(c);  } +#ifdef HAVE_DBUS +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { +    pa_context *c = userdata; +    pa_bool_t is_session; + +    pa_assert(bus); +    pa_assert(message); +    pa_assert(c); + +    if (c->state != PA_CONTEXT_CONNECTING) +        goto finish; + +    if (!c->no_fail) +        goto finish; + +    /* FIXME: We probably should check if this is actually the NameOwnerChanged we were looking for */ + +    is_session = c->session_bus && bus == pa_dbus_wrap_connection_get(c->session_bus); +    pa_log_debug("Rock!! PulseAudio might be back on %s bus", is_session ? "session" : "system"); + +    if (is_session) +        /* The user instance via PF_LOCAL */ +        c->server_list = prepend_per_user(c->server_list); +    else +        /* The system wide instance via PF_LOCAL */ +        c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET); + +    if (!c->client) +        try_next_connection(c); + +finish: +    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} +#endif +  int pa_context_connect(          pa_context *c,          const char *server, @@ -828,14 +938,18 @@ int pa_context_connect(      PA_CHECK_VALIDITY(c, !pa_detect_fork(), PA_ERR_FORKED);      PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE); -    PA_CHECK_VALIDITY(c, !(flags & ~PA_CONTEXT_NOAUTOSPAWN), PA_ERR_INVALID); +    PA_CHECK_VALIDITY(c, !(flags & ~(PA_CONTEXT_NOAUTOSPAWN|PA_CONTEXT_NOFAIL)), PA_ERR_INVALID);      PA_CHECK_VALIDITY(c, !server || *server, PA_ERR_INVALID); -    if (!server) +    if (server) +        c->conf->autospawn = FALSE; +    else          server = c->conf->default_server;      pa_context_ref(c); +    c->no_fail = !!(flags & PA_CONTEXT_NOFAIL); +    c->server_specified = !!server;      pa_assert(!c->server_list);      if (server) { @@ -851,10 +965,7 @@ int pa_context_connect(          /* Follow the X display */          if ((d = getenv("DISPLAY"))) { -            char *e; -            d = pa_xstrdup(d); -            if ((e = strchr(d, ':'))) -                *e = 0; +            d = pa_xstrndup(d, strcspn(d, ":"));              if (*d)                  c->server_list = pa_strlist_prepend(c->server_list, d); @@ -871,18 +982,18 @@ int pa_context_connect(          /* The user instance via PF_LOCAL */          c->server_list = prepend_per_user(c->server_list); +    } -        /* Set up autospawning */ -        if (!(flags & PA_CONTEXT_NOAUTOSPAWN) && c->conf->autospawn) { +    /* Set up autospawning */ +    if (!(flags & PA_CONTEXT_NOAUTOSPAWN) && c->conf->autospawn) { -            if (getuid() == 0) -                pa_log_debug("Not doing autospawn since we are root."); -            else { -                c->do_autospawn = TRUE; +        if (getuid() == 0) +            pa_log_debug("Not doing autospawn since we are root."); +        else { +            c->do_autospawn = TRUE; -                if (api) -                    c->spawn_api = *api; -            } +            if (api) +                c->spawn_api = *api;          }      } @@ -1344,3 +1455,31 @@ finish:      if (pl)          pa_proplist_free(pl);  } + +pa_time_event* pa_context_rttime_new(pa_context *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) { +    struct timeval tv; + +    pa_assert(c); +    pa_assert(c->mainloop); + +    if (usec == PA_USEC_INVALID) +        return c->mainloop->time_new(c->mainloop, NULL, cb, userdata); + +    pa_timeval_rtstore(&tv, usec, c->use_rtclock); + +    return c->mainloop->time_new(c->mainloop, &tv, cb, userdata); +} + +void pa_context_rttime_restart(pa_context *c, pa_time_event *e, pa_usec_t usec) { +    struct timeval tv; + +    pa_assert(c); +    pa_assert(c->mainloop); + +    if (usec == PA_USEC_INVALID) +        c->mainloop->time_restart(e, NULL); +    else { +        pa_timeval_rtstore(&tv, usec, c->use_rtclock); +        c->mainloop->time_restart(e, &tv); +    } +} diff --git a/src/pulse/context.h b/src/pulse/context.h index c32cf443..cd129313 100644 --- a/src/pulse/context.h +++ b/src/pulse/context.h @@ -207,9 +207,10 @@ pa_context_state_t pa_context_get_state(pa_context *c);  connect to the default server. This routine may but will not always  return synchronously on error. Use pa_context_set_state_callback() to  be notified when the connection is established. If flags doesn't have -PA_NOAUTOSPAWN set and no specific server is specified or accessible a -new daemon is spawned. If api is non-NULL, the functions specified in -the structure are used when forking a new child process. */ +PA_CONTEXT_NOAUTOSPAWN set and no specific server is specified or +accessible a new daemon is spawned. If api is non-NULL, the functions +specified in the structure are used when forking a new child +process. */  int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);  /** Terminate the context connection immediately */ @@ -259,6 +260,14 @@ pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[]   * introspection functions, such as pa_context_get_client_info(). \since 0.9.11 */  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 */ +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 */ +void pa_context_rttime_restart(pa_context *c, pa_time_event *e, pa_usec_t usec); + +  PA_C_DECL_END  #endif diff --git a/src/pulse/def.h b/src/pulse/def.h index 3629aabc..08399ca8 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -71,7 +71,7 @@ typedef enum pa_stream_state {      PA_STREAM_UNCONNECTED,  /**< The stream is not yet connected to any sink or source */      PA_STREAM_CREATING,     /**< The stream is being created */      PA_STREAM_READY,        /**< The stream is established, you may pass audio data to it now */ -    PA_STREAM_FAILED,       /**< An error occured that made the stream invalid */ +    PA_STREAM_FAILED,       /**< An error occurred that made the stream invalid */      PA_STREAM_TERMINATED    /**< The stream has been terminated cleanly */  } pa_stream_state_t; @@ -109,13 +109,16 @@ typedef enum pa_operation_state {  /** Some special flags for contexts. */  typedef enum pa_context_flags { -    PA_CONTEXT_NOAUTOSPAWN = 1 +    PA_CONTEXT_NOAUTOSPAWN = 0x0001U,      /**< Disabled autospawning of the PulseAudio daemon if required */ +    PA_CONTEXT_NOFAIL = 0x0002U +    /**< Don't fail if the daemon is not available when pa_context_connect() is called, instead enter PA_CONTEXT_CONNECTING state and wait for the daemon to appear.  \since 0.9.15 */  } pa_context_flags_t;  /** \cond fulldocs */  /* Allow clients to check with #ifdef for those flags */  #define PA_CONTEXT_NOAUTOSPAWN PA_CONTEXT_NOAUTOSPAWN +#define PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL  /** \endcond */  /** The direction of a pa_stream object */ @@ -213,7 +216,7 @@ typedef enum pa_stream_flags {       * sink/device. Useful if you use any of the PA_STREAM_FIX_ flags       * and want to make sure that resampling never takes place --       * which might happen if the stream is moved to another -     * sink/source whith a different sample spec/channel map. Only +     * sink/source with a different sample spec/channel map. Only       * supported when the server is at least PA 0.9.8. It is ignored       * on older servers. \since 0.9.8 */ @@ -247,7 +250,7 @@ typedef enum pa_stream_flags {       * asking for less new data than this value will be made to the       * client it will also guarantee that requests are generated as       * early as this limit is reached. This flag should only be set in -     * very few situations where compatiblity with a fragment-based +     * very few situations where compatibility with a fragment-based       * playback model needs to be kept and the client applications       * cannot deal with data requests that are delayed to the latest       * moment possible. (Usually these are programs that use usleep() @@ -323,12 +326,12 @@ typedef struct pa_buffer_attr {       * plus the playback buffer size is configured to this value. Set       * PA_STREAM_ADJUST_LATENCY if you are interested in adjusting the       * overall latency. Don't set it if you are interested in -     * configuring the server-sider per-stream playback buffer +     * configuring the server-side per-stream playback buffer       * size. */      uint32_t prebuf;      /**< Playback only: pre-buffering. The server does not start with -     * playback before at least prebug bytes are available in the +     * playback before at least prebuf bytes are available in the       * buffer. It is recommended to set this to (uint32_t) -1, which       * will initialize this to the same value as tlength, whatever       * that may be. Initialize to 0 to enable manual start/stop @@ -349,7 +352,7 @@ typedef struct pa_buffer_attr {      uint32_t fragsize;      /**< Recording only: fragment size. The server sends data in -     * blocks of fragsize bytes size. Large values deminish +     * blocks of fragsize bytes size. Large values diminish       * interactivity with other operations on the connection context       * but decrease control overhead. It is recommended to set this to       * (uint32_t) -1, which will initialize this to a value that is @@ -389,7 +392,8 @@ enum {      PA_ERR_NOEXTENSION,            /**< Extension does not exist. \since 0.9.12 */      PA_ERR_OBSOLETE,               /**< Obsolete functionality. \since 0.9.15 */      PA_ERR_NOTIMPLEMENTED,         /**< Missing implementation. \since 0.9.15 */ -    PA_ERR_FORKED,                 /**< The caler forked without calling execve() and tried to reuse the context. \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_MAX                     /**< Not really an error but the first invalid error code */  }; @@ -487,7 +491,7 @@ typedef enum pa_subscription_event_type {      /**< Event type: Sample cache item */      PA_SUBSCRIPTION_EVENT_SERVER = 0x0007U, -    /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. */ +    /**< Event type: Global server change, only occurring with PA_SUBSCRIPTION_EVENT_CHANGE. */  /** \cond fulldocs */      PA_SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U, @@ -573,7 +577,7 @@ typedef struct pa_timing_info {      /**< Non-zero if the local and the remote machine have       * synchronized clocks. If synchronized clocks are detected       * transport_usec becomes much more reliable. However, the code -     * that detects synchronized clocks is very limited und unreliable +     * that detects synchronized clocks is very limited and unreliable       * itself. */      pa_usec_t sink_usec; @@ -624,7 +628,7 @@ typedef struct pa_timing_info {      /**< The configured latency for the sink. \since 0.9.11 */      pa_usec_t configured_source_usec; -    /**< The configured latency for * the source. \since 0.9.11 */ +    /**< The configured latency for the source. \since 0.9.11 */      int64_t since_underrun;      /**< Bytes that were handed to the sink since the last underrun @@ -702,9 +706,13 @@ typedef enum pa_sink_flags {      /**< Volume can be translated to dB with pa_sw_volume_to_dB()       * \since 0.9.11 */ -    PA_SINK_FLAT_VOLUME = 0x0040U +    PA_SINK_FLAT_VOLUME = 0x0040U,      /**< This sink is in flat volume mode, i.e. always the maximum of       * the volume of all connected inputs. \since 0.9.15 */ + +    PA_SINK_DYNAMIC_LATENCY = 0x0080U +    /**< The latency can be adjusted dynamically depending on the +     * needs of the connected streams. \since 0.9.15 */  } pa_sink_flags_t;  /** \cond fulldocs */ @@ -715,6 +723,7 @@ typedef enum pa_sink_flags {  #define PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL  #define PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME  #define PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME +#define PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY  /** \endcond */  /** Sink state. \since 0.9.15 */ @@ -780,9 +789,13 @@ typedef enum pa_source_flags {      PA_SOURCE_HW_MUTE_CTRL = 0x0010U,      /**< Supports hardware mute control \since 0.9.11 */ -    PA_SOURCE_DECIBEL_VOLUME = 0x0020U +    PA_SOURCE_DECIBEL_VOLUME = 0x0020U,      /**< Volume can be translated to dB with pa_sw_volume_to_dB()       * \since 0.9.11 */ + +    PA_SOURCE_DYNAMIC_LATENCY = 0x0040U +    /**< The latency can be adjusted dynamically depending on the +     * needs of the connected streams. \since 0.9.15 */  } pa_source_flags_t;  /** \cond fulldocs */ @@ -792,6 +805,7 @@ typedef enum pa_source_flags {  #define PA_SOURCE_NETWORK PA_SOURCE_NETWORK  #define PA_SOURCE_HW_MUTE_CTRL PA_SOURCE_HW_MUTE_CTRL  #define PA_SOURCE_DECIBEL_VOLUME PA_SOURCE_DECIBEL_VOLUME +#define PA_SOURCE_DYNAMIC_LATENCY PA_SOURCE_DYNAMIC_LATENCY  /** \endcond */  /** Source state. \since 0.9.15 */ diff --git a/src/pulse/fork-detect.c b/src/pulse/fork-detect.c index f10fc029..a4e0dd16 100644 --- a/src/pulse/fork-detect.c +++ b/src/pulse/fork-detect.c @@ -39,7 +39,7 @@ int pa_detect_fork(void) {       * however have to deal with this cleanly, so we try to detect the       * forks making sure all our calls fail cleanly after the fork. */ -    pa_assert(sizeof(pa_atomic_t) >= sizeof(pid_t)); +    pa_assert_cc(sizeof(pa_atomic_t) >= sizeof(pid_t));      for (;;) {          pid_t stored_pid = (pid_t) pa_atomic_load(&pid); diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 9646d8a6..e069c9e9 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -42,6 +42,9 @@  #include <pulsecore/hashmap.h>  #include <pulsecore/refcnt.h>  #include <pulsecore/time-smoother.h> +#ifdef HAVE_DBUS +#include <pulsecore/dbus-util.h> +#endif  #include "client-conf.h" @@ -50,6 +53,11 @@  struct pa_context {      PA_REFCNT_DECLARE; +#ifdef HAVE_DBUS +    pa_dbus_wrap_connection *system_bus; +    pa_dbus_wrap_connection *session_bus; +#endif +      pa_proplist *proplist;      pa_mainloop_api* mainloop; @@ -78,8 +86,10 @@ struct pa_context {      pa_bool_t is_local:1;      pa_bool_t do_shm:1; - +    pa_bool_t server_specified:1; +    pa_bool_t no_fail:1;      pa_bool_t do_autospawn:1; +    pa_bool_t use_rtclock:1;      pa_spawn_api spawn_api;      pa_strlist *server_list; @@ -135,12 +145,17 @@ struct pa_stream {      uint32_t syncid;      uint32_t stream_index; -    uint32_t requested_bytes; +    int64_t requested_bytes;      pa_buffer_attr buffer_attr;      uint32_t device_index;      char *device_name; +    /* playback */ +    pa_memblock *write_memblock; +    void *write_data; + +    /* recording */      pa_memchunk peek_memchunk;      void *peek_data;      pa_memblockq *record_memblockq; @@ -155,12 +170,13 @@ struct pa_stream {      uint32_t write_index_not_before;      uint32_t read_index_not_before; -    /* Data about individual timing update correctoins */ +    /* Data about individual timing update corrections */      pa_index_correction write_index_corrections[PA_MAX_WRITE_INDEX_CORRECTIONS];      int current_write_index_correction;      /* Latency interpolation stuff */      pa_time_event *auto_timing_update_event; +    pa_usec_t auto_timing_interval_usec;      pa_smoother *smoother; @@ -185,6 +201,8 @@ struct pa_stream {      void *started_userdata;      pa_stream_event_cb_t event_callback;      void *event_userdata; +    pa_stream_notify_cb_t buffer_attr_callback; +    void *buffer_attr_userdata;  };  typedef void (*pa_operation_cb_t)(void); @@ -213,6 +231,7 @@ void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, p  void pa_command_stream_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  void pa_command_stream_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  void pa_command_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t callback, void *userdata);  void pa_operation_done(pa_operation *o); @@ -266,4 +285,6 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta  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); +  #endif diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index ac8a11aa..3414f7de 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -49,7 +49,7 @@ static void context_stat_callback(pa_pdispatch *pd, uint32_t command, uint32_t t      pa_assert(o);      pa_assert(PA_REFCNT_VALUE(o) >= 1); -    memset(&i, 0, sizeof(i)); +    pa_zero(i);      if (!o->context)          goto finish; @@ -93,7 +93,7 @@ static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command,      pa_assert(o);      pa_assert(PA_REFCNT_VALUE(o) >= 1); -    memset(&i, 0, sizeof(i)); +    pa_zero(i);      if (!o->context)          goto finish; @@ -161,8 +161,10 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u              pa_bool_t mute;              uint32_t flags;              uint32_t state; +            uint32_t j; +            const char *ap = NULL; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              i.base_volume = PA_VOLUME_NORM;              i.n_volume_steps = PA_VOLUME_NORM+1; @@ -190,13 +192,55 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u                   (pa_tagstruct_get_volume(t, &i.base_volume) < 0 ||                    pa_tagstruct_getu32(t, &state) < 0 ||                    pa_tagstruct_getu32(t, &i.n_volume_steps) < 0 || -                  pa_tagstruct_getu32(t, &i.card) < 0))) { +                  pa_tagstruct_getu32(t, &i.card) < 0)) || +                (o->context->version >= 16 && +                 (pa_tagstruct_getu32(t, &i.n_ports)))) {                  pa_context_fail(o->context, PA_ERR_PROTOCOL);                  pa_proplist_free(i.proplist);                  goto finish;              } +            if (o->context->version >= 16) { +                if (i.n_ports > 0) { +                    i.ports = pa_xnew(pa_sink_port_info*, i.n_ports+1); +                    i.ports[0] = pa_xnew(pa_sink_port_info, i.n_ports); + +                    for (j = 0; j < i.n_ports; j++) { +                        if (pa_tagstruct_gets(t, &i.ports[0][j].name) < 0 || +                            pa_tagstruct_gets(t, &i.ports[0][j].description) < 0 || +                            pa_tagstruct_getu32(t, &i.ports[0][j].priority) < 0) { + +                            pa_context_fail(o->context, PA_ERR_PROTOCOL); +                            pa_xfree(i.ports); +                            pa_xfree(i.ports[0]); +                            pa_proplist_free(i.proplist); +                            goto finish; +                        } + +                        i.ports[j] = &i.ports[0][j]; +                    } + +                    i.ports[j] = NULL; +                } + +                if (pa_tagstruct_gets(t, &ap) < 0) { +                    pa_context_fail(o->context, PA_ERR_PROTOCOL); +                    pa_xfree(i.ports[0]); +                    pa_xfree(i.ports); +                    pa_proplist_free(i.proplist); +                    goto finish; +                } + +                if (ap) { +                    for (j = 0; j < i.n_ports; j++) +                        if (pa_streq(i.ports[j]->name, ap)) { +                            i.active_port = i.ports[j]; +                            break; +                        } +                } +            } +              i.mute = (int) mute;              i.flags = (pa_sink_flags_t) flags;              i.state = (pa_sink_state_t) state; @@ -271,6 +315,56 @@ pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name,      return o;  } +pa_operation* pa_context_set_sink_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata) { +    pa_operation *o; +    pa_tagstruct *t; +    uint32_t tag; + +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + +    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + +    t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_PORT, &tag); +    pa_tagstruct_putu32(t, idx); +    pa_tagstruct_puts(t, NULL); +    pa_tagstruct_puts(t, port); +    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_context_set_sink_port_by_name(pa_context *c, const char *name, const char*port, pa_context_success_cb_t cb, void *userdata) { +    pa_operation *o; +    pa_tagstruct *t; +    uint32_t tag; + +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + +    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + +    t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_PORT, &tag); +    pa_tagstruct_putu32(t, PA_INVALID_INDEX); +    pa_tagstruct_puts(t, name); +    pa_tagstruct_puts(t, port); +    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; +} +  /*** Source info ***/  static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { @@ -296,8 +390,10 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,              pa_bool_t mute;              uint32_t flags;              uint32_t state; +            unsigned j; +            const char *ap; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              i.base_volume = PA_VOLUME_NORM;              i.n_volume_steps = PA_VOLUME_NORM+1; @@ -325,13 +421,55 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,                   (pa_tagstruct_get_volume(t, &i.base_volume) < 0 ||                    pa_tagstruct_getu32(t, &state) < 0 ||                    pa_tagstruct_getu32(t, &i.n_volume_steps) < 0 || -                  pa_tagstruct_getu32(t, &i.card) < 0))) { +                  pa_tagstruct_getu32(t, &i.card) < 0)) || +                (o->context->version >= 16 && +                 (pa_tagstruct_getu32(t, &i.n_ports)))) {                  pa_context_fail(o->context, PA_ERR_PROTOCOL);                  pa_proplist_free(i.proplist);                  goto finish;              } +            if (o->context->version >= 16) { +                if (i.n_ports > 0) { +                    i.ports = pa_xnew(pa_source_port_info*, i.n_ports+1); +                    i.ports[0] = pa_xnew(pa_source_port_info, i.n_ports); + +                    for (j = 0; j < i.n_ports; j++) { +                        if (pa_tagstruct_gets(t, &i.ports[0][j].name) < 0 || +                            pa_tagstruct_gets(t, &i.ports[0][j].description) < 0 || +                            pa_tagstruct_getu32(t, &i.ports[0][j].priority) < 0) { + +                            pa_context_fail(o->context, PA_ERR_PROTOCOL); +                            pa_xfree(i.ports[0]); +                            pa_xfree(i.ports); +                            pa_proplist_free(i.proplist); +                            goto finish; +                        } + +                        i.ports[j] = &i.ports[0][j]; +                    } + +                    i.ports[j] = NULL; +                } + +                if (pa_tagstruct_gets(t, &ap) < 0) { +                    pa_context_fail(o->context, PA_ERR_PROTOCOL); +                    pa_xfree(i.ports[0]); +                    pa_xfree(i.ports); +                    pa_proplist_free(i.proplist); +                    goto finish; +                } + +                if (ap) { +                    for (j = 0; j < i.n_ports; j++) +                        if (pa_streq(i.ports[j]->name, ap)) { +                            i.active_port = i.ports[j]; +                            break; +                        } +                } +            } +              i.mute = (int) mute;              i.flags = (pa_source_flags_t) flags;              i.state = (pa_source_state_t) state; @@ -406,6 +544,56 @@ pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name      return o;  } +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) { +    pa_operation *o; +    pa_tagstruct *t; +    uint32_t tag; + +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + +    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + +    t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_PORT, &tag); +    pa_tagstruct_putu32(t, idx); +    pa_tagstruct_puts(t, NULL); +    pa_tagstruct_puts(t, port); +    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_context_set_source_port_by_name(pa_context *c, const char *name, const char*port, pa_context_success_cb_t cb, void *userdata) { +    pa_operation *o; +    pa_tagstruct *t; +    uint32_t tag; + +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + +    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + +    t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_PORT, &tag); +    pa_tagstruct_putu32(t, PA_INVALID_INDEX); +    pa_tagstruct_puts(t, name); +    pa_tagstruct_puts(t, port); +    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; +} +  /*** Client info ***/  static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { @@ -429,7 +617,7 @@ static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command,          while (!pa_tagstruct_eof(t)) {              pa_client_info i; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              if (pa_tagstruct_getu32(t, &i.index) < 0 || @@ -514,7 +702,7 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u              uint32_t j;              const char*ap; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              if (pa_tagstruct_getu32(t, &i.index) < 0 ||                  pa_tagstruct_gets(t, &i.name) < 0 || @@ -527,7 +715,7 @@ static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, u              }              if (i.n_profiles > 0) { -                i.profiles = pa_xnew(pa_card_profile_info, i.n_profiles+1); +                i.profiles = pa_xnew0(pa_card_profile_info, i.n_profiles+1);                  for (j = 0; j < i.n_profiles; j++) { @@ -715,7 +903,7 @@ static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command,              pa_module_info i;              pa_bool_t auto_unload = FALSE; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              if (pa_tagstruct_getu32(t, &i.index) < 0 || @@ -800,7 +988,7 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm              pa_sink_input_info i;              pa_bool_t mute = FALSE; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              if (pa_tagstruct_getu32(t, &i.index) < 0 || @@ -894,7 +1082,7 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c          while (!pa_tagstruct_eof(t)) {              pa_source_output_info i; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              if (pa_tagstruct_getu32(t, &i.index) < 0 || @@ -1236,7 +1424,7 @@ static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command,              pa_sample_info i;              pa_bool_t lazy = FALSE; -            memset(&i, 0, sizeof(i)); +            pa_zero(i);              i.proplist = pa_proplist_new();              if (pa_tagstruct_getu32(t, &i.index) < 0 || diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index ec8a2c06..ee982100 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -193,6 +193,15 @@ PA_C_DECL_BEGIN  /** @{ \name Sinks */ +/** Stores information about a specific port of a sink.  Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. \since 0.9.16 */ +typedef struct pa_sink_port_info { +    const char *name;                   /**< Name of this port */ +    const char *description;            /**< Description of this port */ +    uint32_t priority;                  /**< The higher this value is the more useful this port is as a default */ +} pa_sink_port_info; +  /** Stores information about sinks. Please note that this structure   * can be extended as part of evolutionary API updates at any time in   * any new release. */ @@ -216,6 +225,9 @@ typedef struct pa_sink_info {      pa_sink_state_t state;             /**< State \since 0.9.15 */      uint32_t n_volume_steps;           /**< Number of volume steps for sinks which do not support arbitrary volumes. \since 0.9.15 */      uint32_t card;                     /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ +    uint32_t n_ports;                  /**< Number of entries in port array \since 0.9.16 */ +    pa_sink_port_info** ports;         /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports \since 0.9.16 */ +    pa_sink_port_info* active_port;    /**< Pointer to active port in the array, or NULL \since 0.9.16 */  } pa_sink_info;  /** Callback prototype for pa_context_get_sink_info_by_name() and friends */ @@ -248,10 +260,25 @@ pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_na  /** Suspend/Resume a sink. If idx is PA_INVALID_INDEX all sinks will be suspended. \since 0.9.7 */  pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend,  pa_context_success_cb_t cb, void* userdata); +/** Change the profile of a sink. \since 0.9.16 */ +pa_operation* pa_context_set_sink_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata); + +/** Change the profile of a sink. \since 0.9.15 */ +pa_operation* pa_context_set_sink_port_by_name(pa_context *c, const char*name, const char*port, pa_context_success_cb_t cb, void *userdata); +  /** @} */  /** @{ \name Sources */ +/** Stores information about a specific port of a source.  Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. \since 0.9.16 */ +typedef struct pa_source_port_info { +    const char *name;                   /**< Name of this port */ +    const char *description;            /**< Description of this port */ +    uint32_t priority;                  /**< The higher this value is the more useful this port is as a default */ +} pa_source_port_info; +  /** Stores information about sources. Please note that this structure   * can be extended as part of evolutionary API updates at any time in   * any new release. */ @@ -275,6 +302,9 @@ typedef struct pa_source_info {      pa_source_state_t state;            /**< State \since 0.9.15 */      uint32_t n_volume_steps;            /**< Number of volume steps for sources which do not support arbitrary volumes. \since 0.9.15 */      uint32_t card;                      /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ +    uint32_t n_ports;                   /**< Number of entries in port array \since 0.9.16 */ +    pa_source_port_info** ports;        /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports \since 0.9.16  */ +    pa_source_port_info* active_port;   /**< Pointer to active port in the array, or NULL \since 0.9.16  */  } pa_source_info;  /** Callback prototype for pa_context_get_source_info_by_name() and friends */ @@ -301,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); +/** 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); + +/** Change the profile of a source. \since 0.9.15 */ +pa_operation* pa_context_set_source_port_by_name(pa_context *c, const char*name, const char*port, pa_context_success_cb_t cb, void *userdata); +  /** @} */  /** @{ \name Server */ @@ -344,7 +380,7 @@ typedef struct pa_module_info {      pa_proplist *proplist;              /**< Property list \since 0.9.15 */  } pa_module_info; -/** Callback prototype for pa_context_get_module_info() and firends*/ +/** Callback prototype for pa_context_get_module_info() and friends*/  typedef void (*pa_module_info_cb_t) (pa_context *c, const pa_module_info*i, int eol, void *userdata);  /** Get some information about a module by its index */ @@ -377,7 +413,7 @@ typedef struct pa_client_info {      pa_proplist *proplist;               /**< Property list \since 0.9.11 */  } pa_client_info; -/** Callback prototype for pa_context_get_client_info() and firends*/ +/** Callback prototype for pa_context_get_client_info() and friends*/  typedef void (*pa_client_info_cb_t) (pa_context *c, const pa_client_info*i, int eol, void *userdata);  /** Get information about a client by its index */ @@ -418,7 +454,7 @@ typedef struct pa_card_info {      pa_proplist *proplist;               /**< Property list */  } pa_card_info; -/** Callback prototype for pa_context_get_card_info() and firends \since 0.9.15 */ +/** Callback prototype for pa_context_get_card_info() and friends \since 0.9.15 */  typedef void (*pa_card_info_cb_t) (pa_context *c, const pa_card_info*i, int eol, void *userdata);  /** Get information about a card by its index \since 0.9.15 */ @@ -454,13 +490,13 @@ typedef struct pa_sink_input_info {      pa_cvolume volume;                   /**< The volume of this sink input */      pa_usec_t buffer_usec;               /**< Latency due to buffering in sink input, see pa_latency_info for details */      pa_usec_t sink_usec;                 /**< Latency of the sink device, see pa_latency_info for details */ -    const char *resample_method;         /**< Thre resampling method used by this sink input. */ +    const char *resample_method;         /**< The resampling method used by this sink input. */      const char *driver;                  /**< Driver name */      int mute;                            /**< Stream muted \since 0.9.7 */      pa_proplist *proplist;               /**< Property list \since 0.9.11 */  } pa_sink_input_info; -/** Callback prototype for pa_context_get_sink_input_info() and firends*/ +/** Callback prototype for pa_context_get_sink_input_info() and friends*/  typedef void (*pa_sink_input_info_cb_t) (pa_context *c, const pa_sink_input_info *i, int eol, void *userdata);  /** Get some information about a sink input by its index */ @@ -501,12 +537,12 @@ typedef struct pa_source_output_info {      pa_channel_map channel_map;          /**< Channel map */      pa_usec_t buffer_usec;               /**< Latency due to buffering in the source output, see pa_latency_info for details. */      pa_usec_t source_usec;               /**< Latency of the source device, see pa_latency_info for details. */ -    const char *resample_method;         /**< Thre resampling method used by this source output. */ +    const char *resample_method;         /**< The resampling method used by this source output. */      const char *driver;                  /**< Driver name */      pa_proplist *proplist;               /**< Property list \since 0.9.11 */  } pa_source_output_info; -/** Callback prototype for pa_context_get_source_output_info() and firends*/ +/** Callback prototype for pa_context_get_source_output_info() and friends*/  typedef void (*pa_source_output_info_cb_t) (pa_context *c, const pa_source_output_info *i, int eol, void *userdata);  /** Get information about a source output by its index */ @@ -539,7 +575,7 @@ pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_cont   * any new release. */  typedef struct pa_stat_info {      uint32_t memblock_total;           /**< Currently allocated memory blocks */ -    uint32_t memblock_total_size;      /**< Currentl total size of allocated memory blocks */ +    uint32_t memblock_total_size;      /**< Current total size of allocated memory blocks */      uint32_t memblock_allocated;       /**< Allocated memory blocks during the whole lifetime of the daemon */      uint32_t memblock_allocated_size;  /**< Total size of all memory blocks allocated during the whole lifetime of the daemon */      uint32_t scache_size;              /**< Total size of all sample cache entries. */ @@ -571,7 +607,7 @@ typedef struct pa_sample_info {      pa_proplist *proplist;                /**< Property list for this sample. \since 0.9.11 */  } pa_sample_info; -/** Callback prototype for pa_context_get_sample_info_by_name() and firends */ +/** Callback prototype for pa_context_get_sample_info_by_name() and friends */  typedef void (*pa_sample_info_cb_t)(pa_context *c, const pa_sample_info *i, int eol, void *userdata);  /** Get information about a sample by its name */ @@ -606,7 +642,7 @@ typedef struct pa_autoload_info {      const char *argument;         /**< Argument string for module */  } pa_autoload_info; -/** \deprecated Callback prototype for pa_context_get_autoload_info_by_name() and firends */ +/** \deprecated Callback prototype for pa_context_get_autoload_info_by_name() and friends */  typedef void (*pa_autoload_info_cb_t)(pa_context *c, const pa_autoload_info *i, int eol, void *userdata);  /** \deprecated Get info about a specific autoload entry. */ diff --git a/src/pulse/mainloop-api.h b/src/pulse/mainloop-api.h index e353ed96..aa0d5e73 100644 --- a/src/pulse/mainloop-api.h +++ b/src/pulse/mainloop-api.h @@ -27,6 +27,7 @@  #include <time.h>  #include <pulse/cdecl.h> +#include <pulse/sample.h>  #include <pulse/version.h>  /** \file diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c index 52f11c80..3dc74398 100644 --- a/src/pulse/mainloop-signal.c +++ b/src/pulse/mainloop-signal.c @@ -170,7 +170,7 @@ pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t _callback, void *userdata      for (e = signals; e; e = e->next)          if (e->sig == sig) -            goto fail; +            return NULL;      e = pa_xnew(pa_signal_event, 1);      e->sig = sig; @@ -196,8 +196,7 @@ pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t _callback, void *userdata      return e;  fail: -    if (e) -        pa_xfree(e); +    pa_xfree(e);      return NULL;  } diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c index 225fd098..c418d108 100644 --- a/src/pulse/mainloop.c +++ b/src/pulse/mainloop.c @@ -42,10 +42,12 @@  #include <pulsecore/pipe.h>  #endif +#include <pulse/i18n.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h> -#include <pulse/i18n.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/llist.h>  #include <pulsecore/log.h> @@ -54,6 +56,7 @@  #include <pulsecore/macro.h>  #include "mainloop.h" +#include "internal.h"  struct pa_io_event {      pa_mainloop *mainloop; @@ -75,7 +78,7 @@ struct pa_time_event {      pa_bool_t dead:1;      pa_bool_t enabled:1; -    struct timeval timeval; +    pa_usec_t time;      pa_time_event_cb_t callback;      void *userdata; @@ -317,6 +320,23 @@ 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; +    struct timeval ttv; + +    if (!tv) +        return PA_USEC_INVALID; + +    ttv = *tv; +    is_rtclock = !!(ttv.tv_usec & PA_TIMEVAL_RTCLOCK); +    ttv.tv_usec &= ~PA_TIMEVAL_RTCLOCK; + +    if (!is_rtclock) +        pa_rtclock_from_wallclock(&ttv); + +    return pa_timeval_load(&ttv); +} +  static pa_time_event* mainloop_time_new(          pa_mainloop_api*a,          const struct timeval *tv, @@ -325,11 +345,14 @@ static pa_time_event* mainloop_time_new(      pa_mainloop *m;      pa_time_event *e; +    pa_usec_t t;      pa_assert(a);      pa_assert(a->userdata);      pa_assert(callback); +    t = timeval_load(tv); +      m = a->userdata;      pa_assert(a == &m->api); @@ -337,15 +360,15 @@ static pa_time_event* mainloop_time_new(      e->mainloop = m;      e->dead = FALSE; -    if ((e->enabled = !!tv)) { -        e->timeval = *tv; +    if ((e->enabled = (t != PA_USEC_INVALID))) { +        e->time = t;          m->n_enabled_time_events++;          if (m->cached_next_time_event) {              pa_assert(m->cached_next_time_event->enabled); -            if (pa_timeval_cmp(tv, &m->cached_next_time_event->timeval) < 0) +            if (t < m->cached_next_time_event->time)                  m->cached_next_time_event = e;          }      } @@ -363,24 +386,30 @@ 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_assert(e);      pa_assert(!e->dead); -    if (e->enabled && !tv) { +    t = timeval_load(tv); + +    valid = (t != PA_USEC_INVALID); +    if (e->enabled && !valid) {          pa_assert(e->mainloop->n_enabled_time_events > 0);          e->mainloop->n_enabled_time_events--; -    } else if (!e->enabled && tv) +    } else if (!e->enabled && valid)          e->mainloop->n_enabled_time_events++; -    if ((e->enabled = !!tv)) { -        e->timeval = *tv; +    if ((e->enabled = valid)) { +        e->time = t;          pa_mainloop_wakeup(e->mainloop);      }      if (e->mainloop->cached_next_time_event && e->enabled) {          pa_assert(e->mainloop->cached_next_time_event->enabled); -        if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0) +        if (t < e->mainloop->cached_next_time_event->time)              e->mainloop->cached_next_time_event = e;      } else if (e->mainloop->cached_next_time_event == e)          e->mainloop->cached_next_time_event = NULL; @@ -428,10 +457,10 @@ static void mainloop_quit(pa_mainloop_api*a, int retval) {  static const pa_mainloop_api vtable = {      .userdata = NULL, -    .io_new= mainloop_io_new, -    .io_enable= mainloop_io_enable, -    .io_free= mainloop_io_free, -    .io_set_destroy= mainloop_io_set_destroy, +    .io_new = mainloop_io_new, +    .io_enable = mainloop_io_enable, +    .io_free = mainloop_io_free, +    .io_set_destroy = mainloop_io_set_destroy,      .time_new = mainloop_time_new,      .time_restart = mainloop_time_restart, @@ -721,11 +750,11 @@ static pa_time_event* find_next_time_event(pa_mainloop *m) {          if (t->dead || !t->enabled)              continue; -        if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) { +        if (!n || t->time < n->time) {              n = t; -            /* Shortcut for tv = { 0, 0 } */ -            if (n->timeval.tv_sec <= 0) +            /* Shortcut for time == 0 */ +            if (n->time == 0)                  break;          }      } @@ -736,7 +765,6 @@ static pa_time_event* find_next_time_event(pa_mainloop *m) {  static int calc_next_timeout(pa_mainloop *m) {      pa_time_event *t; -    struct timeval now;      pa_usec_t usec;      if (!m->n_enabled_time_events) @@ -745,41 +773,41 @@ static int calc_next_timeout(pa_mainloop *m) {      t = find_next_time_event(m);      pa_assert(t); -    if (t->timeval.tv_sec <= 0) +    if (t->time == 0)          return 0; -    pa_gettimeofday(&now); +    usec = t->time - pa_rtclock_now(); -    if (pa_timeval_cmp(&t->timeval, &now) <= 0) +    if (usec <= 0)          return 0; -    usec = pa_timeval_diff(&t->timeval, &now); -    return (int) (usec / 1000); +    return (int) (usec / 1000); /* in milliseconds */  }  static int dispatch_timeout(pa_mainloop *m) {      pa_time_event *e; -    struct timeval now; +    pa_usec_t now;      int r = 0;      pa_assert(m);      if (m->n_enabled_time_events <= 0)          return 0; -    pa_gettimeofday(&now); +    now = pa_rtclock_now();      for (e = m->time_events; e && !m->quit; e = e->next) {          if (e->dead || !e->enabled)              continue; -        if (pa_timeval_cmp(&e->timeval, &now) <= 0) { +        if (e->time <= now) { +            struct timeval tv;              pa_assert(e->callback);              /* Disable time event */              mainloop_time_restart(e, NULL); -            e->callback(&m->api, e, &e->timeval, e->userdata); +            e->callback(&m->api, e, pa_timeval_rtstore(&tv, e->time, TRUE), e->userdata);              r++;          } @@ -967,3 +995,9 @@ void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *use      m->poll_func = poll_func;      m->poll_func_userdata = userdata;  } + +pa_bool_t pa_mainloop_is_our_api(pa_mainloop_api*m) { +    pa_assert(m); + +    return m->io_new == mainloop_io_new; +} diff --git a/src/pulse/mainloop.h b/src/pulse/mainloop.h index 3a03ac9a..4a83ebe8 100644 --- a/src/pulse/mainloop.h +++ b/src/pulse/mainloop.h @@ -50,7 +50,7 @@ struct pollfd;   *   * -# Prepare - Build a list of file descriptors   *               that need to be monitored and calculate the next timeout. - * -# Poll - Execute the actuall poll() system call. + * -# Poll - Execute the actual poll() system call.   * -# Dispatch - Dispatch any events that have fired.   *   * When using the main loop, the application can either execute each diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c index db4c9344..c904f533 100644 --- a/src/pulse/proplist.c +++ b/src/pulse/proplist.c @@ -140,6 +140,21 @@ static int proplist_setn(pa_proplist *p, const char *key, size_t key_length, con      return 0;  } +/** Will accept only valid UTF-8 */ +int pa_proplist_setp(pa_proplist *p, const char *pair) { +    const char *t; + +    pa_assert(p); +    pa_assert(pair); + +    if (!(t = strchr(pair, '='))) +        return -1; + +    return proplist_setn(p, +                         pair, t - pair, +                         t + 1, strchr(pair, 0) - t - 1); +} +  static int proplist_sethex(pa_proplist *p, const char *key, size_t key_length, const char *value, size_t value_length) {      struct property *prop;      pa_bool_t add = FALSE; diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index d5f5bc04..bc4dbd8a 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -39,6 +39,12 @@ PA_C_DECL_BEGIN  /** For streams: localized media artist if applicable, formatted as UTF-8. e.g. "Guns'N'Roses" */  #define PA_PROP_MEDIA_ARTIST                   "media.artist" +/** For streams: localized media copyright string if applicable, formatted as UTF-8. e.g. "Evil Record Corp." */ +#define PA_PROP_MEDIA_COPYRIGHT                "media.copyright" + +/** For streams: localized media generator software string if applicable, formatted as UTF-8. e.g. "Foocrop AudioFrobnicator" */ +#define PA_PROP_MEDIA_SOFTWARE                 "media.software" +  /** For streams: media language if applicable, in standard POSIX format. e.g. "de_DE" */  #define PA_PROP_MEDIA_LANGUAGE                 "media.language" @@ -200,6 +206,9 @@ PA_C_DECL_BEGIN  /** For devices: profile identifier for the profile this devices is in. e.g. "analog-stereo", "analog-surround-40", "iec958-stereo", ...*/  #define PA_PROP_DEVICE_PROFILE_NAME            "device.profile.name" +/** For devices: intended use. A comma seperated list of roles (see PA_PROP_MEDIA_ROLE) this device is particularly well suited for, due to latency, quality or form factor. \since 0.9.16 */ +#define PA_PROP_DEVICE_INTENDED_ROLES          "device.intended_roles" +  /** For devices: human readable one-line description of the profile this device is in. e.g. "Analog Stereo", ... */  #define PA_PROP_DEVICE_PROFILE_DESCRIPTION     "device.profile.description" @@ -234,6 +243,14 @@ int pa_proplist_sets(pa_proplist *p, const char *key, const char *value);  /** Append a new string entry to the property list, possibly   * overwriting an already existing entry with the same key. An   * internal copy of the data passed is made. Will accept only valid + * UTF-8. The string passed in must contain a '='. Left hand side of + * the '=' is used as key name, the right hand side as string + * data. \since 0.9.16 */ +int pa_proplist_setp(pa_proplist *p, const char *pair); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid   * UTF-8. The data can be passed as printf()-style format string with   * arguments. \since 0.9.11 */  int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) PA_GCC_PRINTF_ATTR(3,4); @@ -259,7 +276,7 @@ int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *  /** Update mode enum for pa_proplist_update(). \since 0.9.11 */  typedef enum pa_update_mode {      PA_UPDATE_SET -    /**< Replace the entirey property list with the new one. Don't keep +    /**< Replace the entire property list with the new one. Don't keep       *  any of the old data around */,      PA_UPDATE_MERGE diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h index 5086783d..aa369e69 100644 --- a/src/pulse/pulseaudio.h +++ b/src/pulse/pulseaudio.h @@ -59,7 +59,7 @@   * \section intro_sec Introduction   *   * This document describes the client API for the PulseAudio sound - * server. The API comes in two flavours to accomodate different styles + * server. The API comes in two flavours to accommodate different styles   * of applications and different needs in complexity:   *   * \li The complete but somewhat complicated to use asynchronous API diff --git a/src/pulse/rtclock.c b/src/pulse/rtclock.c new file mode 100644 index 00000000..49ff6aae --- /dev/null +++ b/src/pulse/rtclock.c @@ -0,0 +1,35 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 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 +  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 <pulsecore/core-rtclock.h> + +#include "rtclock.h" +#include "timeval.h" + +pa_usec_t pa_rtclock_now(void) { +    struct timeval tv; + +    return pa_timeval_load(pa_rtclock_get(&tv)); +} diff --git a/src/pulse/rtclock.h b/src/pulse/rtclock.h new file mode 100644 index 00000000..6459d92d --- /dev/null +++ b/src/pulse/rtclock.h @@ -0,0 +1,41 @@ +#ifndef foortclockfoo +#define foortclockfoo + +/*** +  This file is part of PulseAudio. + +  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 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 <pulse/cdecl.h> +#include <pulse/def.h> +#include <pulse/gccmacro.h> + +/** \file + *  Monotonic clock utilities. */ + +PA_C_DECL_BEGIN + +/** Return the current monotonic system time in usec, if such a clock + * is available.  If it is not available this will return the + * wallclock time instead.  \since 0.9.16 */ +pa_usec_t pa_rtclock_now(void); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/sample.c b/src/pulse/sample.c index 1e67b037..0f19f8eb 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -231,13 +231,46 @@ pa_sample_format_t pa_parse_sample_format(const char *format) {      else if (strcasecmp(format, "s24re") == 0)          return PA_SAMPLE_S24RE;      else if (strcasecmp(format, "s24-32le") == 0) -        return PA_SAMPLE_S24LE; +        return PA_SAMPLE_S24_32LE;      else if (strcasecmp(format, "s24-32be") == 0) -        return PA_SAMPLE_S24BE; +        return PA_SAMPLE_S24_32BE;      else if (strcasecmp(format, "s24-32ne") == 0 || strcasecmp(format, "s24-32") == 0) -        return PA_SAMPLE_S24NE; +        return PA_SAMPLE_S24_32NE;      else if (strcasecmp(format, "s24-32re") == 0) -        return PA_SAMPLE_S24RE; +        return PA_SAMPLE_S24_32RE;      return -1;  } + +int pa_sample_format_is_le(pa_sample_format_t f) { +    pa_assert(f >= PA_SAMPLE_U8); +    pa_assert(f < PA_SAMPLE_MAX); + +    switch (f) { +        case PA_SAMPLE_S16LE: +        case PA_SAMPLE_S24LE: +        case PA_SAMPLE_S32LE: +        case PA_SAMPLE_S24_32LE: +        case PA_SAMPLE_FLOAT32LE: +            return 1; + +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_S32BE: +        case PA_SAMPLE_S24_32BE: +        case PA_SAMPLE_FLOAT32BE: +            return 0; + +        default: +            return -1; +    } +} + +int pa_sample_format_is_be(pa_sample_format_t f) { +    int r; + +    if ((r = pa_sample_format_is_le(f)) < 0) +        return r; + +    return !r; +} diff --git a/src/pulse/sample.h b/src/pulse/sample.h index aef34b6b..53d7dea3 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -71,7 +71,7 @@   *   * \section chan_sec Channels   * - * PulseAudio supports up to 32 individiual channels. The order of the + * PulseAudio supports up to 32 individual channels. The order of the   * channels is up to the application, but they must be continous. To map   * channels to speakers, see \ref channelmap.   * @@ -221,7 +221,7 @@ typedef enum pa_sample_format {  #define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32NE  /** \cond fulldocs */ -/* Allow clients to check with #ifdef for thse sample formats */ +/* Allow clients to check with #ifdef for these sample formats */  #define PA_SAMPLE_U8 PA_SAMPLE_U8  #define PA_SAMPLE_ALAW PA_SAMPLE_ALAW  #define PA_SAMPLE_ULAW PA_SAMPLE_ULAW @@ -305,6 +305,26 @@ char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec);  /** Pretty print a byte size value. (i.e. "2.5 MiB") */  char* pa_bytes_snprint(char *s, size_t l, unsigned v); +/** Return 1 when the specified format is little endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +int pa_sample_format_is_le(pa_sample_format_t f) PA_GCC_PURE; + +/** Return 1 when the specified format is big endian, return -1 when + * endianess does not apply to this format. \since 0.9.16 */ +int pa_sample_format_is_be(pa_sample_format_t f) PA_GCC_PURE; + +#ifdef WORDS_BIGENDIAN +#define pa_sample_format_is_ne(f) pa_sample_format_is_be(f) +#define pa_sample_format_is_re(f) pa_sample_format_is_le(f) +#else +/** Return 1 when the specified format is native endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +#define pa_sample_format_is_ne(f) pa_sample_format_is_le(f) +/** Return 1 when the specified format is reverse endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +#define pa_sample_format_is_re(f) pa_sample_format_is_be(f) +#endif +  PA_C_DECL_END  #endif diff --git a/src/pulse/simple.c b/src/pulse/simple.c index e70b7b1f..f4481fc3 100644 --- a/src/pulse/simple.c +++ b/src/pulse/simple.c @@ -50,35 +50,38 @@ struct pa_simple {      int operation_success;  }; -#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) do { \ -if (!(expression)) { \ -    if (rerror) \ -        *(rerror) = error; \ -    return (ret); \ -    }  \ -} while(0); - -#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) do { \ -if (!(expression)) { \ -    if (rerror) \ -        *(rerror) = pa_context_errno((p)->context); \ -    goto label; \ -    }  \ -} while(0); - -#define CHECK_DEAD_GOTO(p, rerror, label) do { \ -if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ -    !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ -        if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ -            ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ -            if (rerror) \ -                *(rerror) = pa_context_errno((p)->context); \ -        } else \ -            if (rerror) \ -                *(rerror) = PA_ERR_BADSTATE; \ -        goto label; \ -    } \ -} while(0); +#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret)       \ +    do {                                                                \ +        if (!(expression)) {                                            \ +            if (rerror)                                                 \ +                *(rerror) = error;                                      \ +            return (ret);                                               \ +        }                                                               \ +    } while(FALSE); + +#define CHECK_SUCCESS_GOTO(p, rerror, expression, label)        \ +    do {                                                        \ +        if (!(expression)) {                                    \ +            if (rerror)                                         \ +                *(rerror) = pa_context_errno((p)->context);     \ +            goto label;                                         \ +        }                                                       \ +    } while(FALSE); + +#define CHECK_DEAD_GOTO(p, rerror, label)                               \ +    do {                                                                \ +        if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ +            !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ +            if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ +                ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ +                if (rerror)                                             \ +                    *(rerror) = pa_context_errno((p)->context);         \ +            } else                                                      \ +                if (rerror)                                             \ +                    *(rerror) = PA_ERR_BADSTATE;                        \ +            goto label;                                                 \ +        }                                                               \ +    } while(FALSE);  static void context_state_cb(pa_context *c, void *userdata) {      pa_simple *p = userdata; @@ -198,9 +201,15 @@ pa_simple* pa_simple_new(      pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p);      if (dir == PA_STREAM_PLAYBACK) -        r = pa_stream_connect_playback(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); +        r = pa_stream_connect_playback(p->stream, dev, attr, +                                       PA_STREAM_INTERPOLATE_TIMING +                                       |PA_STREAM_ADJUST_LATENCY +                                       |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);      else -        r = pa_stream_connect_record(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE); +        r = pa_stream_connect_record(p->stream, dev, attr, +                                     PA_STREAM_INTERPOLATE_TIMING +                                     |PA_STREAM_ADJUST_LATENCY +                                     |PA_STREAM_AUTO_TIMING_UPDATE);      if (r < 0) {          error = pa_context_errno(p->context); diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 9a0ea0fd..5baf5c2c 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -30,18 +30,20 @@  #include <pulse/def.h>  #include <pulse/timeval.h> +#include <pulse/rtclock.h>  #include <pulse/xmalloc.h>  #include <pulsecore/pstream-util.h>  #include <pulsecore/log.h>  #include <pulsecore/hashmap.h>  #include <pulsecore/macro.h> -#include <pulsecore/rtclock.h> +#include <pulsecore/core-rtclock.h>  #include "fork-detect.h"  #include "internal.h" -#define LATENCY_IPOL_INTERVAL_USEC (333*PA_USEC_PER_MSEC) +#define AUTO_TIMING_INTERVAL_START_USEC (10*PA_USEC_PER_MSEC) +#define AUTO_TIMING_INTERVAL_END_USEC (1500*PA_USEC_PER_MSEC)  #define SMOOTHER_ADJUST_TIME (1000*PA_USEC_PER_MSEC)  #define SMOOTHER_HISTORY_TIME (5000*PA_USEC_PER_MSEC) @@ -72,6 +74,8 @@ static void reset_callbacks(pa_stream *s) {      s->started_userdata = NULL;      s->event_callback = NULL;      s->event_userdata = NULL; +    s->buffer_attr_callback = NULL; +    s->buffer_attr_userdata = NULL;  }  pa_stream *pa_stream_new_with_proplist( @@ -138,14 +142,15 @@ pa_stream *pa_stream_new_with_proplist(      s->device_index = PA_INVALID_INDEX;      s->device_name = NULL;      s->suspended = FALSE; +    s->corked = FALSE; + +    s->write_memblock = NULL; +    s->write_data = NULL;      pa_memchunk_reset(&s->peek_memchunk);      s->peek_data = NULL; -      s->record_memblockq = NULL; -    s->corked = FALSE; -      memset(&s->timing_info, 0, sizeof(s->timing_info));      s->timing_info_valid = FALSE; @@ -159,6 +164,7 @@ pa_stream *pa_stream_new_with_proplist(      s->auto_timing_update_event = NULL;      s->auto_timing_update_requested = FALSE; +    s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC;      reset_callbacks(s); @@ -216,6 +222,11 @@ static void stream_free(pa_stream *s) {      stream_unlink(s); +    if (s->write_memblock) { +        pa_memblock_release(s->write_memblock); +        pa_memblock_unref(s->write_data); +    } +      if (s->peek_memchunk.memblock) {          if (s->peek_data)              pa_memblock_release(s->peek_memchunk.memblock); @@ -306,7 +317,7 @@ static void request_auto_timing_update(pa_stream *s, pa_bool_t force) {          (force || !s->auto_timing_update_requested)) {          pa_operation *o; -/*         pa_log("automatically requesting new timing data"); */ +/*         pa_log("Automatically requesting new timing data"); */          if ((o = pa_stream_update_timing_info(s, NULL, NULL))) {              pa_operation_unref(o); @@ -315,10 +326,12 @@ static void request_auto_timing_update(pa_stream *s, pa_bool_t force) {      }      if (s->auto_timing_update_event) { -        struct timeval next; -        pa_gettimeofday(&next); -        pa_timeval_add(&next, LATENCY_IPOL_INTERVAL_USEC); -        s->mainloop->time_restart(s->auto_timing_update_event, &next); +        if (force) +            s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; + +        pa_context_rttime_restart(s->context, s->auto_timing_update_event, pa_rtclock_now() + s->auto_timing_interval_usec); + +        s->auto_timing_interval_usec = PA_MIN(AUTO_TIMING_INTERVAL_END_USEC, s->auto_timing_interval_usec*2);      }  } @@ -363,27 +376,20 @@ static void check_smoother_status(pa_stream *s, pa_bool_t aposteriori, pa_bool_t      if (!s->smoother)          return; -    x = pa_rtclock_usec(); +    x = pa_rtclock_now();      if (s->timing_info_valid) {          if (aposteriori)              x -= s->timing_info.transport_usec;          else              x += s->timing_info.transport_usec; - -        if (s->direction == PA_STREAM_PLAYBACK) -            /* it takes a while until the pause/resume is actually -             * audible */ -            x += s->timing_info.sink_usec; -        else -            /* Data froma  while back will be dropped */ -            x -= s->timing_info.source_usec;      }      if (s->suspended || s->corked || force_stop)          pa_smoother_pause(s->smoother, x);      else if (force_start || s->buffer_attr.prebuf == 0) -        pa_smoother_resume(s->smoother, x); +        pa_smoother_resume(s->smoother, x, TRUE); +      /* Please note that we have no idea if playback actually started       * if prebuf is non-zero! */ @@ -396,7 +402,7 @@ void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, p      const char *dn;      pa_bool_t suspended;      uint32_t di; -    pa_usec_t usec; +    pa_usec_t usec = 0;      uint32_t maxlength = 0, fragsize = 0, minreq = 0, tlength = 0, prebuf = 0;      pa_assert(pd); @@ -486,6 +492,80 @@ finish:      pa_context_unref(c);  } +void pa_command_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { +    pa_context *c = userdata; +    pa_stream *s; +    uint32_t channel; +    pa_usec_t usec = 0; +    uint32_t maxlength = 0, fragsize = 0, minreq = 0, tlength = 0, prebuf = 0; + +    pa_assert(pd); +    pa_assert(command == PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED || command == PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED); +    pa_assert(t); +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    pa_context_ref(c); + +    if (c->version < 15) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (pa_tagstruct_getu32(t, &channel) < 0) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (command == PA_COMMAND_RECORD_STREAM_MOVED) { +        if (pa_tagstruct_getu32(t, &maxlength) < 0 || +            pa_tagstruct_getu32(t, &fragsize) < 0 || +            pa_tagstruct_get_usec(t, &usec) < 0) { +            pa_context_fail(c, PA_ERR_PROTOCOL); +            goto finish; +        } +    } else { +        if (pa_tagstruct_getu32(t, &maxlength) < 0 || +            pa_tagstruct_getu32(t, &tlength) < 0 || +            pa_tagstruct_getu32(t, &prebuf) < 0 || +            pa_tagstruct_getu32(t, &minreq) < 0 || +            pa_tagstruct_get_usec(t, &usec) < 0) { +            pa_context_fail(c, PA_ERR_PROTOCOL); +            goto finish; +        } +    } + +    if (!pa_tagstruct_eof(t)) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED ? c->playback_streams : c->record_streams, channel))) +        goto finish; + +    if (s->state != PA_STREAM_READY) +        goto finish; + +    if (s->direction == PA_STREAM_RECORD) +        s->timing_info.configured_source_usec = usec; +    else +        s->timing_info.configured_sink_usec = usec; + +    s->buffer_attr.maxlength = maxlength; +    s->buffer_attr.fragsize = fragsize; +    s->buffer_attr.tlength = tlength; +    s->buffer_attr.prebuf = prebuf; +    s->buffer_attr.minreq = minreq; + +    request_auto_timing_update(s, TRUE); + +    if (s->buffer_attr_callback) +        s->buffer_attr_callback(s, s->buffer_attr_userdata); + +finish: +    pa_context_unref(c); +} +  void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_context *c = userdata;      pa_stream *s; @@ -645,7 +725,7 @@ void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tag      s->requested_bytes += bytes;      if (s->requested_bytes > 0 && s->write_callback) -        s->write_callback(s, s->requested_bytes, s->write_userdata); +        s->write_callback(s, (size_t) s->requested_bytes, s->write_userdata);  finish:      pa_context_unref(c); @@ -723,7 +803,7 @@ static void invalidate_indexes(pa_stream *s, pa_bool_t r, pa_bool_t w) {      request_auto_timing_update(s, TRUE);  } -static void auto_timing_update_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void auto_timing_update_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      pa_stream *s = userdata;      pa_assert(s); @@ -742,14 +822,12 @@ static void create_stream_complete(pa_stream *s) {      pa_stream_set_state(s, PA_STREAM_READY);      if (s->requested_bytes > 0 && s->write_callback) -        s->write_callback(s, s->requested_bytes, s->write_userdata); +        s->write_callback(s, (size_t) s->requested_bytes, s->write_userdata);      if (s->flags & PA_STREAM_AUTO_TIMING_UPDATE) { -        struct timeval tv; -        pa_gettimeofday(&tv); -        tv.tv_usec += (suseconds_t) LATENCY_IPOL_INTERVAL_USEC; /* every 100 ms */ +        s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC;          pa_assert(!s->auto_timing_update_event); -        s->auto_timing_update_event = s->mainloop->time_new(s->mainloop, &tv, &auto_timing_update_callback, s); +	s->auto_timing_update_event = pa_context_rttime_new(s->context, pa_rtclock_now() + s->auto_timing_interval_usec, &auto_timing_update_callback, s);          request_auto_timing_update(s, TRUE);      } @@ -789,6 +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;      pa_assert(pd);      pa_assert(s); @@ -808,11 +887,13 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag,      if (pa_tagstruct_getu32(t, &s->channel) < 0 ||          s->channel == PA_INVALID_INDEX ||          ((s->direction != PA_STREAM_UPLOAD) && (pa_tagstruct_getu32(t, &s->stream_index) < 0 ||  s->stream_index == PA_INVALID_INDEX)) || -        ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &s->requested_bytes) < 0)) { +        ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &requested_bytes) < 0)) {          pa_context_fail(s->context, PA_ERR_PROTOCOL);          goto finish;      } +    s->requested_bytes = (int64_t) requested_bytes; +      if (s->context->version >= 9) {          if (s->direction == PA_STREAM_PLAYBACK) {              if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 || @@ -948,6 +1029,7 @@ static int create_stream(      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); +    PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);      /* Althought some of the other flags are not supported on older       * version, we don't check for them here, because it doesn't hurt       * when they are passed but actually not supported. This makes @@ -975,14 +1057,17 @@ static int create_stream(      if (flags & PA_STREAM_INTERPOLATE_TIMING) {          pa_usec_t x; -        if (s->smoother) -            pa_smoother_free(s->smoother); - -        s->smoother = pa_smoother_new(SMOOTHER_ADJUST_TIME, SMOOTHER_HISTORY_TIME, !(flags & PA_STREAM_NOT_MONOTONIC), SMOOTHER_MIN_HISTORY); - -        x = pa_rtclock_usec(); -        pa_smoother_set_time_offset(s->smoother, x); -        pa_smoother_pause(s->smoother, x); +        x = pa_rtclock_now(); + +        pa_assert(!s->smoother); +        s->smoother = pa_smoother_new( +                SMOOTHER_ADJUST_TIME, +                SMOOTHER_HISTORY_TIME, +                !(flags & PA_STREAM_NOT_MONOTONIC), +                TRUE, +                SMOOTHER_MIN_HISTORY, +                x, +                TRUE);      }      if (!dev) @@ -1108,20 +1193,60 @@ int pa_stream_connect_record(      return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL);  } +int pa_stream_begin_write( +        pa_stream *s, +        void **data, +        size_t *nbytes) { + +    pa_assert(s); +    pa_assert(PA_REFCNT_VALUE(s) >= 1); + +    PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); +    PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY(s->context, data, PA_ERR_INVALID); +    PA_CHECK_VALIDITY(s->context, nbytes && *nbytes != 0, PA_ERR_INVALID); + +    if (!s->write_memblock) { +        s->write_memblock = pa_memblock_new(s->context->mempool, *nbytes); +        s->write_data = pa_memblock_acquire(s->write_memblock); +    } + +    *data = s->write_data; +    *nbytes = pa_memblock_get_length(s->write_memblock); + +    return 0; +} + +int pa_stream_cancel_write( +        pa_stream *s) { + +    pa_assert(s); +    pa_assert(PA_REFCNT_VALUE(s) >= 1); + +    PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); +    PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY(s->context, s->write_memblock, PA_ERR_BADSTATE); + +    pa_assert(s->write_data); + +    pa_memblock_release(s->write_memblock); +    pa_memblock_unref(s->write_memblock); +    s->write_memblock = NULL; +    s->write_data = NULL; + +    return 0; +} +  int pa_stream_write(          pa_stream *s,          const void *data,          size_t length, -        void (*free_cb)(void *p), +        pa_free_cb_t free_cb,          int64_t offset,          pa_seek_mode_t seek) { -    pa_memchunk chunk; -    pa_seek_mode_t t_seek; -    int64_t t_offset; -    size_t t_length; -    const void *t_data; -      pa_assert(s);      pa_assert(PA_REFCNT_VALUE(s) >= 1);      pa_assert(data); @@ -1131,53 +1256,75 @@ int pa_stream_write(      PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE);      PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID);      PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || (seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID); +    PA_CHECK_VALIDITY(s->context, +                      !s->write_memblock || +                      ((data >= s->write_data) && +                       ((const char*) data + length <= (const char*) s->write_data + pa_memblock_get_length(s->write_memblock))), +                      PA_ERR_INVALID); +    PA_CHECK_VALIDITY(s->context, !free_cb || !s->write_memblock, PA_ERR_INVALID); -    if (length <= 0) -        return 0; +    if (s->write_memblock) { +        pa_memchunk chunk; -    t_seek = seek; -    t_offset = offset; -    t_length = length; -    t_data = data; +        /* pa_stream_write_begin() was called before */ -    while (t_length > 0) { +        pa_memblock_release(s->write_memblock); -        chunk.index = 0; +        chunk.memblock = s->write_memblock; +        chunk.index = (const char *) data - (const char *) s->write_data; +        chunk.length = length; -        if (free_cb && !pa_pstream_get_shm(s->context->pstream)) { -            chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) t_data, t_length, free_cb, 1); -            chunk.length = t_length; -        } else { -            void *d; +        s->write_memblock = NULL; +        s->write_data = NULL; -            chunk.length = PA_MIN(t_length, pa_mempool_block_size_max(s->context->mempool)); -            chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length); +        pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk); +        pa_memblock_unref(chunk.memblock); -            d = pa_memblock_acquire(chunk.memblock); -            memcpy(d, t_data, chunk.length); -            pa_memblock_release(chunk.memblock); -        } +    } else { +        pa_seek_mode_t t_seek = seek; +        int64_t t_offset = offset; +        size_t t_length = length; +        const void *t_data = data; -        pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk); +        /* pa_stream_write_begin() was not called before */ -        t_offset = 0; -        t_seek = PA_SEEK_RELATIVE; +        while (t_length > 0) { +            pa_memchunk chunk; -        t_data = (const uint8_t*) t_data + chunk.length; -        t_length -= chunk.length; +            chunk.index = 0; -        pa_memblock_unref(chunk.memblock); -    } +            if (free_cb && !pa_pstream_get_shm(s->context->pstream)) { +                chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) t_data, t_length, free_cb, 1); +                chunk.length = t_length; +            } else { +                void *d; -    if (free_cb && pa_pstream_get_shm(s->context->pstream)) -        free_cb((void*) data); +                chunk.length = PA_MIN(t_length, pa_mempool_block_size_max(s->context->mempool)); +                chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length); -    if (length < s->requested_bytes) -        s->requested_bytes -= (uint32_t) length; -    else -        s->requested_bytes = 0; +                d = pa_memblock_acquire(chunk.memblock); +                memcpy(d, t_data, chunk.length); +                pa_memblock_release(chunk.memblock); +            } + +            pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk); + +            t_offset = 0; +            t_seek = PA_SEEK_RELATIVE; + +            t_data = (const uint8_t*) t_data + chunk.length; +            t_length -= chunk.length; -    /* FIXME!!! ^^^ will break when offset is != 0 and mode is not RELATIVE*/ +            pa_memblock_unref(chunk.memblock); +        } + +        if (free_cb && pa_pstream_get_shm(s->context->pstream)) +            free_cb((void*) data); +    } + +    /* This is obviously wrong since we ignore the seeking index . But +     * that's OK, the server side applies the same error */ +    s->requested_bytes -= (seek == PA_SEEK_RELATIVE ? offset : 0) + (int64_t) length;      if (s->direction == PA_STREAM_PLAYBACK) { @@ -1273,7 +1420,7 @@ size_t pa_stream_writable_size(pa_stream *s) {      PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1);      PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1); -    return s->requested_bytes; +    return s->requested_bytes > 0 ? (size_t) s->requested_bytes : 0;  }  size_t pa_stream_readable_size(pa_stream *s) { @@ -1512,7 +1659,7 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,          if (o->stream->smoother) {              pa_usec_t u, x; -            u = x = pa_rtclock_usec() - i->transport_usec; +            u = x = pa_rtclock_now() - i->transport_usec;              if (o->stream->direction == PA_STREAM_PLAYBACK && o->context->version >= 13) {                  pa_usec_t su; @@ -1537,7 +1684,7 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,                  pa_smoother_put(o->stream->smoother, u, calc_time(o->stream, TRUE));              if (i->playing) -                pa_smoother_resume(o->stream->smoother, x); +                pa_smoother_resume(o->stream->smoother, x, TRUE);          }      } @@ -1797,6 +1944,20 @@ void pa_stream_set_event_callback(pa_stream *s, pa_stream_event_cb_t cb, void *u      s->event_userdata = userdata;  } +void pa_stream_set_buffer_attr_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { +    pa_assert(s); +    pa_assert(PA_REFCNT_VALUE(s) >= 1); + +    if (pa_detect_fork()) +        return; + +    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) +        return; + +    s->buffer_attr_callback = cb; +    s->buffer_attr_userdata = userdata; +} +  void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_operation *o = userdata;      int success = 1; @@ -2007,7 +2168,7 @@ int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) {      PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt, PA_ERR_NODATA);      if (s->smoother) -        usec = pa_smoother_get(s->smoother, pa_rtclock_usec()); +        usec = pa_smoother_get(s->smoother, pa_rtclock_now());      else          usec = calc_time(s, FALSE); diff --git a/src/pulse/stream.h b/src/pulse/stream.h index e80bc65d..fecc5870 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -418,13 +418,71 @@ int pa_stream_connect_record(  /** Disconnect a stream from a source/sink */  int pa_stream_disconnect(pa_stream *s); -/** Write some data to the server (for playback sinks), if free_cb is - * non-NULL this routine is called when all data has been written out - * and an internal reference to the specified data is kept, the data - * is not copied. If NULL, the data is copied into an internal - * buffer. The client my freely seek around in the output buffer. For +/** Prepare writing data to the server (for playback streams). This + * function may be used to optimize the number of memory copies when + * doing playback ("zero-copy"). It is recommended to call this + * function before each call to pa_stream_write(). Pass in the address + * to a pointer and an address of the number of bytes you want to + * write. On return the two values will contain a pointer where you + * can place the data to write and the maximum number of bytes you can + * write. On return *nbytes can be larger or have the same value as + * you passed in. You need to be able to handle both cases. Accessing + * memory beyond the returned *nbytes value is invalid. Acessing the + * memory returned after the following pa_stream_write() or + * pa_stream_cancel_write() is invalid. On invocation only *nbytes + * needs to be initialized, on return both *data and *nbytes will be + * valid. If you place (size_t) -1 in *nbytes on invocation the memory + * size will be chosen automatically (which is recommended to + * do). After placing your data in the memory area returned call + * pa_stream_write() with data set to an address within this memory + * area and an nbytes value that is smaller or equal to what was + * returned by this function to actually execute the write. An + * invocation of pa_stream_write() should follow "quickly" on + * pa_stream_begin_write(). It is not recommended letting an unbounded + * amount of time pass after calling pa_stream_begin_write() and + * before calling pa_stream_write(). If you want to cancel a + * previously called pa_stream_begin_write() without calling + * pa_stream_write() use pa_stream_cancel_write() instead. Calling + * pa_stream_begin_write() twice without calling pa_stream_write() or + * pa_stream_cancel_write() in between will return exactly the same + * pointer/nbytes values.\since 0.9.16 */ +int pa_stream_begin_write( +        pa_stream *p, +        void **data, +        size_t *nbytes); + +/** Reverses the effect of pa_stream_begin_write() dropping all data + * that has already been placed in the memory area returned by + * pa_stream_begin_write(). Only valid to call if + * pa_stream_begin_write() was called before and neither + * pa_stream_cancel_write() nor pa_stream_write() have been called + * yet. Accessing the memory previously returned by + * pa_stream_begin_write() after this call is invalid. Any further + * explicit freeing of the memory area is not necessary. \since + * 0.9.16 */ +int pa_stream_cancel_write( +        pa_stream *p); + +/** Write some data to the server (for playback streams), if free_cb + * is non-NULL this routine is called when all data has been written + * out and an internal reference to the specified data is kept, the + * data is not copied. If NULL, the data is copied into an internal + * buffer. The client may freely seek around in the output buffer. For   * most applications passing 0 and PA_SEEK_RELATIVE as arguments for - * offset and seek should be useful.*/ + * offset and seek should be useful. Afte ther write call succeeded + * the write index will be a the position after where this chunk of + * data has been written to. + * + * As an optimization for avoiding needless memory copies you may call + * pa_stream_begin_write() before this call and then place your audio + * data directly in the memory area returned by that call. Then, pass + * a pointer to that memory area to pa_stream_write(). After the + * invocation of pa_stream_write() the memory area may no longer be + * accessed. Any further explicit freeing of the memory area is not + * necessary. It is OK to write the memory area returned by + * pa_stream_begin_write() only partially with this call, skipping + * bytes both at the end and at the beginning of the reserved memory + * area.*/  int pa_stream_write(          pa_stream *p             /**< The stream to use */,          const void *data         /**< The data to write */, @@ -433,7 +491,7 @@ int pa_stream_write(          int64_t offset,          /**< Offset for seeking, must be 0 for upload streams */          pa_seek_mode_t seek      /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */); -/** Read the next fragment from the buffer (for recording). +/** Read the next fragment from the buffer (for recording streams).   * data will point to the actual data and length will contain the size   * of the data in bytes (which can be less than a complete framgnet).   * Use pa_stream_drop() to actually remove the data from the @@ -512,7 +570,23 @@ void pa_stream_set_suspended_callback(pa_stream *p, pa_stream_notify_cb_t cb, vo   * control event is received.\since 0.9.15 */  void pa_stream_set_event_callback(pa_stream *p, pa_stream_event_cb_t cb, void *userdata); -/** Pause (or resume) playback of this stream temporarily. Available on both playback and recording streams. */ +/** Set the callback function that is called whenver the buffer + * attributes on the server side change. Please note that the buffer + * attributes can change when moving a stream to a different + * sink/source too, hence if you use this callback you should use + * pa_stream_set_moved_callback() as well. \since 0.9.15 */ +void pa_stream_set_buffer_attr_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Pause (or resume) playback of this stream temporarily. Available + * on both playback and recording streams. If b is 1 the stream is + * paused. If b is 0 the stream is resumed. The pause/resume operation + * is executed as quickly as possible. If a cork is very quickly + * followed by an uncork or the other way round this might not + * actually have any effect on the stream that is output. You can use + * pa_stream_is_corked() to find out whether the stream is currently + * paused or not. Normally a stream will be created in uncorked + * state. If you pass PA_STREAM_START_CORKED as flag during connection + * of the stream it will be created in corked state. */  pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata);  /** Flush the playback buffer of this stream. Most of the time you're @@ -530,42 +604,68 @@ pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *us   * temporarily. Available for playback streams only. */  pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); -/** Rename the stream.*/ +/** Rename the stream. */  pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata);  /** Return the current playback/recording time. This is based on the   * data in the timing info structure returned by - * pa_stream_get_timing_info(). This function will usually only return - * new data if a timing info update has been recieved. Only if timing - * interpolation has been requested (PA_STREAM_INTERPOLATE_TIMING) - * the data from the last timing update is used for an estimation of - * the current playback/recording time based on the local time that - * passed since the timing info structure has been acquired. The time - * value returned by this function is guaranteed to increase - * monotonically. (that means: the returned value is always greater or - * equal to the value returned on the last call) This behaviour can - * be disabled by using PA_STREAM_NOT_MONOTONIC. This may be + * pa_stream_get_timing_info(). + * + * This function will usually only return new data if a timing info + * update has been recieved. Only if timing interpolation has been + * requested (PA_STREAM_INTERPOLATE_TIMING) the data from the last + * timing update is used for an estimation of the current + * playback/recording time based on the local time that passed since + * the timing info structure has been acquired. + * + * The time value returned by this function is guaranteed to increase + * monotonically.  (that means: the returned value is always greater + * or equal to the value returned on the last call). This behaviour + * can be disabled by using PA_STREAM_NOT_MONOTONIC. This may be   * desirable to deal better with bad estimations of transport   * latencies, but may have strange effects if the application is not - * able to deal with time going 'backwards'. */ + * able to deal with time going 'backwards'. + * + * The time interpolator activated by PA_STREAM_INTERPOLATE_TIMING + * favours 'smooth' time graphs over accurate ones to improve the + * smoothness of UI operations that are tied to the audio clock. If + * accuracy is more important to you you might need to estimate your + * timing based on the data from pa_stream_get_timing_info() yourself + * or not work with interpolated timing at all and instead always + * query on the server side for the most up to date timing with + * pa_stream_update_timing_info(). + * + * If no timing information has been + * recieved yet this call will return PA_ERR_NODATA. For more details + * see pa_stream_get_timing_info(). */  int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec);  /** Return the total stream latency. This function is based on - * pa_stream_get_time(). In case the stream is a monitoring stream the - * result can be negative, i.e. the captured samples are not yet - * played. In this case *negative is set to 1. */ + * pa_stream_get_time(). + * + * In case the stream is a monitoring stream the result can be + * negative, i.e. the captured samples are not yet played. In this + * case *negative is set to 1. + * + * If no timing information has been recieved yet this call will + * return PA_ERR_NODATA. For more details see + * pa_stream_get_timing_info() and pa_stream_get_time(). */  int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative);  /** Return the latest raw timing data structure. The returned pointer   * points to an internal read-only instance of the timing   * structure. The user should make a copy of this structure if he   * wants to modify it. An in-place update to this data structure may - * be requested using pa_stream_update_timing_info(). If no - * pa_stream_update_timing_info() call was issued before, this - * function will fail with PA_ERR_NODATA. Please note that the - * write_index member field (and only this field) is updated on each - * pa_stream_write() call, not just when a timing update has been - * recieved. */ + * be requested using pa_stream_update_timing_info(). + * + * If no timing information has been received before (i.e. by + * requesting pa_stream_update_timing_info() or by using + * PA_STREAM_AUTO_TIMING_UPDATE), this function will fail with + * PA_ERR_NODATA. + * + * Please note that the write_index member field (and only this field) + * is updated on each pa_stream_write() call, not just when a timing + * update has been recieved. */  const pa_timing_info* pa_stream_get_timing_info(pa_stream *s);  /** Return a pointer to the stream's sample specification. */ diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h index a93510ad..44ed24ae 100644 --- a/src/pulse/subscribe.h +++ b/src/pulse/subscribe.h @@ -35,7 +35,7 @@   * \section overv_sec Overview   *   * The application can be notified, asynchronously, whenever the internal - * layout of the server changes. Possible notifications are desribed in the + * layout of the server changes. Possible notifications are described in the   * \ref pa_subscription_event_type and \ref pa_subscription_mask   * enumerations.   * diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c index c77cc64e..6916d867 100644 --- a/src/pulse/thread-mainloop.c +++ b/src/pulse/thread-mainloop.c @@ -24,6 +24,10 @@  #include <config.h>  #endif +#ifndef OS_IS_WIN32 +#include <pthread.h> +#endif +  #include <signal.h>  #include <stdio.h> diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h index 2b3faf16..48c6cdb3 100644 --- a/src/pulse/timeval.h +++ b/src/pulse/timeval.h @@ -40,16 +40,19 @@ PA_C_DECL_BEGIN  #define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL)  /** The number of nanoseconds in a second */ -#define PA_NSEC_PER_SEC ((pa_usec_t) 1000000000ULL) +#define PA_NSEC_PER_SEC ((unsigned long long) 1000000000ULL)  /** The number of microseconds in a millisecond */  #define PA_USEC_PER_MSEC ((pa_usec_t) 1000ULL)  /** The number of nanoseconds in a millisecond */ -#define PA_NSEC_PER_MSEC ((pa_usec_t) 1000000ULL) +#define PA_NSEC_PER_MSEC ((unsigned long long) 1000000ULL)  /** The number of nanoseconds in a microsecond */ -#define PA_NSEC_PER_USEC ((pa_usec_t) 1000ULL) +#define PA_NSEC_PER_USEC ((unsigned long long) 1000ULL) + +/** Invalid time in usec */ +#define PA_USEC_INVALID ((pa_usec_t) -1)  struct timeval; @@ -60,7 +63,7 @@ struct timeval *pa_gettimeofday(struct timeval *tv);   * structs. */  pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) PA_GCC_PURE; -/** Compare the two timeval structs and return 0 when equal, negative when a < b, positive otherwse */ +/** Compare the two timeval structs and return 0 when equal, negative when a < b, positive otherwise */  int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) PA_GCC_PURE;  /** Return the time difference between now and the specified timestamp */ diff --git a/src/pulse/util.c b/src/pulse/util.c index 54a188d5..6f1e40a9 100644 --- a/src/pulse/util.c +++ b/src/pulse/util.c @@ -219,7 +219,8 @@ char *pa_get_binary_name(char *s, size_t l) {  char *pa_path_get_filename(const char *p) {      char *fn; -    pa_assert(p); +    if (!p) +        return NULL;      if ((fn = strrchr(p, PA_PATH_SEP_CHAR)))          return fn+1; diff --git a/src/pulse/util.h b/src/pulse/util.h index f6dd40cb..ad85653d 100644 --- a/src/pulse/util.h +++ b/src/pulse/util.h @@ -51,7 +51,7 @@ char *pa_get_home_dir(char *s, size_t l);  char *pa_get_binary_name(char *s, size_t l);  /** Return a pointer to the filename inside a path (which is the last - * component). */ + * component). If passed NULL will return NULL. */  char *pa_path_get_filename(const char *p);  /** Wait t milliseconds */ diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in index 566dd55e..c2c1f20a 100644 --- a/src/pulse/version.h.in +++ b/src/pulse/version.h.in @@ -60,6 +60,13 @@ const char* pa_get_library_version(void);  /** The micro version of PA. \since 0.9.15 */  #define PA_MICRO @PA_MICRO@ +/** Evaluates to TRUE if the PulseAudio library version is equal or + * newer than the specified. \since 0.9.16 */ +#define PA_CHECK_VERSION(major,minor,micro)                             \ +    ((PA_MAJOR > (major)) ||                                            \ +     (PA_MAJOR == (major) && PA_MINOR > (minor)) ||                     \ +     (PA_MAJOR == (major) && PA_MINOR == (minor) && PA_MICRO >= (micro))) +  PA_C_DECL_END  #endif diff --git a/src/pulse/volume.c b/src/pulse/volume.c index c865058d..42cde5b9 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -27,8 +27,10 @@  #include <string.h>  #include <pulse/i18n.h> +  #include <pulsecore/core-util.h>  #include <pulsecore/macro.h> +#include <pulsecore/sample-util.h>  #include "volume.h" @@ -80,29 +82,78 @@ pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) {  pa_volume_t pa_cvolume_avg(const pa_cvolume *a) {      uint64_t sum = 0; -    int i; +    unsigned c;      pa_assert(a);      pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); -    for (i = 0; i < a->channels; i++) -        sum += a->values[i]; +    for (c = 0; c < a->channels; c++) +        sum += a->values[c];      sum /= a->channels;      return (pa_volume_t) sum;  } +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { +    uint64_t sum = 0; +    unsigned c, n; + +    pa_assert(a); + +    if (!cm) +        return pa_cvolume_avg(a); + +    pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + +    for (c = n = 0; c < a->channels; c++) { + +        if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) +            continue; + +        sum += a->values[c]; +        n ++; +    } + +    if (n > 0) +        sum /= n; + +    return (pa_volume_t) sum; +} +  pa_volume_t pa_cvolume_max(const pa_cvolume *a) {      pa_volume_t m = 0; -    int i; +    unsigned c;      pa_assert(a);      pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); -    for (i = 0; i < a->channels; i++) -        if (a->values[i] > m) -            m = a->values[i]; +    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_assert(a); + +    if (!cm) +        return pa_cvolume_max(a); + +    pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + +    for (c = n = 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;  } @@ -120,39 +171,57 @@ pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) {      return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) / v);  } -#define USER_DECIBEL_RANGE 60 +/* Amplitude, not power */ +static double linear_to_dB(double v) { +    return 20.0 * log10(v); +} + +static double dB_to_linear(double v) { +    return pow(10.0, v / 20.0); +}  pa_volume_t pa_sw_volume_from_dB(double dB) { -    if (isinf(dB) < 0 || dB <= -USER_DECIBEL_RANGE) +    if (isinf(dB) < 0 || dB <= PA_DECIBEL_MININFTY)          return PA_VOLUME_MUTED; -    return (pa_volume_t) lrint((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM); +    return pa_sw_volume_from_linear(dB_to_linear(dB));  }  double pa_sw_volume_to_dB(pa_volume_t v) { -    if (v == PA_VOLUME_MUTED) + +    if (v <= PA_VOLUME_MUTED)          return PA_DECIBEL_MININFTY; -    return ((double) v/PA_VOLUME_NORM-1)*USER_DECIBEL_RANGE; +    return linear_to_dB(pa_sw_volume_to_linear(v));  }  pa_volume_t pa_sw_volume_from_linear(double v) { -    if (v <= 0) +    if (v <= 0.0)          return PA_VOLUME_MUTED; -    if (v > .999 && v < 1.001) -        return PA_VOLUME_NORM; +    /* +     * We use a cubic mapping here, as suggested and discussed here: +     * +     * http://www.robotplanet.dk/audio/audio_gui_design/ +     * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 +     */ -    return pa_sw_volume_from_dB(20*log10(v)); +    return (pa_volume_t) (cbrt(v) * PA_VOLUME_NORM);  }  double pa_sw_volume_to_linear(pa_volume_t v) { +    double f; -    if (v == PA_VOLUME_MUTED) -        return 0; +    if (v <= PA_VOLUME_MUTED) +        return 0.0; + +    if (v == PA_VOLUME_NORM) +        return 1.0; -    return pow(10.0, pa_sw_volume_to_dB(v)/20.0); +    f = ((double) v / PA_VOLUME_NORM); + +    return f*f*f;  }  char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { @@ -225,7 +294,7 @@ char *pa_sw_cvolume_snprint_dB(char *s, size_t l, const pa_cvolume *c) {          l -= pa_snprintf(e, l, "%s%u: %0.2f dB",                           first ? "" : " ",                           channel, -                         isinf(f) < 0 || f <= -USER_DECIBEL_RANGE ? -INFINITY : f); +                         isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f);          e = strchr(e, 0);          first = FALSE; @@ -249,7 +318,7 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v) {      f = pa_sw_volume_to_dB(v);      pa_snprintf(s, l, "%0.2f dB", -                isinf(f) < 0 || f <= -USER_DECIBEL_RANGE ?  -INFINITY : f); +                isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ?  -INFINITY : f);      return s;  } @@ -277,7 +346,7 @@ pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const      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 < PA_CHANNELS_MAX; i++) +    for (i = 0; i < a->channels && i < b->channels; i++)          dest->values[i] = pa_sw_volume_multiply(a->values[i], b->values[i]);      dest->channels = (uint8_t) i; @@ -285,6 +354,22 @@ pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const      return dest;  } +pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { +    unsigned i; + +    pa_assert(dest); +    pa_assert(a); + +    pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + +    for (i = 0; i < a->channels; i++) +        dest->values[i] = pa_sw_volume_multiply(a->values[i], b); + +    dest->channels = (uint8_t) i; + +    return dest; +} +  pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) {      unsigned i; @@ -295,7 +380,7 @@ pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa      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 < PA_CHANNELS_MAX; i++) +    for (i = 0; i < a->channels && i < b->channels; i++)          dest->values[i] = pa_sw_volume_divide(a->values[i], b->values[i]);      dest->channels = (uint8_t) i; @@ -303,6 +388,22 @@ pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa      return dest;  } +pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { +    unsigned i; + +    pa_assert(dest); +    pa_assert(a); + +    pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + +    for (i = 0; i < a->channels; i++) +        dest->values[i] = pa_sw_volume_divide(a->values[i], b); + +    dest->channels = (uint8_t) i; + +    return dest; +} +  int pa_cvolume_valid(const pa_cvolume *v) {      unsigned c; @@ -319,65 +420,27 @@ int pa_cvolume_valid(const pa_cvolume *v) {  }  static pa_bool_t on_left(pa_channel_position_t p) { - -    return -        p == PA_CHANNEL_POSITION_FRONT_LEFT || -        p == PA_CHANNEL_POSITION_REAR_LEFT || -        p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || -        p == PA_CHANNEL_POSITION_SIDE_LEFT || -        p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || -        p == PA_CHANNEL_POSITION_TOP_REAR_LEFT; +    return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_LEFT);  }  static pa_bool_t on_right(pa_channel_position_t p) { - -    return -        p == PA_CHANNEL_POSITION_FRONT_RIGHT || -        p == PA_CHANNEL_POSITION_REAR_RIGHT || -        p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER || -        p == PA_CHANNEL_POSITION_SIDE_RIGHT || -        p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || -        p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT; +    return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_RIGHT);  }  static pa_bool_t on_center(pa_channel_position_t p) { - -    return -        p == PA_CHANNEL_POSITION_FRONT_CENTER || -        p == PA_CHANNEL_POSITION_REAR_CENTER || -        p == PA_CHANNEL_POSITION_TOP_CENTER || -        p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER || -        p == PA_CHANNEL_POSITION_TOP_REAR_CENTER; +    return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_CENTER);  }  static pa_bool_t on_lfe(pa_channel_position_t p) { - -    return -        p == PA_CHANNEL_POSITION_LFE; +    return p == PA_CHANNEL_POSITION_LFE;  }  static pa_bool_t on_front(pa_channel_position_t p) { - -    return -        p == PA_CHANNEL_POSITION_FRONT_LEFT || -        p == PA_CHANNEL_POSITION_FRONT_RIGHT || -        p == PA_CHANNEL_POSITION_FRONT_CENTER || -        p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || -        p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER || -        p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || -        p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || -        p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER; +    return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_FRONT);  }  static pa_bool_t on_rear(pa_channel_position_t p) { - -    return -        p == PA_CHANNEL_POSITION_REAR_LEFT || -        p == PA_CHANNEL_POSITION_REAR_RIGHT || -        p == PA_CHANNEL_POSITION_REAR_CENTER || -        p == PA_CHANNEL_POSITION_TOP_REAR_LEFT || -        p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT || -        p == PA_CHANNEL_POSITION_TOP_REAR_CENTER; +    return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_REAR);  }  pa_cvolume *pa_cvolume_remap(pa_cvolume *v, const pa_channel_map *from, const pa_channel_map *to) { @@ -572,11 +635,29 @@ pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) {      pa_return_val_if_fail(pa_cvolume_valid(v), NULL);      pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); +    t = pa_cvolume_max(v); + +    if (t <= PA_VOLUME_MUTED) +        return pa_cvolume_set(v, v->channels, max); +      for (c = 0; c < v->channels; c++) -        if (v->values[c] > t) -            t = v->values[c]; +        v->values[c] = (pa_volume_t) (((uint64_t)  v->values[c] * (uint64_t) max) / (uint64_t) t); -    if (t <= 0) +    return v; +} + +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask) { +    unsigned c; +    pa_volume_t t = 0; + +    pa_assert(v); + +    pa_return_val_if_fail(pa_cvolume_valid(v), NULL); +    pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + +    t = pa_cvolume_max_mask(v, cm, mask); + +    if (t <= PA_VOLUME_MUTED)          return pa_cvolume_set(v, v->channels, max);      for (c = 0; c < v->channels; c++) @@ -685,3 +766,49 @@ pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float      return v;  } + +pa_cvolume* pa_cvolume_set_position( +        pa_cvolume *cv, +        const pa_channel_map *map, +        pa_channel_position_t t, +        pa_volume_t v) { + +    unsigned c; +    pa_bool_t good = FALSE; + +    pa_assert(cv); +    pa_assert(map); + +    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); + +    for (c = 0; c < map->channels; c++) +        if (map->map[c] == t) { +            cv->values[c] = v; +            good = TRUE; +        } + +    return good ? cv : NULL; +} + +pa_volume_t pa_cvolume_get_position( +        pa_cvolume *cv, +        const pa_channel_map *map, +        pa_channel_position_t t) { + +    unsigned c; +    pa_volume_t v = PA_VOLUME_MUTED; + +    pa_assert(cv); +    pa_assert(map); + +    pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), PA_VOLUME_MUTED); +    pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, PA_VOLUME_MUTED); + +    for (c = 0; c < map->channels; c++) +        if (map->map[c] == t) +            if (cv->values[c] > v) +                v = cv->values[c]; + +    return v; +} diff --git a/src/pulse/volume.h b/src/pulse/volume.h index c3c396c8..05b7ebb4 100644 --- a/src/pulse/volume.h +++ b/src/pulse/volume.h @@ -24,6 +24,7 @@  ***/  #include <inttypes.h> +#include <limits.h>  #include <pulse/cdecl.h>  #include <pulse/gccmacro.h> @@ -102,12 +103,15 @@ PA_C_DECL_BEGIN   * > PA_VOLUME_NORM: increased volume */  typedef uint32_t pa_volume_t; -/** Normal volume (100%) */ +/** Normal volume (100%, 0 dB) */  #define PA_VOLUME_NORM ((pa_volume_t) 0x10000U) -/** Muted volume (0%) */ +/** Muted 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) +  /** A structure encapsulating a per-channel volume */  typedef struct pa_cvolume {      uint8_t channels;                     /**< Number of channels */ @@ -174,9 +178,23 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v);  /** Return the average volume of all channels */  pa_volume_t pa_cvolume_avg(const pa_cvolume *a) PA_GCC_PURE; +/** Return the average 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_avg(). If no + * channel is selected the returned value will be + * PA_VOLUME_MUTED. \since 0.9.16 */ +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; +  /** Return the maximum volume of all channels. \since 0.9.12 */  pa_volume_t pa_cvolume_max(const pa_cvolume *a) PA_GCC_PURE; +/** Return the maximum 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_max(). If no + * channel is selected the returned value will be PA_VOLUME_MUTED. + * \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 TRUE when the passed cvolume structure is valid, FALSE otherwise */  int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE; @@ -198,20 +216,30 @@ pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) PA_GCC_CONST;   * *dest. This is only valid for software volumes! */  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 + * 0.9.16 */ +pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b); +  /** Divide two volume specifications, return the result. This uses   * PA_VOLUME_NORM as neutral element of division. This is only valid   * for software volumes! If a division by zero is tried the result   * will be 0. \since 0.9.13 */  pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; -/** Multiply to per-channel volumes and return the result in +/** Divide two per-channel volumes and return the result in   * *dest. This is only valid for software volumes! \since 0.9.13 */  pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); -/** Convert a decibel value to a volume. This is only valid for software volumes! */ +/** Divide a per-channel volume by a scalar volume and return the + * result in *dest. This is only valid for software volumes! \since + * 0.9.16 */ +pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b); + +/** Convert a decibel value to a volume (amplitude, not power). This is only valid for software volumes! */  pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST; -/** Convert a volume to a decibel value. This is only valid for software volumes! */ +/** Convert a volume to a decibel value (amplitude, not power). This is only valid for software volumes! */  double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST;  /** Convert a linear factor to a volume. This is only valid for software volumes! */ @@ -223,7 +251,7 @@ double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST;  #ifdef INFINITY  #define PA_DECIBEL_MININFTY ((double) -INFINITY)  #else -/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). */ +/** This floor value is used as minus infinity when using pa_volume_{to,from}_dB(). */  #define PA_DECIBEL_MININFTY ((double) -200.0)  #endif @@ -279,6 +307,25 @@ pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float   * volumes are kept. \since 0.9.15 */  pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max); +/** Scale the passed pa_cvolume structure so that the maximum volume + * of all channels selected via cm/mask equals max. This also modifies + * the volume of those channels that are unmasked. The proportions + * between the channel volumes are kept. \since 0.9.16 */ +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask); + +/** Set the passed volume to all channels at the specified channel + * position. Will return the updated volume struct, or NULL if there + * is no channel at the position specified. You can check if a channel + * map includes a specific position by calling + * pa_channel_map_has_position(). \since 0.9.16 */ +pa_cvolume* pa_cvolume_set_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t, pa_volume_t v); + +/** Get the maximum volume of all channels at the specified channel + * position. Will return 0 if there is no channel at the position + * specified. You can check if a channel map includes a specific + * 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; +  PA_C_DECL_END  #endif diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h index db20496f..f720d83f 100644 --- a/src/pulse/xmalloc.h +++ b/src/pulse/xmalloc.h @@ -88,9 +88,20 @@ static inline void* _pa_xnewdup_internal(const void *p, size_t n, size_t k) {      return pa_xmemdup(p, n*k);  } -/** Same as pa_xnew() but set the memory to zero */ +/** Same as pa_xnew() but duplicate the specified data */  #define pa_xnewdup(type, p, n) ((type*) _pa_xnewdup_internal((p), (n), sizeof(type))) +/** Internal helper for pa_xrenew() */ +static void* _pa_xrenew_internal(void *p, size_t n, size_t k) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE2(2,3); + +static inline void* _pa_xrenew_internal(void *p, size_t n, size_t k) { +    assert(n < INT_MAX/k); +    return pa_xrealloc(p, n*k); +} + +/** Reallocate n new structures of the specified type. */ +#define pa_xrenew(type, p, n) ((type*) _pa_xrenew_internal(p, (n), sizeof(type))) +  PA_C_DECL_END  #endif diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c index 67f661fe..072ef02c 100644 --- a/src/pulsecore/asyncq.c +++ b/src/pulsecore/asyncq.c @@ -131,7 +131,7 @@ void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) {      pa_xfree(l);  } -static int push(pa_asyncq*l, void *p, pa_bool_t wait) { +static int push(pa_asyncq*l, void *p, pa_bool_t wait_op) {      unsigned idx;      pa_atomic_ptr_t *cells; @@ -145,7 +145,7 @@ static int push(pa_asyncq*l, void *p, pa_bool_t wait) {      if (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)) { -        if (!wait) +        if (!wait_op)              return -1;  /*         pa_log("sleeping on push"); */ @@ -163,14 +163,14 @@ static int push(pa_asyncq*l, void *p, pa_bool_t wait) {      return 0;  } -static pa_bool_t flush_postq(pa_asyncq *l, pa_bool_t wait) { +static pa_bool_t flush_postq(pa_asyncq *l, pa_bool_t wait_op) {      struct localq *q;      pa_assert(l);      while ((q = l->last_localq)) { -        if (push(l, q->data, wait) < 0) +        if (push(l, q->data, wait_op) < 0)              return FALSE;          l->last_localq = q->prev; @@ -184,13 +184,13 @@ static pa_bool_t flush_postq(pa_asyncq *l, pa_bool_t wait) {      return TRUE;  } -int pa_asyncq_push(pa_asyncq*l, void *p, pa_bool_t wait) { +int pa_asyncq_push(pa_asyncq*l, void *p, pa_bool_t wait_op) {      pa_assert(l); -    if (!flush_postq(l, wait)) +    if (!flush_postq(l, wait_op))          return -1; -    return push(l, p, wait); +    return push(l, p, wait_op);  }  void pa_asyncq_post(pa_asyncq*l, void *p) { @@ -221,7 +221,7 @@ void pa_asyncq_post(pa_asyncq*l, void *p) {      return;  } -void* pa_asyncq_pop(pa_asyncq*l, pa_bool_t wait) { +void* pa_asyncq_pop(pa_asyncq*l, pa_bool_t wait_op) {      unsigned idx;      void *ret;      pa_atomic_ptr_t *cells; @@ -235,7 +235,7 @@ void* pa_asyncq_pop(pa_asyncq*l, pa_bool_t wait) {      if (!(ret = pa_atomic_ptr_load(&cells[idx]))) { -        if (!wait) +        if (!wait_op)              return NULL;  /*         pa_log("sleeping on pop"); */ diff --git a/src/pulsecore/aupdate.c b/src/pulsecore/aupdate.c new file mode 100644 index 00000000..56ebb8e5 --- /dev/null +++ b/src/pulsecore/aupdate.c @@ -0,0 +1,129 @@ +/*** +  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/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> + +#include "aupdate.h" + +#define MSB (1U << (sizeof(unsigned)*8U-1)) +#define WHICH(n) (!!((n) & MSB)) +#define COUNTER(n) ((n) & ~MSB) + +struct pa_aupdate { +    pa_atomic_t read_lock; +    pa_mutex *write_lock; +    pa_semaphore *semaphore; +}; + +pa_aupdate *pa_aupdate_new(void) { +    pa_aupdate *a; + +    a = pa_xnew(pa_aupdate, 1); +    pa_atomic_store(&a->read_lock, 0); +    a->write_lock = pa_mutex_new(FALSE, FALSE); +    a->semaphore = pa_semaphore_new(0); + +    return a; +} + +void pa_aupdate_free(pa_aupdate *a) { +    pa_assert(a); + +    pa_mutex_free(a->write_lock); +    pa_semaphore_free(a->semaphore); + +    pa_xfree(a); +} + +unsigned pa_aupdate_read_begin(pa_aupdate *a) { +    unsigned n; + +    pa_assert(a); + +    /* Increase the lock counter */ +    n = (unsigned) pa_atomic_inc(&a->read_lock); + +    /* When n is 0 we have about 2^31 threads running that all try to +     * access the data at the same time, oh my! */ +    pa_assert(COUNTER(n)+1 > 0); + +    /* The uppermost bit tells us which data to look at */ +    return WHICH(n); +} + +void pa_aupdate_read_end(pa_aupdate *a) { +    unsigned n; + +    pa_assert(a); + +    /* Decrease the lock counter */ +    n = (unsigned) pa_atomic_dec(&a->read_lock); + +    /* Make sure the counter was valid */ +    pa_assert(COUNTER(n) > 0); + +    /* Post the semaphore */ +    pa_semaphore_post(a->semaphore); +} + +unsigned pa_aupdate_write_begin(pa_aupdate *a) { +    unsigned n; + +    pa_assert(a); + +    pa_mutex_lock(a->write_lock); + +    n = (unsigned) pa_atomic_load(&a->read_lock); + +    return !WHICH(n); +} + +unsigned pa_aupdate_write_swap(pa_aupdate *a) { +    unsigned n; + +    pa_assert(a); + +    for (;;) { +        n = (unsigned) pa_atomic_load(&a->read_lock); + +        /* If the read counter is > 0 wait; if it is 0 try to swap the lists */ +        if (COUNTER(n) > 0) +            pa_semaphore_wait(a->semaphore); +        else if (pa_atomic_cmpxchg(&a->read_lock, (int) n, (int) (n ^ MSB))) +            break; +    } + +    return WHICH(n); +} + +void pa_aupdate_write_end(pa_aupdate *a) { +    pa_assert(a); + +    pa_mutex_unlock(a->write_lock); +} diff --git a/src/pulsecore/aupdate.h b/src/pulsecore/aupdate.h new file mode 100644 index 00000000..072e382d --- /dev/null +++ b/src/pulsecore/aupdate.h @@ -0,0 +1,98 @@ +#ifndef foopulsecoreaupdatehfoo +#define foopulsecoreaupdatehfoo + +/*** +  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. +***/ + +typedef struct pa_aupdate pa_aupdate; + +pa_aupdate *pa_aupdate_new(void); +void pa_aupdate_free(pa_aupdate *a); + +/* Will return 0, or 1, depending on which copy of the data the caller + * should look at */ +unsigned pa_aupdate_read_begin(pa_aupdate *a); +void pa_aupdate_read_end(pa_aupdate *a); + +/* Will return 0, or 1, depending which copy of the data the caller + * should modify */ +unsigned pa_aupdate_write_begin(pa_aupdate *a); +void pa_aupdate_write_end(pa_aupdate *a); + +/* Will return 0, or 1, depending which copy of the data the caller + * should modify. Each time called this will return the opposite of + * the previous pa_aupdate_write_begin()/pa_aupdate_write_swap() + * call. Should only be called between pa_aupdate_write_begin() and + * pa_aupdate_write_end() */ +unsigned pa_aupdate_write_swap(pa_aupdate *a); + +/* + * This infrastructure allows lock-free updates of arbitrary data + * structures in an rcu'ish way: two copies of the data structure + * should be exisiting. One side ('the reader') has read access to one + * of the two data structure at a time. It does not have to lock it, + * however it needs to signal that it is using it/stopped using + * it. The other side ('the writer') modifes the second data structure, + * and then atomically swaps the two data structures, followed by a + * modification of the other one. + * + * This is intended to be used for cases where the reader side needs + * to be fast while the writer side can be slow. + * + * The reader side is signal handler safe. + * + * The writer side lock is not recursive. The reader side is. + * + * There may be multiple readers and multiple writers at the same + * time. + * + * Usage is like this: + * + * static struct foo bar[2]; + * static pa_aupdate *a; + * + * reader() { + *     unsigned j; + * + *     j = pa_update_read_begin(a); + * + *     ... read the data structure bar[j] ... + * + *     pa_update_read_end(a); + * } + * + * writer() { + *    unsigned j; + * + *    j = pa_update_write_begin(a); + * + *    ... update the data structure bar[j] ... + * + *    j = pa_update_write_swap(a); + * + *    ... update the data structure bar[j], the same way as above ... + * + *    pa_update_write_end(a) + * } + * + */ + +#endif diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c index b122feee..1e31d076 100644 --- a/src/pulsecore/authkey.c +++ b/src/pulsecore/authkey.c @@ -190,7 +190,7 @@ int pa_authkey_load_auto(const char *fn, void *data, size_t length) {      return pa_authkey_load(p, data, length);  } -/* Store the specified cookie in the speicified cookie file */ +/* Store the specified cookie in the specified cookie file */  int pa_authkey_save(const char *fn, const void *data, size_t length) {      int fd = -1;      int unlock = 0, ret = -1; diff --git a/src/pulsecore/avahi-wrap.c b/src/pulsecore/avahi-wrap.c index 56d9d3dd..f1f08bcc 100644 --- a/src/pulsecore/avahi-wrap.c +++ b/src/pulsecore/avahi-wrap.c @@ -23,6 +23,7 @@  #include <config.h>  #endif +#include <pulse/timeval.h>  #include <pulse/xmalloc.h>  #include <pulsecore/log.h> @@ -116,14 +117,13 @@ struct AvahiTimeout {      void *userdata;  }; -static void timeout_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { -    AvahiTimeout *t = userdata; +static void timeout_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { +    AvahiTimeout *to = userdata;      pa_assert(a);      pa_assert(e); -    pa_assert(t); -    t->callback(t, t->userdata); +    to->callback(to, to->userdata);  }  static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) { @@ -145,6 +145,7 @@ static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv,  }  static void timeout_update(AvahiTimeout *t, const struct timeval *tv) { +      pa_assert(t);      if (t->time_event && tv) diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index 6419c234..2f0a3af0 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -141,20 +141,19 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {      data->profiles = NULL;      c->active_profile = NULL; +    c->save_profile = FALSE;      if (data->active_profile && c->profiles) -        c->active_profile = pa_hashmap_get(c->profiles, data->active_profile); +        if ((c->active_profile = pa_hashmap_get(c->profiles, data->active_profile))) +            c->save_profile = data->save_profile;      if (!c->active_profile && c->profiles) { -        void *state = NULL; +        void *state;          pa_card_profile *p; -        while ((p = pa_hashmap_iterate(c->profiles, &state, NULL))) { -            if (!c->active_profile || -                p->priority > c->active_profile->priority) - +        PA_HASHMAP_FOREACH(p, c->profiles, state) +            if (!c->active_profile || p->priority > c->active_profile->priority)                  c->active_profile = p; -        }      }      c->userdata = NULL; @@ -162,6 +161,7 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {      pa_device_init_description(c->proplist);      pa_device_init_icon(c->proplist, TRUE); +    pa_device_init_intended_roles(c->proplist);      pa_assert_se(pa_idxset_put(core->cards, c, &c->index) >= 0); @@ -174,7 +174,6 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {  void pa_card_free(pa_card *c) {      pa_core *core; -    pa_card_profile *profile;      pa_assert(c);      pa_assert(c->core); @@ -197,8 +196,10 @@ void pa_card_free(pa_card *c) {      pa_idxset_free(c->sources, NULL, NULL);      if (c->profiles) { -        while ((profile = pa_hashmap_steal_first(c->profiles))) -            pa_card_profile_free(profile); +        pa_card_profile *p; + +        while ((p = pa_hashmap_steal_first(c->profiles))) +            pa_card_profile_free(p);          pa_hashmap_free(c->profiles, NULL, NULL);      } @@ -209,49 +210,62 @@ void pa_card_free(pa_card *c) {      pa_xfree(c);  } -int pa_card_set_profile(pa_card *c, const char *name) { +int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save) {      pa_card_profile *profile; +    int r;      pa_assert(c);      if (!c->set_profile) { -        pa_log_warn("set_profile() operation not implemented for card %u \"%s\"", c->index, c->name); -        return -1; +        pa_log_debug("set_profile() operation not implemented for card %u \"%s\"", c->index, c->name); +        return -PA_ERR_NOTIMPLEMENTED;      }      if (!c->profiles) -        return -1; +        return -PA_ERR_NOENTITY;      if (!(profile = pa_hashmap_get(c->profiles, name))) -        return -1; +        return -PA_ERR_NOENTITY; -    if (c->active_profile == profile) +    if (c->active_profile == profile) { +        c->save_profile = c->save_profile || save;          return 0; +    } -    if (c->set_profile(c, profile) < 0) -        return -1; +    if ((r = c->set_profile(c, profile)) < 0) +        return r;      pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);      pa_log_info("Changed profile of card %u \"%s\" to %s", c->index, c->name, profile->name);      c->active_profile = profile; +    c->save_profile = save;      return 0;  } -int pa_card_suspend(pa_card *c, pa_bool_t suspend) { +int pa_card_suspend(pa_card *c, pa_bool_t suspend, pa_suspend_cause_t cause) {      pa_sink *sink;      pa_source *source;      uint32_t idx;      int ret = 0;      pa_assert(c); +    pa_assert(cause != 0); -    for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) -        ret -= pa_sink_suspend(sink, suspend) < 0; +    for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) { +        int r; -    for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) -        ret -= pa_source_suspend(source, suspend) < 0; +        if ((r = pa_sink_suspend(sink, suspend, cause)) < 0) +            ret = r; +    } + +    for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) { +        int r; + +        if ((r = pa_source_suspend(source, suspend, cause)) < 0) +            ret = r; +    }      return ret;  } diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index c80d4e2e..2d691b67 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -63,6 +63,8 @@ struct pa_card {      pa_hashmap *profiles;      pa_card_profile *active_profile; +    pa_bool_t save_profile:1; +      void *userdata;      int (*set_profile)(pa_card *c, pa_card_profile *profile); @@ -70,9 +72,8 @@ struct pa_card {  typedef struct pa_card_new_data {      char *name; -    char *description; -      pa_proplist *proplist; +      const char *driver;      pa_module *module; @@ -80,6 +81,8 @@ typedef struct pa_card_new_data {      char *active_profile;      pa_bool_t namereg_fail:1; + +    pa_bool_t save_profile:1;  } pa_card_new_data;  pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra); @@ -93,8 +96,8 @@ void pa_card_new_data_done(pa_card_new_data *data);  pa_card *pa_card_new(pa_core *c, pa_card_new_data *data);  void pa_card_free(pa_card *c); -int pa_card_set_profile(pa_card *c, const char *name); +int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save); -int pa_card_suspend(pa_card *c, pa_bool_t suspend); +int pa_card_suspend(pa_card *c, pa_bool_t suspend, pa_suspend_cause_t cause);  #endif diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index b5f7e7f5..e2c3c066 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -125,6 +125,8 @@ static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa  static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);  static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);  static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);  /* A method table for all available commands */ @@ -175,11 +177,13 @@ static const struct command commands[] = {      { "suspend-sink",            pa_cli_command_suspend_sink,       "Suspend sink (args: index|name, bool)", 3},      { "suspend-source",          pa_cli_command_suspend_source,     "Suspend source (args: index|name, bool)", 3},      { "suspend",                 pa_cli_command_suspend,            "Suspend all sinks and all sources (args: bool)", 2}, -    { "set-card-profile",        pa_cli_command_card_profile,       "Change the profile of a card (aargs: index, name)", 3}, +    { "set-card-profile",        pa_cli_command_card_profile,       "Change the profile of a card (args: index, name)", 3}, +    { "set-sink-port",           pa_cli_command_sink_port,          "Change the port of a sink (args: index, name)", 3}, +    { "set-source-port",         pa_cli_command_source_port,        "Change the port of a source (args: index, name)", 3},      { "set-log-level",           pa_cli_command_log_level,          "Change the log level (args: numeric level)", 2},      { "set-log-meta",            pa_cli_command_log_meta,           "Show source code location in log messages (args: bool)", 2},      { "set-log-time",            pa_cli_command_log_time,           "Show timestamps in log messages (args: bool)", 2}, -    { "set-log-backtrace",       pa_cli_command_log_backtrace,      "Show bakctrace in log messages (args: frames)", 2}, +    { "set-log-backtrace",       pa_cli_command_log_backtrace,      "Show backtrace in log messages (args: frames)", 2},      { NULL, NULL, NULL, 0 }  }; @@ -483,6 +487,8 @@ static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf,              if (i->usage)                  pa_strbuf_printf(buf, "Usage: %s\n", i->usage);              pa_strbuf_printf(buf, "Load Once: %s\n", pa_yes_no(i->load_once)); +            if (i->deprecated) +                pa_strbuf_printf(buf, "Warning, deprecated: %s\n", i->deprecated);          }          pa_modinfo_free(i); @@ -524,7 +530,7 @@ static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *bu      }      pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume); -    pa_sink_set_volume(sink, &cvolume, TRUE, TRUE); +    pa_sink_set_volume(sink, &cvolume, TRUE, TRUE, TRUE, TRUE);      return 0;  } @@ -566,7 +572,7 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb      }      pa_cvolume_set(&cvolume, si->sample_spec.channels, volume); -    pa_sink_input_set_volume(si, &cvolume, TRUE); +    pa_sink_input_set_volume(si, &cvolume, TRUE, TRUE);      return 0;  } @@ -602,7 +608,7 @@ static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *      }      pa_cvolume_set(&cvolume, source->sample_spec.channels, volume); -    pa_source_set_volume(source, &cvolume); +    pa_source_set_volume(source, &cvolume, TRUE);      return 0;  } @@ -636,7 +642,7 @@ static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf,          return -1;      } -    pa_sink_set_mute(sink, mute); +    pa_sink_set_mute(sink, mute, TRUE);      return 0;  } @@ -670,7 +676,7 @@ static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *bu          return -1;      } -    pa_source_set_mute(source, mute); +    pa_source_set_mute(source, mute, TRUE);      return 0;  } @@ -699,7 +705,10 @@ static int pa_cli_command_update_sink_proplist(pa_core *c, pa_tokenizer *t, pa_s          return -1;      } -    p = pa_proplist_from_string(s); +    if (!(p = pa_proplist_from_string(s))) { +        pa_strbuf_puts(buf, "Failed to parse proplist.\n"); +        return -1; +    }      pa_sink_update_proplist(sink, PA_UPDATE_REPLACE, p); @@ -733,7 +742,10 @@ static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa          return -1;      } -    p = pa_proplist_from_string(s); +    if (!(p = pa_proplist_from_string(s))) { +        pa_strbuf_puts(buf, "Failed to parse proplist.\n"); +        return -1; +    }      pa_source_update_proplist(source, PA_UPDATE_REPLACE, p); @@ -773,7 +785,10 @@ static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t          return -1;      } -    p = pa_proplist_from_string(s); +    if (!(p = pa_proplist_from_string(s))) { +        pa_strbuf_puts(buf, "Failed to parse proplist.\n"); +        return -1; +    }      pa_sink_input_update_proplist(si, PA_UPDATE_REPLACE, p); @@ -813,7 +828,10 @@ static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer          return -1;      } -    p = pa_proplist_from_string(s); +    if (!(p = pa_proplist_from_string(s))) { +        pa_strbuf_puts(buf, "Failed to parse proplist.\n"); +        return -1; +    }      pa_source_output_update_proplist(so, PA_UPDATE_REPLACE, p); @@ -1264,7 +1282,7 @@ static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *b          return -1;      } -    if ((r = pa_sink_suspend(sink, suspend)) < 0) +    if ((r = pa_sink_suspend(sink, suspend, PA_SUSPEND_USER)) < 0)          pa_strbuf_printf(buf, "Failed to resume/suspend sink: %s\n", pa_strerror(r));      return 0; @@ -1300,7 +1318,7 @@ static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf          return -1;      } -    if ((r = pa_source_suspend(source, suspend)) < 0) +    if ((r = pa_source_suspend(source, suspend, PA_SUSPEND_USER)) < 0)          pa_strbuf_printf(buf, "Failed to resume/suspend source: %s\n", pa_strerror(r));      return 0; @@ -1325,10 +1343,10 @@ static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, p          return -1;      } -    if ((r = pa_sink_suspend_all(c, suspend)) < 0) +    if ((r = pa_sink_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0)          pa_strbuf_printf(buf, "Failed to resume/suspend all sinks: %s\n", pa_strerror(r)); -    if ((r = pa_source_suspend_all(c, suspend)) < 0) +    if ((r = pa_source_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0)          pa_strbuf_printf(buf, "Failed to resume/suspend all sources: %s\n", pa_strerror(r));      return 0; @@ -1454,7 +1472,7 @@ static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *b          return -1;      } -    if (pa_card_set_profile(card, p) < 0) { +    if (pa_card_set_profile(card, p, TRUE) < 0) {          pa_strbuf_printf(buf, "Failed to set card profile to '%s'.\n", p);          return -1;      } @@ -1462,6 +1480,70 @@ static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *b      return 0;  } +static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { +    const char *n, *p; +    pa_sink *sink; + +    pa_core_assert_ref(c); +    pa_assert(t); +    pa_assert(buf); +    pa_assert(fail); + +    if (!(n = pa_tokenizer_get(t, 1))) { +        pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); +        return -1; +    } + +    if (!(p = pa_tokenizer_get(t, 2))) { +        pa_strbuf_puts(buf, "You need to specify a profile by its name.\n"); +        return -1; +    } + +    if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) { +        pa_strbuf_puts(buf, "No sink found by this name or index.\n"); +        return -1; +    } + +    if (pa_sink_set_port(sink, p, TRUE) < 0) { +        pa_strbuf_printf(buf, "Failed to set sink port to '%s'.\n", p); +        return -1; +    } + +    return 0; +} + +static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { +    const char *n, *p; +    pa_source *source; + +    pa_core_assert_ref(c); +    pa_assert(t); +    pa_assert(buf); +    pa_assert(fail); + +    if (!(n = pa_tokenizer_get(t, 1))) { +        pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); +        return -1; +    } + +    if (!(p = pa_tokenizer_get(t, 2))) { +        pa_strbuf_puts(buf, "You need to specify a profile by its name.\n"); +        return -1; +    } + +    if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) { +        pa_strbuf_puts(buf, "No source found by this name or index.\n"); +        return -1; +    } + +    if (pa_source_set_port(source, p, TRUE) < 0) { +        pa_strbuf_printf(buf, "Failed to set source port to '%s'.\n", p); +        return -1; +    } + +    return 0; +} +  static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {      pa_module *m;      pa_sink *sink; @@ -1504,7 +1586,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b              nl = 1;          } -        pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, FALSE))); +        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-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));      } diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 76adc4dd..9395513d 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -139,11 +139,10 @@ char *pa_card_list_to_string(pa_core *c) {          if (card->profiles) {              pa_card_profile *p; -            void *state = NULL; +            void *state;              pa_strbuf_puts(s, "\tprofiles:\n"); - -            while ((p = pa_hashmap_iterate(card->profiles, &state, NULL))) +            PA_HASHMAP_FOREACH(p, card->profiles, state)                  pa_strbuf_printf(s, "\t\t%s: %s (priority %u)\n", p->name, p->description, p->priority);          } @@ -220,27 +219,25 @@ char *pa_sink_list_to_string(pa_core *c) {              v[PA_VOLUME_SNPRINT_MAX],              vdb[PA_SW_VOLUME_SNPRINT_DB_MAX],              cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; -        pa_usec_t min_latency, max_latency;          const char *cmn;          cmn = pa_channel_map_to_pretty_name(&sink->channel_map); -        pa_sink_get_latency_range(sink, &min_latency, &max_latency);          pa_strbuf_printf(              s,              "  %c index: %u\n"              "\tname: <%s>\n"              "\tdriver: <%s>\n" -            "\tflags: %s%s%s%s%s%s%s\n" +            "\tflags: %s%s%s%s%s%s%s%s\n"              "\tstate: %s\n" +            "\tsuspend cause: %s%s%s%s\n"              "\tvolume: %s%s%s\n"              "\t        balance %0.2f\n"              "\tbase volume: %s%s%s\n"              "\tvolume steps: %u\n"              "\tmuted: %s\n"              "\tcurrent latency: %0.2f ms\n" -            "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"              "\tmax request: %lu KiB\n"              "\tmax rewind: %lu KiB\n"              "\tmonitor source: %u\n" @@ -258,21 +255,23 @@ char *pa_sink_list_to_string(pa_core *c) {              sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",              sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",              sink->flags & PA_SINK_LATENCY ? "LATENCY " : "", -            sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME" : "", +            sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "", +            sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",              sink_state_to_string(pa_sink_get_state(sink)), -            pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE)), +            sink->suspend_cause & PA_SUSPEND_USER ? "USER " : "", +            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->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)) : "", -            pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE), &sink->channel_map), +            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),              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) : "",              sink->n_volume_steps,              pa_yes_no(pa_sink_get_mute(sink, FALSE)),              (double) pa_sink_get_latency(sink) / (double) PA_USEC_PER_MSEC, -            (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC, -            (double) min_latency / PA_USEC_PER_MSEC, -            (double) max_latency / PA_USEC_PER_MSEC,              (unsigned long) pa_sink_get_max_request(sink) / 1024,              (unsigned long) pa_sink_get_max_rewind(sink) / 1024,              sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, @@ -283,6 +282,22 @@ char *pa_sink_list_to_string(pa_core *c) {              pa_sink_used_by(sink),              pa_sink_linked_by(sink)); +        if (sink->flags & PA_SINK_DYNAMIC_LATENCY) { +            pa_usec_t min_latency, max_latency; +            pa_sink_get_latency_range(sink, &min_latency, &max_latency); + +            pa_strbuf_printf( +                    s, +                    "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n", +                    (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC, +                    (double) min_latency / PA_USEC_PER_MSEC, +                    (double) max_latency / PA_USEC_PER_MSEC); +        } else +            pa_strbuf_printf( +                    s, +                    "\tfixed latency: %0.2f ms\n", +                    (double) pa_sink_get_requested_latency(sink) / PA_USEC_PER_MSEC); +          if (sink->card)              pa_strbuf_printf(s, "\tcard: %u <%s>\n", sink->card->index, sink->card->name);          if (sink->module) @@ -291,6 +306,22 @@ char *pa_sink_list_to_string(pa_core *c) {          t = pa_proplist_to_string_sep(sink->proplist, "\n\t\t");          pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);          pa_xfree(t); + +        if (sink->ports) { +            pa_device_port *p; +            void *state; + +            pa_strbuf_puts(s, "\tports:\n"); +            PA_HASHMAP_FOREACH(p, sink->ports, state) +                pa_strbuf_printf(s, "\t\t%s: %s (priority %u)\n", p->name, p->description, p->priority); +        } + + +        if (sink->active_port) +            pa_strbuf_printf( +                    s, +                    "\tactive port: <%s>\n", +                    sink->active_port->name);      }      return pa_strbuf_tostring_free(s); @@ -313,27 +344,24 @@ char *pa_source_list_to_string(pa_core *c) {              v[PA_VOLUME_SNPRINT_MAX],              vdb[PA_SW_VOLUME_SNPRINT_DB_MAX],              cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; -        pa_usec_t min_latency, max_latency;          const char *cmn;          cmn = pa_channel_map_to_pretty_name(&source->channel_map); -        pa_source_get_latency_range(source, &min_latency, &max_latency); -          pa_strbuf_printf(              s,              "  %c index: %u\n"              "\tname: <%s>\n"              "\tdriver: <%s>\n" -            "\tflags: %s%s%s%s%s%s\n" +            "\tflags: %s%s%s%s%s%s%s\n"              "\tstate: %s\n" +            "\tsuspend cause: %s%s%s%s\n"              "\tvolume: %s%s%s\n"              "\t        balance %0.2f\n"              "\tbase volume: %s%s%s\n"              "\tvolume steps: %u\n"              "\tmuted: %s\n"              "\tcurrent latency: %0.2f ms\n" -            "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"              "\tmax rewind: %lu KiB\n"              "\tsample spec: %s\n"              "\tchannel map: %s%s%s\n" @@ -349,7 +377,12 @@ char *pa_source_list_to_string(pa_core *c) {              source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",              source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",              source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", +            source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",              source_state_to_string(pa_source_get_state(source)), +            source->suspend_cause & PA_SUSPEND_USER ? "USER " : "", +            source->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", +            source->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", +            source->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "",              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)) : "", @@ -360,9 +393,6 @@ char *pa_source_list_to_string(pa_core *c) {              source->n_volume_steps,              pa_yes_no(pa_source_get_mute(source, FALSE)),              (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC, -            (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, -            (double) min_latency / PA_USEC_PER_MSEC, -            (double) max_latency / PA_USEC_PER_MSEC,              (unsigned long) pa_source_get_max_rewind(source) / 1024,              pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec),              pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map), @@ -371,6 +401,22 @@ char *pa_source_list_to_string(pa_core *c) {              pa_source_used_by(source),              pa_source_linked_by(source)); +        if (source->flags & PA_SOURCE_DYNAMIC_LATENCY) { +            pa_usec_t min_latency, max_latency; +            pa_source_get_latency_range(source, &min_latency, &max_latency); + +            pa_strbuf_printf( +                    s, +                    "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n", +                    (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, +                    (double) min_latency / PA_USEC_PER_MSEC, +                    (double) max_latency / PA_USEC_PER_MSEC); +        } else +            pa_strbuf_printf( +                    s, +                    "\tfixed latency: %0.2f ms\n", +                    (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC); +          if (source->monitor_of)              pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index);          if (source->card) @@ -381,6 +427,21 @@ char *pa_source_list_to_string(pa_core *c) {          t = pa_proplist_to_string_sep(source->proplist, "\n\t\t");          pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);          pa_xfree(t); + +        if (source->ports) { +            pa_device_port *p; +            void *state; + +            pa_strbuf_puts(s, "\tports:\n"); +            PA_HASHMAP_FOREACH(p, source->ports, state) +                pa_strbuf_printf(s, "\t\t%s: %s (priority %u)\n", p->name, p->description, p->priority); +        } + +        if (source->active_port) +            pa_strbuf_printf( +                    s, +                    "\tactive port: <%s>\n", +                    source->active_port->name);      }      return pa_strbuf_tostring_free(s); @@ -486,6 +547,9 @@ char *pa_sink_input_list_to_string(pa_core *c) {          char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];          pa_usec_t cl;          const char *cmn; +        pa_cvolume v; + +        pa_sink_input_get_volume(i, &v, TRUE);          cmn = pa_channel_map_to_pretty_name(&i->channel_map); @@ -526,9 +590,9 @@ char *pa_sink_input_list_to_string(pa_core *c) {              i->flags & PA_SINK_INPUT_FAIL_ON_SUSPEND ? "FAIL_ON_SUSPEND " : "",              state_table[pa_sink_input_get_state(i)],              i->sink->index, i->sink->name, -            pa_cvolume_snprint(cv, sizeof(cv), pa_sink_input_get_volume(i)), -            pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_input_get_volume(i)), -            pa_cvolume_get_balance(pa_sink_input_get_volume(i), &i->channel_map), +            pa_cvolume_snprint(cv, sizeof(cv), &v), +            pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &v), +            pa_cvolume_get_balance(&v, &i->channel_map),              pa_yes_no(pa_sink_input_get_mute(i)),              (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC,              clt, diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index a784f583..54514e7f 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -59,6 +59,8 @@ struct pa_cli {      pa_bool_t fail, kill_requested;      int defer_kill; + +    char *last_line;  };  static void line_callback(pa_ioline *line, const char *s, void *userdata); @@ -101,6 +103,8 @@ pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) {      c->fail = c->kill_requested = FALSE;      c->defer_kill = 0; +    c->last_line = NULL; +      return c;  } @@ -110,6 +114,7 @@ void pa_cli_free(pa_cli *c) {      pa_ioline_close(c->line);      pa_ioline_unref(c->line);      pa_client_free(c->client); +    pa_xfree(c->last_line);      pa_xfree(c);  } @@ -144,6 +149,14 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {          return;      } +    /* Magic command, like they had in AT Hayes Modems! Those were the good days! */ +    if (pa_streq(s, "/")) +        s = c->last_line; +    else if (s[0]) { +        pa_xfree(c->last_line); +        c->last_line = pa_xstrdup(s); +    } +      pa_assert_se(buf = pa_strbuf_new());      c->defer_kill++;      pa_cli_command_execute_line(c->core, s, buf, &c->fail); diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c index a6eb581c..2dc9a223 100644 --- a/src/pulsecore/conf-parser.c +++ b/src/pulsecore/conf-parser.c @@ -40,19 +40,35 @@  #define COMMENTS "#;\n"  /* Run the user supplied parser for an assignment */ -static int next_assignment(const char *filename, unsigned line, const char *section, const pa_config_item *t, const char *lvalue, const char *rvalue, void *userdata) { +static int next_assignment( +        const char *filename, +        unsigned line, +        const char *section, +        const pa_config_item *t, +        const char *lvalue, +        const char *rvalue, +        void *userdata) { +      pa_assert(filename);      pa_assert(t);      pa_assert(lvalue);      pa_assert(rvalue); -    for (; t->parse; t++) -        if (!t->lvalue || -            (pa_streq(lvalue, t->lvalue) && -             ((!section && !t->section) || pa_streq(section, t->section)))) -            return t->parse(filename, line, section, lvalue, rvalue, t->data, userdata); +    for (; t->parse; t++) { + +        if (t->lvalue && !pa_streq(lvalue, t->lvalue)) +            continue; + +        if (t->section && !section) +            continue; + +        if (t->section && !pa_streq(section, t->section)) +            continue; + +        return t->parse(filename, line, section, lvalue, rvalue, t->data, userdata); +    } -    pa_log("[%s:%u] Unknown lvalue '%s' in section '%s'.", filename, line, lvalue, pa_strnull(section)); +    pa_log("[%s:%u] Unknown lvalue '%s' in section '%s'.", filename, line, lvalue, pa_strna(section));      return -1;  } @@ -96,6 +112,25 @@ static int parse_line(const char *filename, unsigned line, char **section, const      if (!*b)          return 0; +    if (pa_startswith(b, ".include ")) { +        char *path, *fn; +        int r; + +        fn = strip(b+9); +        if (!pa_is_path_absolute(fn)) { +            const char *k; +            if ((k = strrchr(filename, '/'))) { +                char *dir = pa_xstrndup(filename, k-filename); +                fn = path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir, fn); +                pa_xfree(dir); +            } +        } + +        r = pa_config_parse(fn, NULL, t, userdata); +        pa_xfree(path); +        return r; +    } +      if (*b == '[') {          size_t k; @@ -135,6 +170,7 @@ int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void      if (!f && !(f = fopen(filename, "r"))) {          if (errno == ENOENT) { +            pa_log_debug("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno));              r = 0;              goto finish;          } diff --git a/src/pulsecore/core-rtclock.c b/src/pulsecore/core-rtclock.c new file mode 100644 index 00000000..3bc9e830 --- /dev/null +++ b/src/pulsecore/core-rtclock.c @@ -0,0 +1,199 @@ +/*** +  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 +  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 <stddef.h> +#include <time.h> +#include <sys/time.h> +#include <errno.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <pulse/timeval.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> + +#include "core-rtclock.h" + +pa_usec_t pa_rtclock_age(const struct timeval *tv) { +    struct timeval now; +    pa_assert(tv); + +    return pa_timeval_diff(pa_rtclock_get(&now), tv); +} + +struct timeval *pa_rtclock_get(struct timeval *tv) { +#ifdef HAVE_CLOCK_GETTIME +    struct timespec ts; + +#ifdef CLOCK_MONOTONIC +    /* No locking or atomic ops for no_monotonic here */ +    static pa_bool_t no_monotonic = FALSE; + +    if (!no_monotonic) +        if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) +            no_monotonic = TRUE; + +    if (no_monotonic) +#endif +        pa_assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + +    pa_assert(tv); + +    tv->tv_sec = ts.tv_sec; +    tv->tv_usec = ts.tv_nsec / PA_NSEC_PER_USEC; + +    return tv; + +#else /* HAVE_CLOCK_GETTIME */ + +    return pa_gettimeofday(tv); + +#endif +} + +pa_bool_t pa_rtclock_hrtimer(void) { +#ifdef 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 + +    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 */ + +    return FALSE; + +#endif +} + +#define TIMER_SLACK_NS (int) ((500 * PA_NSEC_PER_USEC)) + +void pa_rtclock_hrtimer_enable(void) { +#ifdef PR_SET_TIMERSLACK +    int slack_ns; + +    if ((slack_ns = prctl(PR_GET_TIMERSLACK, 0, 0, 0, 0)) < 0) { +        pa_log_info("PR_GET_TIMERSLACK/PR_SET_TIMERSLACK not supported."); +        return; +    } + +    pa_log_debug("Timer slack is set to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC)); + +    if (slack_ns > TIMER_SLACK_NS) { +        slack_ns = TIMER_SLACK_NS; + +        pa_log_debug("Setting timer slack to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC)); + +        if (prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0) < 0) { +            pa_log_warn("PR_SET_TIMERSLACK failed: %s", pa_cstrerror(errno)); +            return; +        } +    } + +#endif +} + +struct timeval* pa_rtclock_from_wallclock(struct timeval *tv) { + +#ifdef HAVE_CLOCK_GETTIME +    struct timeval wc_now, rt_now; + +    pa_gettimeofday(&wc_now); +    pa_rtclock_get(&rt_now); + +    pa_assert(tv); + +    if (pa_timeval_cmp(&wc_now, tv) < 0) +        pa_timeval_add(&rt_now, pa_timeval_diff(tv, &wc_now)); +    else +        pa_timeval_sub(&rt_now, pa_timeval_diff(&wc_now, tv)); + +    *tv = rt_now; +#endif + +    return tv; +} + +struct timespec *pa_timespec_store(struct timespec *ts, pa_usec_t v)  { +    pa_assert(ts); + +    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; +} + +pa_usec_t pa_timespec_load(const struct timespec *ts) { +    pa_assert(ts); + +    return +        (pa_usec_t) ts->tv_sec * PA_USEC_PER_SEC + +        (pa_usec_t) ts->tv_nsec / PA_NSEC_PER_USEC; +} + + +static struct timeval* wallclock_from_rtclock(struct timeval *tv) { + +#ifdef HAVE_CLOCK_GETTIME +    struct timeval wc_now, rt_now; + +    pa_gettimeofday(&wc_now); +    pa_rtclock_get(&rt_now); + +    pa_assert(tv); + +    if (pa_timeval_cmp(&rt_now, tv) < 0) +        pa_timeval_add(&wc_now, pa_timeval_diff(tv, &rt_now)); +    else +        pa_timeval_sub(&wc_now, pa_timeval_diff(&rt_now, tv)); + +    *tv = wc_now; +#endif + +    return tv; +} + +struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, pa_bool_t rtclock) { +    pa_assert(tv); + +    if (v == PA_USEC_INVALID) +        return NULL; + +    pa_timeval_store(tv, v); + +    if (rtclock) +        tv->tv_usec |= PA_TIMEVAL_RTCLOCK; +    else +        wallclock_from_rtclock(tv); + +    return tv; +} diff --git a/src/pulsecore/core-rtclock.h b/src/pulsecore/core-rtclock.h new file mode 100644 index 00000000..152a3397 --- /dev/null +++ b/src/pulsecore/core-rtclock.h @@ -0,0 +1,51 @@ +#ifndef foopulsertclockhfoo +#define foopulsertclockhfoo + +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006 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 +  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 <pulsecore/macro.h> +#include <pulse/sample.h> + +struct timeval; + +/* Something like pulse/timeval.h but based on CLOCK_MONOTONIC */ + +struct timeval *pa_rtclock_get(struct timeval *ts); + +pa_usec_t pa_rtclock_age(const struct timeval *tv); +pa_bool_t pa_rtclock_hrtimer(void); +void pa_rtclock_hrtimer_enable(void); + +/* timer with a resolution better than this are considered high-resolution */ +#define PA_HRTIMER_THRESHOLD_USEC 10 + +/* bit to set in tv.tv_usec to mark that the timeval is in monotonic time */ +#define PA_TIMEVAL_RTCLOCK ((time_t) (1LU << 30)) + +struct timeval* pa_rtclock_from_wallclock(struct timeval *tv); + +struct timespec *pa_timespec_store(struct timespec *ts, pa_usec_t v); +pa_usec_t pa_timespec_load(const struct timespec *ts); + +struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, pa_bool_t rtclock); + +#endif diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index 34d60a8f..4c5a4b26 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -47,6 +47,7 @@  #include <pulse/util.h>  #include <pulse/volume.h>  #include <pulse/xmalloc.h> +#include <pulse/rtclock.h>  #include <pulsecore/sink-input.h>  #include <pulsecore/sample-util.h> @@ -54,6 +55,7 @@  #include <pulsecore/core-subscribe.h>  #include <pulsecore/namereg.h>  #include <pulsecore/sound-file.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/log.h>  #include <pulsecore/core-error.h> @@ -61,11 +63,10 @@  #include "core-scache.h" -#define UNLOAD_POLL_TIME 60 +#define UNLOAD_POLL_TIME (60 * PA_USEC_PER_SEC) -static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) { +static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      pa_core *c = userdata; -    struct timeval ntv;      pa_assert(c);      pa_assert(c->mainloop == m); @@ -73,9 +74,7 @@ static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, const struct t      pa_scache_unload_unused(c); -    pa_gettimeofday(&ntv); -    ntv.tv_sec += UNLOAD_POLL_TIME; -    m->time_restart(e, &ntv); +    pa_core_rttime_restart(c, e, pa_rtclock_now() + UNLOAD_POLL_TIME);  }  static void free_entry(pa_scache_entry *e) { @@ -219,11 +218,14 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3      pa_assert(name);      pa_assert(filename); -    if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0) -        return -1; -      p = pa_proplist_new();      pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename); + +    if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) { +        pa_proplist_free(p); +        return -1; +    } +      r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx);      pa_memblock_unref(chunk.memblock);      pa_proplist_free(p); @@ -253,12 +255,8 @@ int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename,      pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename); -    if (!c->scache_auto_unload_event) { -        struct timeval ntv; -        pa_gettimeofday(&ntv); -        ntv.tv_sec += UNLOAD_POLL_TIME; -        c->scache_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c); -    } +    if (!c->scache_auto_unload_event) +        c->scache_auto_unload_event = pa_core_rttime_new(c, pa_rtclock_now() + UNLOAD_POLL_TIME, timeout_callback, c);      if (idx)          *idx = e->index; @@ -311,11 +309,14 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t      if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))          return -1; +    merged = pa_proplist_new(); +    pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name); +      if (e->lazy && !e->memchunk.memblock) {          pa_channel_map old_channel_map = e->channel_map; -        if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk) < 0) -            return -1; +        if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0) +            goto fail;          pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); @@ -328,7 +329,7 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t      }      if (!e->memchunk.memblock) -        return -1; +        goto fail;      pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); @@ -344,17 +345,13 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t      else          pass_volume = FALSE; -    merged = pa_proplist_new(); -    pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name);      pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist);      if (p)          pa_proplist_update(merged, PA_UPDATE_REPLACE, p); -    if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) { -        pa_proplist_free(merged); -        return -1; -    } +    if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) +        goto fail;      pa_proplist_free(merged); @@ -362,6 +359,10 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t          time(&e->last_used_time);      return 0; + +fail: +    pa_proplist_free(merged); +    return -1;  }  int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index ba3a9b21..ec83fffd 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -43,6 +43,7 @@  #include <regex.h>  #include <langinfo.h>  #include <sys/utsname.h> +#include <sys/socket.h>  #ifdef HAVE_STRTOF_L  #include <locale.h> @@ -50,6 +51,10 @@  #ifdef HAVE_SCHED_H  #include <sched.h> + +#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK) +#define SCHED_RESET_ON_FORK 0x40000000 +#endif  #endif  #ifdef HAVE_SYS_RESOURCE_H @@ -92,6 +97,10 @@  #include <xlocale.h>  #endif +#ifdef HAVE_DBUS +#include "rtkit.h" +#endif +  #include <pulse/xmalloc.h>  #include <pulse/util.h>  #include <pulse/utf8.h> @@ -296,7 +305,15 @@ ssize_t pa_read(int fd, void *buf, size_t count, int *type) {  #endif -    return read(fd, buf, count); +    for (;;) { +        ssize_t r; + +        if ((r = read(fd, buf, count)) < 0) +            if (errno == EINTR) +                continue; + +        return r; +    }  }  /** Similar to pa_read(), but handles writes */ @@ -305,8 +322,17 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) {      if (!type || *type == 0) {          ssize_t r; -        if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0) +        for (;;) { +            if ((r = send(fd, buf, count, MSG_NOSIGNAL)) < 0) { + +                if (errno == EINTR) +                    continue; + +                break; +            } +              return r; +        }  #ifdef OS_IS_WIN32          if (WSAGetLastError() != WSAENOTSOCK) { @@ -322,11 +348,19 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) {              *type = 1;      } -    return write(fd, buf, count); +    for (;;) { +        ssize_t r; + +        if ((r = write(fd, buf, count)) < 0) +            if (errno == EINTR) +                continue; + +        return r; +    }  }  /** Calls read() in a loop. Makes sure that as much as 'size' bytes, - * unless EOF is reached or an error occured */ + * unless EOF is reached or an error occurred */  ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) {      ssize_t ret = 0;      int _type; @@ -407,11 +441,11 @@ int pa_close(int fd) {      for (;;) {          int r; -        if ((r = close(fd)) >= 0) -            return r; +        if ((r = close(fd)) < 0) +            if (errno == EINTR) +                continue; -        if (errno != EINTR) -            return r; +        return r;      }  } @@ -527,127 +561,121 @@ char *pa_strlcpy(char *b, const char *s, size_t l) {      return b;  } -/* Make the current thread a realtime thread, and acquire the highest - * rtprio we can get that is less or equal the specified parameter. If - * the thread is already realtime, don't do anything. */ -int pa_make_realtime(int rtprio) { - -#ifdef _POSIX_PRIORITY_SCHEDULING +static int set_scheduler(int rtprio) {      struct sched_param sp; -    int r, policy; +    int r; +#ifdef HAVE_DBUS +    DBusError error; +    DBusConnection *bus; -    memset(&sp, 0, sizeof(sp)); -    policy = 0; +    dbus_error_init(&error); +#endif -    if ((r = pthread_getschedparam(pthread_self(), &policy, &sp)) != 0) { -        pa_log("pthread_getschedgetparam(): %s", pa_cstrerror(r)); -        return -1; +    pa_zero(sp); +    sp.sched_priority = rtprio; + +#ifdef SCHED_RESET_ON_FORK +    if ((r = 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 (policy == SCHED_FIFO && sp.sched_priority >= rtprio) { -        pa_log_info("Thread already being scheduled with SCHED_FIFO with priority %i.", sp.sched_priority); +    if ((r = pthread_setschedparam(pthread_self(), SCHED_RR, &sp)) == 0) { +        pa_log_debug("SCHED_RR worked.");          return 0;      } -    sp.sched_priority = rtprio; -    if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) != 0) { +#ifdef HAVE_DBUS +    /* Try to talk to RealtimeKit */ -        while (sp.sched_priority > 1) { -            sp.sched_priority --; +    if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) { +        pa_log("Failed to connect to system bus: %s\n", error.message); +        dbus_error_free(&error); +        errno = -EIO; +        return -1; +    } -            if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) == 0) { -                pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i, which is lower than the requested %i.", sp.sched_priority, rtprio); -                return 0; -            } -        } +    r = rtkit_make_realtime(bus, 0, rtprio); +    dbus_connection_unref(bus); -        pa_log_warn("pthread_setschedparam(): %s", pa_cstrerror(r)); -        return -1; +    if (r >= 0) { +        pa_log_debug("RealtimeKit worked."); +        return 0;      } -    pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i.", sp.sched_priority); -    return 0; +    errno = -r;  #else +    errno = r; +#endif -    errno = ENOTSUP;      return -1; -#endif  } -/* This is merely used for giving the user a hint. This is not correct - * for anything security related */ -pa_bool_t pa_can_realtime(void) { - -    if (geteuid() == 0) -        return TRUE; +/* Make the current thread a realtime thread, and acquire the highest + * rtprio we can get that is less or equal the specified parameter. If + * the thread is already realtime, don't do anything. */ +int pa_make_realtime(int rtprio) { -#if defined(HAVE_SYS_RESOURCE_H) && defined(RLIMIT_RTPRIO) -    { -        struct rlimit rl; +#ifdef _POSIX_PRIORITY_SCHEDULING +    int p; -        if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0) -            if (rl.rlim_cur > 0 || rl.rlim_cur == RLIM_INFINITY) -                return TRUE; +    if (set_scheduler(rtprio) >= 0) { +        pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i.", rtprio); +        return 0;      } -#endif - -#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_SYS_NICE) -    { -        cap_t cap; -        if ((cap = cap_get_proc())) { -            cap_flag_value_t flag = CAP_CLEAR; +    for (p = rtprio-1; p >= 1; p--) +        if (set_scheduler(p)) { +            pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio); +            return 0; +        } -            if (cap_get_flag(cap, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0) -                if (flag == CAP_SET) { -                    cap_free(cap); -                    return TRUE; -                } +    pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno)); +    return -1; +#else -            cap_free(cap); -        } -    } +    errno = ENOTSUP; +    return -1;  #endif - -    return FALSE;  } -/* This is merely used for giving the user a hint. This is not correct - * for anything security related */ -pa_bool_t pa_can_high_priority(void) { - -    if (geteuid() == 0) -        return TRUE; +static int set_nice(int nice_level) { +#ifdef HAVE_DBUS +    DBusError error; +    DBusConnection *bus; +    int r; -#if defined(HAVE_SYS_RESOURCE_H) && defined(RLIMIT_RTPRIO) -    { -        struct rlimit rl; +    dbus_error_init(&error); +#endif -        if (getrlimit(RLIMIT_NICE, &rl) >= 0) -            if (rl.rlim_cur >= 21 || rl.rlim_cur == RLIM_INFINITY) -                return TRUE; +    if (setpriority(PRIO_PROCESS, 0, nice_level) >= 0) { +        pa_log_debug("setpriority() worked."); +        return 0;      } -#endif -#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_SYS_NICE) -    { -        cap_t cap; +#ifdef HAVE_DBUS +    /* Try to talk to RealtimeKit */ -        if ((cap = cap_get_proc())) { -            cap_flag_value_t flag = CAP_CLEAR; +    if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) { +        pa_log("Failed to connect to system bus: %s\n", error.message); +        dbus_error_free(&error); +        errno = -EIO; +        return -1; +    } -            if (cap_get_flag(cap, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0) -                if (flag == CAP_SET) { -                    cap_free(cap); -                    return TRUE; -                } +    r = rtkit_make_high_priority(bus, 0, nice_level); +    dbus_connection_unref(bus); -            cap_free(cap); -        } +    if (r >= 0) { +        pa_log_debug("RealtimeKit worked."); +        return 0;      } + +    errno = -r;  #endif -    return FALSE; +    return -1;  }  /* Raise the priority of the current process as much as possible that @@ -655,22 +683,21 @@ pa_bool_t pa_can_high_priority(void) {  int pa_raise_priority(int nice_level) {  #ifdef HAVE_SYS_RESOURCE_H -    if (setpriority(PRIO_PROCESS, 0, nice_level) < 0) { -        int n; +    int n; -        for (n = nice_level+1; n < 0; n++) { +    if (set_nice(nice_level) >= 0) { +        pa_log_info("Successfully gained nice level %i.", nice_level); +        return 0; +    } -            if (setpriority(PRIO_PROCESS, 0, n) == 0) { -                pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level); -                return 0; -            } +    for (n = nice_level+1; n < 0; n++) +        if (set_nice(n) > 0) { +            pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level); +            return 0;          } -        pa_log_warn("setpriority(): %s", pa_cstrerror(errno)); -        return -1; -    } - -    pa_log_info("Successfully gained nice level %i.", nice_level); +    pa_log_info("Failed to acquire high-priority scheduling: %s", pa_cstrerror(errno)); +    return -1;  #endif  #ifdef OS_IS_WIN32 @@ -678,9 +705,10 @@ int pa_raise_priority(int nice_level) {          if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {              pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError());              errno = EPERM; -            return .-1; -        } else -            pa_log_info("Successfully gained high priority class."); +            return -1; +        } + +        pa_log_info("Successfully gained high priority class.");      }  #endif @@ -695,8 +723,8 @@ void pa_reset_priority(void) {      setpriority(PRIO_PROCESS, 0, 0); -    memset(&sp, 0, sizeof(sp)); -    pa_assert_se(pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp) == 0); +    pa_zero(sp); +    pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp);  #endif  #ifdef OS_IS_WIN32 @@ -1166,22 +1194,22 @@ int pa_check_in_group(gid_t g) {    (advisory on UNIX, mandatory on Windows) */  int pa_lock_fd(int fd, int b) {  #ifdef F_SETLKW -    struct flock flock; +    struct flock f_lock;      /* Try a R/W lock first */ -    flock.l_type = (short) (b ? F_WRLCK : F_UNLCK); -    flock.l_whence = SEEK_SET; -    flock.l_start = 0; -    flock.l_len = 0; +    f_lock.l_type = (short) (b ? F_WRLCK : F_UNLCK); +    f_lock.l_whence = SEEK_SET; +    f_lock.l_start = 0; +    f_lock.l_len = 0; -    if (fcntl(fd, F_SETLKW, &flock) >= 0) +    if (fcntl(fd, F_SETLKW, &f_lock) >= 0)          return 0;      /* Perhaps the file descriptor qas opened for read only, than try again with a read lock. */      if (b && errno == EBADF) { -        flock.l_type = F_RDLCK; -        if (fcntl(fd, F_SETLKW, &flock) >= 0) +        f_lock.l_type = F_RDLCK; +        if (fcntl(fd, F_SETLKW, &f_lock) >= 0)              return 0;      } @@ -1242,7 +1270,7 @@ int pa_lock_lockfile(const char *fn) {              goto fail;          } -        /* Check wheter the file has been removed meanwhile. When yes, +        /* Check whether the file has been removed meanwhile. When yes,           * restart this loop, otherwise, we're done */          if (st.st_nlink >= 1)              break; @@ -2210,11 +2238,10 @@ int pa_close_all(int except_fd, ...) {  int pa_close_allv(const int except_fds[]) {      struct rlimit rl; -    int fd; -    int saved_errno; +    int maxfd, fd;  #ifdef __linux__ - +    int saved_errno;      DIR *d;      if ((d = opendir("/proc/self/fd"))) { @@ -2277,10 +2304,12 @@ int pa_close_allv(const int except_fds[]) {  #endif -    if (getrlimit(RLIMIT_NOFILE, &rl) < 0) -        return -1; +    if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) +        maxfd = (int) rl.rlim_max; +    else +        maxfd = sysconf(_SC_OPEN_MAX); -    for (fd = 3; fd < (int) rl.rlim_max; fd++) { +    for (fd = 3; fd < maxfd; fd++) {          int i;          pa_bool_t found; @@ -2442,31 +2471,29 @@ pa_bool_t pa_in_system_mode(void) {      return !!atoi(e);  } -char *pa_machine_id(void) { -    FILE *f; -    size_t l; - -    /* The returned value is supposed be some kind of ascii identifier -     * that is unique and stable across reboots. */ - -    /* First we try the D-Bus UUID, which is the best option we have, -     * since it fits perfectly our needs and is not as volatile as the -     * hostname which might be set from dhcp. */ +char *pa_get_user_name_malloc(void) { +    ssize_t k; +    char *u; -    if ((f = fopen(PA_MACHINE_ID, "r"))) { -        char ln[34] = "", *r; +#ifdef _SC_LOGIN_NAME_MAX +    k = (ssize_t) sysconf(_SC_LOGIN_NAME_MAX); -        r = fgets(ln, sizeof(ln)-1, f); -        fclose(f); +    if (k <= 0) +#endif +        k = 32; -        pa_strip_nl(ln); +    u = pa_xnew(char, k+1); -        if (r && ln[0]) -            return pa_xstrdup(ln); +    if (!(pa_get_user_name(u, k))) { +        pa_xfree(u); +        return NULL;      } -    /* The we fall back to the host name. It supposed to be somewhat -     * unique, at least in a network, but may change. */ +    return u; +} + +char *pa_get_host_name_malloc(void) { +    size_t l;      l = 100;      for (;;) { @@ -2480,13 +2507,16 @@ char *pa_machine_id(void) {                  break;          } else if (strlen(c) < l-1) { +            char *u;              if (*c == 0) {                  pa_xfree(c);                  break;              } -            return c; +            u = pa_utf8_filter(c); +            pa_xfree(c); +            return u;          }          /* Hmm, the hostname is as long the space we offered the @@ -2497,11 +2527,49 @@ char *pa_machine_id(void) {          l *= 2;      } +    return NULL; +} + +char *pa_machine_id(void) { +    FILE *f; +    char *h; + +    /* The returned value is supposed be some kind of ascii identifier +     * that is unique and stable across reboots. */ + +    /* First we try the D-Bus UUID, which is the best option we have, +     * 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"))) { +        char ln[34] = "", *r; + +        r = fgets(ln, sizeof(ln)-1, f); +        fclose(f); + +        pa_strip_nl(ln); + +        if (r && ln[0]) +            return pa_utf8_filter(ln); +    } + +    if ((h = pa_get_host_name_malloc())) +        return h; +      /* If no hostname was set we use the POSIX hostid. It's usually -     * the IPv4 address.  Mit not be that stable. */ +     * the IPv4 address.  Might not be that stable. */      return pa_sprintf_malloc("%08lx", (unsigned long) gethostid);  } +char *pa_session_id(void) { +    const char *e; + +    if (!(e = getenv("XDG_SESSION_COOKIE"))) +        return NULL; + +    return pa_utf8_filter(e); +} +  char *pa_uname_string(void) {      struct utsname u; @@ -2608,7 +2676,7 @@ char *pa_unescape(char *p) {  }  char *pa_realpath(const char *path) { -    char *r, *t; +    char *t;      pa_assert(path);      /* We want only abolsute paths */ @@ -2617,18 +2685,89 @@ char *pa_realpath(const char *path) {          return NULL;      } -#ifndef __GLIBC__ +#if defined(__GLIBC__) || defined(__APPLE__) +    { +        char *r; + +        if (!(r = realpath(path, NULL))) +            return NULL; + +        /* We copy this here in case our pa_xmalloc() is not +         * implemented on top of libc malloc() */ +        t = pa_xstrdup(r); +        pa_xfree(r); +    } +#elif defined(PATH_MAX) +    { +        char *path_buf; +        path_buf = pa_xmalloc(PATH_MAX); + +        if (!(t = realpath(path, path_buf))) { +            pa_xfree(path_buf); +            return NULL; +        } +    } +#else  #error "It's not clear whether this system supports realpath(..., NULL) like GNU libc does. If it doesn't we need a private version of realpath() here."  #endif -    if (!(r = realpath(path, NULL))) -        return NULL; +    return t; +} + +void pa_disable_sigpipe(void) { + +#ifdef SIGPIPE +    struct sigaction sa; + +    pa_zero(sa); + +    if (sigaction(SIGPIPE, NULL, &sa) < 0) { +        pa_log("sigaction(): %s", pa_cstrerror(errno)); +        return; +    } -    /* We copy this here in case our pa_xmalloc() is not implemented -     * on top of libc malloc() */ -    t = pa_xstrdup(r); -    pa_xfree(r); +    sa.sa_handler = SIG_IGN; +    if (sigaction(SIGPIPE, &sa, NULL) < 0) { +        pa_log("sigaction(): %s", pa_cstrerror(errno)); +        return; +    } +#endif +} + +void pa_xfreev(void**a) { +    void **p; + +    if (!a) +        return; + +    for (p = a; *p; p++) +        pa_xfree(*p); + +    pa_xfree(a); +} + +char **pa_split_spaces_strv(const char *s) { +    char **t, *e; +    unsigned i = 0, n = 8; +    const char *state = NULL; + +    t = pa_xnew(char*, n); +    while ((e = pa_split_spaces(s, &state))) { +        t[i++] = e; + +        if (i >= n) { +            n *= 2; +            t = pa_xrenew(char*, t, n); +        } +    } + +    if (i <= 0) { +        pa_xfree(t); +        return NULL; +    } + +    t[i] = NULL;      return t;  } @@ -2652,3 +2791,12 @@ pa_bool_t pa_linux_newer_than(unsigned major, unsigned minor, unsigned micro) {      return FALSE;  } + +char* pa_maybe_prefix_path(const char *path, const char *prefix) { +    pa_assert(path); + +    if (pa_is_path_absolute(path)) +        return pa_xstrdup(path); + +    return pa_sprintf_malloc("%s" PA_PATH_SEP "%s", prefix, path); +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index 9ab84b4c..3a1586c9 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -80,9 +80,6 @@ int pa_make_realtime(int rtprio);  int pa_raise_priority(int nice_level);  void pa_reset_priority(void); -pa_bool_t pa_can_realtime(void); -pa_bool_t pa_can_high_priority(void); -  int pa_parse_boolean(const char *s) PA_GCC_PURE;  static inline const char *pa_yes_no(pa_bool_t b) { @@ -201,7 +198,11 @@ pa_bool_t pa_in_system_mode(void);  #define pa_streq(a,b) (!strcmp((a),(b))) +char *pa_get_host_name_malloc(void); +char *pa_get_user_name_malloc(void); +  char *pa_machine_id(void); +char *pa_session_id(void);  char *pa_uname_string(void);  #ifdef HAVE_VALGRIND_MEMCHECK_H @@ -225,4 +226,16 @@ char *pa_realpath(const char *path);  pa_bool_t pa_linux_newer_than(unsigned major, unsigned minor, unsigned micro); +void pa_disable_sigpipe(void); + +void pa_xfreev(void**a); + +static inline void pa_xstrfreev(char **a) { +    pa_xfreev((void**) a); +} + +char **pa_split_spaces_strv(const char *s); + +char* pa_maybe_prefix_path(const char *path, const char *prefix); +  #endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 06573f17..f5eb8352 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -28,6 +28,7 @@  #include <stdio.h>  #include <signal.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h> @@ -35,6 +36,7 @@  #include <pulsecore/sink.h>  #include <pulsecore/source.h>  #include <pulsecore/namereg.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/core-scache.h>  #include <pulsecore/core-subscribe.h> @@ -214,7 +216,7 @@ static void core_free(pa_object *o) {      pa_xfree(c);  } -static void exit_callback(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void exit_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      pa_core *c = userdata;      pa_assert(c->exit_event == e); @@ -229,11 +231,7 @@ void pa_core_check_idle(pa_core *c) {          c->exit_idle_time >= 0 &&          pa_idxset_size(c->clients) == 0) { -        struct timeval tv; -        pa_gettimeofday(&tv); -        tv.tv_sec+= c->exit_idle_time; - -        c->exit_event = c->mainloop->time_new(c->mainloop, &tv, exit_callback, c); +        c->exit_event = pa_core_rttime_new(c, pa_rtclock_now() + c->exit_idle_time * PA_USEC_PER_SEC, exit_callback, c);      } else if (c->exit_event && pa_idxset_size(c->clients) > 0) {          c->mainloop->time_free(c->exit_event); @@ -261,3 +259,21 @@ void pa_core_maybe_vacuum(pa_core *c) {      pa_log_debug("Hmm, no streams around, trying to vacuum.");      pa_mempool_vacuum(c->mempool);  } + +pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) { +    struct timeval tv; + +    pa_assert(c); +    pa_assert(c->mainloop); + +    return c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, usec, TRUE), cb, userdata); +} + +void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) { +    struct timeval tv; + +    pa_assert(c); +    pa_assert(c->mainloop); + +    c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, TRUE)); +} diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index c6794445..e7abd61b 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -27,6 +27,16 @@  typedef struct pa_core pa_core; +/* This is a bitmask that encodes the cause why a sink/source is + * suspended. */ +typedef enum pa_suspend_cause { +    PA_SUSPEND_USER = 1,         /* Exposed to the user via some protocol */ +    PA_SUSPEND_APPLICATION = 2,  /* Used by the device reservation logic */ +    PA_SUSPEND_IDLE = 4,         /* Used by module-suspend-on-idle */ +    PA_SUSPEND_SESSION = 8,      /* Used by module-hal for mark inactive sessions */ +    PA_SUSPEND_ALL = 0xFFFF      /* Magic cause that can be used to resume forcibly */ +} pa_suspend_cause_t; +  #include <pulsecore/idxset.h>  #include <pulsecore/hashmap.h>  #include <pulsecore/memblock.h> @@ -173,4 +183,8 @@ int pa_core_exit(pa_core *c, pa_bool_t force, int retval);  void pa_core_maybe_vacuum(pa_core *c); +/* wrapper for c->mainloop->time_*() RT time events */ +pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata); +void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec); +  #endif diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c new file mode 100644 index 00000000..e65125d3 --- /dev/null +++ b/src/pulsecore/database-gdbm.c @@ -0,0 +1,249 @@ +/*** +  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 +  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 <gdbm.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" + +#define MAKE_GDBM_FILE(x) ((GDBM_FILE) (x)) + +static inline datum* datum_to_gdbm(datum *to, const pa_datum *from) { +    pa_assert(from); +    pa_assert(to); + +    to->dptr = from->data; +    to->dsize = from->size; + +    return to; +} + +static inline pa_datum* datum_from_gdbm(pa_datum *to, const datum *from) { +    pa_assert(from); +    pa_assert(to); + +    to->data = from->dptr; +    to->size = from->dsize; + +    return to; +} + +void pa_datum_free(pa_datum *d) { +    pa_assert(d); + +    free(d->data); /* gdbm uses raw malloc/free hence we should do that here, too */ +    pa_zero(d); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { +    GDBM_FILE f; +    int gdbm_cache_size; +    char *path; + +    pa_assert(fn); + +    /* We include the host identifier in the file name because gdbm +     * files are CPU dependant, and we don't want things to go wrong +     * if we are on a multiarch system. */ +    path = pa_sprintf_malloc("%s."CANONICAL_HOST".gdbm", fn); +    errno = 0; + +    /* We need to set the block size explicitly here, since otherwise +     * gdbm takes the native block size of the underlying file system +     * which might be incredibly large. */ +    f = gdbm_open((char*) path, 1024, GDBM_NOLOCK | (for_write ? GDBM_WRCREAT : GDBM_READER), 0644, NULL); + +    if (f) +        pa_log_debug("Opened GDBM database '%s'", path); + +    pa_xfree(path); + +    if (!f) { +        if (errno == 0) +            errno = EIO; +        return NULL; +    } + +    /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ +    gdbm_cache_size = 10; +    gdbm_setopt(f, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); + +    return (pa_database*) f; +} + +void pa_database_close(pa_database *db) { +    pa_assert(db); + +    gdbm_close(MAKE_GDBM_FILE(db)); +} + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) { +    datum gdbm_key, gdbm_data; + +    pa_assert(db); +    pa_assert(key); +    pa_assert(data); + +    gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)); + +    return gdbm_data.dptr ? +        datum_from_gdbm(data, &gdbm_data) : +        NULL; +} + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite) { +    datum gdbm_key, gdbm_data; + +    pa_assert(db); +    pa_assert(key); +    pa_assert(data); + +    return gdbm_store(MAKE_GDBM_FILE(db), +                      *datum_to_gdbm(&gdbm_key, key), +                      *datum_to_gdbm(&gdbm_data, data), +                      overwrite ? GDBM_REPLACE : GDBM_INSERT) != 0 ? -1 : 0; +} + +int pa_database_unset(pa_database *db, const pa_datum *key) { +    datum gdbm_key; + +    pa_assert(db); +    pa_assert(key); + +    return gdbm_delete(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)) != 0 ? -1 : 0; +} + +int pa_database_clear(pa_database *db) { +    datum gdbm_key; + +    pa_assert(db); + +    gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + +    while (gdbm_key.dptr) { +        datum next; + +        next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key); + +        gdbm_delete(MAKE_GDBM_FILE(db), gdbm_key); + +        free(gdbm_key.dptr); +        gdbm_key = next; +    } + +    return gdbm_reorganize(MAKE_GDBM_FILE(db)) == 0 ? 0 : -1; +} + +signed pa_database_size(pa_database *db) { +    datum gdbm_key; +    unsigned n = 0; + +    pa_assert(db); + +    /* This sucks */ + +    gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + +    while (gdbm_key.dptr) { +        datum next; + +        n++; + +        next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key); +        free(gdbm_key.dptr); +        gdbm_key = next; +    } + +    return (signed) n; +} + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) { +    datum gdbm_key, gdbm_data; + +    pa_assert(db); +    pa_assert(key); + +    gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + +    if (!gdbm_key.dptr) +        return NULL; + +    if (data) { +        gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key); + +        if (!gdbm_data.dptr) { +            free(gdbm_key.dptr); +            return NULL; +        } + +        datum_from_gdbm(data, &gdbm_data); +    } + +    datum_from_gdbm(key, &gdbm_key); + +    return key; +} + +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) { +    datum gdbm_key, gdbm_data; + +    pa_assert(db); +    pa_assert(key); +    pa_assert(next); + +    if (!key) +        return pa_database_first(db, next, data); + +    gdbm_key = gdbm_nextkey(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)); + +    if (!gdbm_key.dptr) +        return NULL; + +    if (data) { +        gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key); + +        if (!gdbm_data.dptr) { +            free(gdbm_key.dptr); +            return NULL; +        } + +        datum_from_gdbm(data, &gdbm_data); +    } + +    datum_from_gdbm(next, &gdbm_key); + +    return next; +} + +int pa_database_sync(pa_database *db) { +    pa_assert(db); + +    gdbm_sync(MAKE_GDBM_FILE(db)); +    return 0; +} diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c new file mode 100644 index 00000000..b79d2837 --- /dev/null +++ b/src/pulsecore/database-tdb.c @@ -0,0 +1,227 @@ +/*** +  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 +  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 <fcntl.h> +#include <unistd.h> +#include <errno.h> + +/* Some versions of tdb lack inclusion of signal.h in the header files but use sigatomic_t */ +#include <signal.h> +#include <tdb.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" + +#define MAKE_TDB_CONTEXT(x) ((struct tdb_context*) (x)) + +static inline TDB_DATA* datum_to_tdb(TDB_DATA *to, const pa_datum *from) { +    pa_assert(from); +    pa_assert(to); + +    to->dptr = from->data; +    to->dsize = from->size; + +    return to; +} + +static inline pa_datum* datum_from_tdb(pa_datum *to, const TDB_DATA *from) { +    pa_assert(from); +    pa_assert(to); + +    to->data = from->dptr; +    to->size = from->dsize; + +    return to; +} + +void pa_datum_free(pa_datum *d) { +    pa_assert(d); + +    free(d->data); /* tdb uses raw malloc/free hence we should do that here, too */ +    pa_zero(d); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { +    struct tdb_context *c; +    char *path; + +    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) +        pa_log_debug("Opened TDB database '%s'", path); + +    pa_xfree(path); + +    if (!c) { +        if (errno == 0) +            errno = EIO; +        return NULL; +    } + +    return (pa_database*) c; +} + +void pa_database_close(pa_database *db) { +    pa_assert(db); + +    tdb_close(MAKE_TDB_CONTEXT(db)); +} + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) { +    TDB_DATA tdb_key, tdb_data; + +    pa_assert(db); +    pa_assert(key); +    pa_assert(data); + +    tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)); + +    return tdb_data.dptr ? +        datum_from_tdb(data, &tdb_data) : +        NULL; +} + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite) { +    TDB_DATA tdb_key, tdb_data; + +    pa_assert(db); +    pa_assert(key); +    pa_assert(data); + +    return tdb_store(MAKE_TDB_CONTEXT(db), +                      *datum_to_tdb(&tdb_key, key), +                      *datum_to_tdb(&tdb_data, data), +                     overwrite ? TDB_REPLACE : TDB_INSERT) != 0 ? -1 : 0; +} + +int pa_database_unset(pa_database *db, const pa_datum *key) { +    TDB_DATA tdb_key; + +    pa_assert(db); +    pa_assert(key); + +    return tdb_delete(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)) != 0 ? -1 : 0; +} + +int pa_database_clear(pa_database *db) { +    pa_assert(db); + +    return tdb_wipe_all(MAKE_TDB_CONTEXT(db)) != 0 ? -1 : 0; +} + +signed pa_database_size(pa_database *db) { +    TDB_DATA tdb_key; +    unsigned n = 0; + +    pa_assert(db); + +    /* This sucks */ + +    tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db)); + +    while (tdb_key.dptr) { +        TDB_DATA next; + +        n++; + +        next = tdb_nextkey(MAKE_TDB_CONTEXT(db), tdb_key); +        free(tdb_key.dptr); +        tdb_key = next; +    } + +    return (signed) n; +} + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) { +    TDB_DATA tdb_key, tdb_data; + +    pa_assert(db); +    pa_assert(key); + +    tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db)); + +    if (!tdb_key.dptr) +        return NULL; + +    if (data) { +        tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key); + +        if (!tdb_data.dptr) { +            free(tdb_key.dptr); +            return NULL; +        } + +        datum_from_tdb(data, &tdb_data); +    } + +    datum_from_tdb(key, &tdb_key); + +    return key; +} + +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) { +    TDB_DATA tdb_key, tdb_data; + +    pa_assert(db); +    pa_assert(key); + +    tdb_key = tdb_nextkey(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)); + +    if (!tdb_key.dptr) +        return NULL; + +    if (data) { +        tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key); + +        if (!tdb_data.dptr) { +            free(tdb_key.dptr); +            return NULL; +        } + +        datum_from_tdb(data, &tdb_data); +    } + +    datum_from_tdb(next, &tdb_key); + +    return next; +} + +int pa_database_sync(pa_database *db) { +    pa_assert(db); + +    return 0; +} diff --git a/src/pulsecore/database.h b/src/pulsecore/database.h new file mode 100644 index 00000000..17455d4c --- /dev/null +++ b/src/pulsecore/database.h @@ -0,0 +1,61 @@ +#ifndef foopulsecoredatabasehfoo +#define foopulsecoredatabasehfoo + +/*** +  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 +  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> + +#include <pulsecore/macro.h> + +/* A little abstraction over simple databases, such as gdbm, tdb, and + * so on. We only make minimal assumptions about the supported + * backend: it does not need to support locking, it does not have to + * be arch independant. */ + +typedef struct pa_database pa_database; + +typedef struct pa_datum { +    void *data; +    size_t size; +} pa_datum; + +void pa_datum_free(pa_datum *d); + +/* This will append a suffix to the filename */ +pa_database* pa_database_open(const char *fn, pa_bool_t for_write); +void pa_database_close(pa_database *db); + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data); + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite); +int pa_database_unset(pa_database *db, const pa_datum *key); + +int pa_database_clear(pa_database *db); + +signed pa_database_size(pa_database *db); + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data /* may be NULL */); +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data /* may be NULL */); + +int pa_database_sync(pa_database *db); + +#endif diff --git a/src/pulsecore/dbus-shared.c b/src/pulsecore/dbus-shared.c new file mode 100644 index 00000000..20ef9b1e --- /dev/null +++ b/src/pulsecore/dbus-shared.c @@ -0,0 +1,107 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006, 2009 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 <stdarg.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulsecore/log.h> +#include <pulsecore/shared.h> + +#include "dbus-shared.h" + +struct pa_dbus_connection { +    PA_REFCNT_DECLARE; + +    pa_dbus_wrap_connection *connection; +    pa_core *core; +    const char *property_name; +}; + +static pa_dbus_connection* dbus_connection_new(pa_core *c, pa_dbus_wrap_connection *conn, const char *name) { +    pa_dbus_connection *pconn; + +    pconn = pa_xnew(pa_dbus_connection, 1); +    PA_REFCNT_INIT(pconn); +    pconn->core = c; +    pconn->property_name = name; +    pconn->connection = conn; + +    pa_shared_set(c, name, pconn); + +    return pconn; +} + +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) { + +    static const char *const prop_name[] = { +        [DBUS_BUS_SESSION] = "dbus-connection-session", +        [DBUS_BUS_SYSTEM] = "dbus-connection-system", +        [DBUS_BUS_STARTER] = "dbus-connection-starter" +    }; +    pa_dbus_wrap_connection *conn; +    pa_dbus_connection *pconn; + +    pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + +    if ((pconn = pa_shared_get(c, prop_name[type]))) +        return pa_dbus_connection_ref(pconn); + +    if (!(conn = pa_dbus_wrap_connection_new(c->mainloop, TRUE, type, error))) +        return NULL; + +    return dbus_connection_new(c, conn, prop_name[type]); +} + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){ +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) > 0); +    pa_assert(c->connection); + +    return pa_dbus_wrap_connection_get(c->connection); +} + +void pa_dbus_connection_unref(pa_dbus_connection *c) { +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) > 0); + +    if (PA_REFCNT_DEC(c) > 0) +        return; + +    pa_dbus_wrap_connection_free(c->connection); + +    pa_shared_remove(c->core, c->property_name); +    pa_xfree(c); +} + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) > 0); + +    PA_REFCNT_INC(c); + +    return c; +} diff --git a/src/pulsecore/dbus-shared.h b/src/pulsecore/dbus-shared.h new file mode 100644 index 00000000..4c154552 --- /dev/null +++ b/src/pulsecore/dbus-shared.h @@ -0,0 +1,42 @@ +#ifndef foodbussharedhfoo +#define foodbussharedhfoo + +/*** +  This file is part of PulseAudio. + +  Copyright 2004-2006, 2009 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. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/core.h> +#include <pulsecore/dbus-util.h> + +typedef struct pa_dbus_connection pa_dbus_connection; + +/* return a pa_dbus_connection of the specified type for the given core, + * like dbus_bus_get(), but integrates the connection with the pa_core */ +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error); + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *conn); + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *conn); +void pa_dbus_connection_unref(pa_dbus_connection *conn); + +#endif diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c new file mode 100644 index 00000000..4e6148f0 --- /dev/null +++ b/src/pulsecore/dbus-util.c @@ -0,0 +1,424 @@ +/*** +  This file is part of PulseAudio. + +  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 <stdarg.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "dbus-util.h" + +struct pa_dbus_wrap_connection { +    pa_mainloop_api *mainloop; +    DBusConnection *connection; +    pa_defer_event* dispatch_event; +    pa_bool_t use_rtclock:1; +}; + +struct timeout_data { +    pa_dbus_wrap_connection *c; +    DBusTimeout *timeout; +}; + +static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) { +    DBusConnection *conn = userdata; + +    if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) { +        /* no more data to process, disable the deferred */ +        ea->defer_enable(ev, 0); +    } +} + +/* DBusDispatchStatusFunction callback for the pa mainloop */ +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { +    pa_dbus_wrap_connection *c = userdata; + +    pa_assert(c); + +    switch(status) { + +        case DBUS_DISPATCH_COMPLETE: +            c->mainloop->defer_enable(c->dispatch_event, 0); +            break; + +        case DBUS_DISPATCH_DATA_REMAINS: +        case DBUS_DISPATCH_NEED_MEMORY: +        default: +            c->mainloop->defer_enable(c->dispatch_event, 1); +            break; +    } +} + +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; +} + +/* pa_io_event_cb_t IO event handler */ +static void handle_io_event(pa_mainloop_api *ea, 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); +} + +/* pa_time_event_cb_t timer event handler */ +static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *t, void *userdata) { +    struct timeval tv; +    struct timeout_data *d = userdata; + +    pa_assert(d); +    pa_assert(d->c); + +    if (dbus_timeout_get_enabled(d->timeout)) { +        dbus_timeout_handle(d->timeout); + +        /* restart it for the next scheduled time */ +        ea->time_restart(e, pa_timeval_rtstore(&tv, pa_timeval_load(t) + dbus_timeout_get_interval(d->timeout) * PA_USEC_PER_MSEC, d->c->use_rtclock)); +    } +} + +/* DBusAddWatchFunction callback for pa mainloop */ +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { +    pa_dbus_wrap_connection *c = data; +    pa_io_event *ev; + +    pa_assert(watch); +    pa_assert(c); + +    ev = c->mainloop->io_new( +            c->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), handle_io_event, watch); + +    dbus_watch_set_data(watch, ev, NULL); + +    return TRUE; +} + +/* DBusRemoveWatchFunction callback for pa mainloop */ +static void remove_watch(DBusWatch *watch, void *data) { +    pa_dbus_wrap_connection *c = data; +    pa_io_event *ev; + +    pa_assert(watch); +    pa_assert(c); + +    if ((ev = dbus_watch_get_data(watch))) +        c->mainloop->io_free(ev); +} + +/* DBusWatchToggledFunction callback for pa mainloop */ +static void toggle_watch(DBusWatch *watch, void *data) { +    pa_dbus_wrap_connection *c = data; +    pa_io_event *ev; + +    pa_assert(watch); +    pa_assert(c); + +    pa_assert_se(ev = dbus_watch_get_data(watch)); + +    /* get_watch_flags() checks if the watch is enabled */ +    c->mainloop->io_enable(ev, get_watch_flags(watch)); +} + +static void time_event_destroy_cb(pa_mainloop_api *a, pa_time_event *e, void *userdata) { +    pa_xfree(userdata); +} + +/* DBusAddTimeoutFunction callback for pa mainloop */ +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { +    pa_dbus_wrap_connection *c = data; +    pa_time_event *ev; +    struct timeval tv; +    struct timeout_data *d; + +    pa_assert(timeout); +    pa_assert(c); + +    if (!dbus_timeout_get_enabled(timeout)) +        return FALSE; + +    d = pa_xnew(struct timeout_data, 1); +    d->c = c; +    d->timeout = timeout; +    ev = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + dbus_timeout_get_interval(timeout) * PA_USEC_PER_MSEC, c->use_rtclock), handle_time_event, d); +    c->mainloop->time_set_destroy(ev, time_event_destroy_cb); + +    dbus_timeout_set_data(timeout, ev, NULL); + +    return TRUE; +} + +/* DBusRemoveTimeoutFunction callback for pa mainloop */ +static void remove_timeout(DBusTimeout *timeout, void *data) { +    pa_dbus_wrap_connection *c = data; +    pa_time_event *ev; + +    pa_assert(timeout); +    pa_assert(c); + +    if ((ev = dbus_timeout_get_data(timeout))) +        c->mainloop->time_free(ev); +} + +/* DBusTimeoutToggledFunction callback for pa mainloop */ +static void toggle_timeout(DBusTimeout *timeout, void *data) { +    struct timeout_data *d = data; +    pa_time_event *ev; +    struct timeval tv; + +    pa_assert(d); +    pa_assert(d->c); +    pa_assert(timeout); + +    pa_assert_se(ev = dbus_timeout_get_data(timeout)); + +    if (dbus_timeout_get_enabled(timeout)) { +        d->c->mainloop->time_restart(ev, pa_timeval_rtstore(&tv, pa_rtclock_now() + dbus_timeout_get_interval(timeout) * PA_USEC_PER_MSEC, d->c->use_rtclock)); +    } else +        d->c->mainloop->time_restart(ev, pa_timeval_rtstore(&tv, PA_USEC_INVALID, d->c->use_rtclock)); +} + +static void wakeup_main(void *userdata) { +    pa_dbus_wrap_connection *c = userdata; + +    pa_assert(c); + +    /* this will wakeup the mainloop and dispatch events, although +     * it may not be the cleanest way of accomplishing it */ +    c->mainloop->defer_enable(c->dispatch_event, 1); +} + +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, pa_bool_t use_rtclock, DBusBusType type, DBusError *error) { +    DBusConnection *conn; +    pa_dbus_wrap_connection *pconn; +    char *id; + +    pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + +    if (!(conn = dbus_bus_get_private(type, error))) +        return NULL; + +    pconn = pa_xnew(pa_dbus_wrap_connection, 1); +    pconn->mainloop = m; +    pconn->connection = 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); + +    pa_log_debug("Successfully connected to D-Bus %s bus %s as %s", +                 type == DBUS_BUS_SYSTEM ? "system" : (type == DBUS_BUS_SESSION ? "session" : "starter"), +                 pa_strnull((id = dbus_connection_get_server_id(conn))), +                 pa_strnull(dbus_bus_get_unique_name(conn))); + +    dbus_free(id); + +    return pconn; +} + +void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) { +    pa_assert(c); + +    if (dbus_connection_get_is_connected(c->connection)) { +        dbus_connection_close(c->connection); +        /* must process remaining messages, bit of a kludge to handle +         * both unload and shutdown */ +        while (dbus_connection_read_write_dispatch(c->connection, -1)) +            ; +    } + +    c->mainloop->defer_free(c->dispatch_event); +    dbus_connection_unref(c->connection); +    pa_xfree(c); +} + +DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *c) { +  pa_assert(c); +  pa_assert(c->connection); + +  return c->connection; +} + +int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) { +    const char *t; +    va_list ap; +    unsigned k = 0; + +    pa_assert(c); +    pa_assert(error); + +    va_start(ap, error); +    while ((t = va_arg(ap, const char*))) { +        dbus_bus_add_match(c, t, error); + +        if (dbus_error_is_set(error)) +            goto fail; + +        k++; +    } +    va_end(ap); +    return 0; + +fail: + +    va_end(ap); +    va_start(ap, error); +    for (; k > 0; k--) { +        DBusError e; + +        pa_assert_se(t = va_arg(ap, const char*)); + +        dbus_error_init(&e); +        dbus_bus_remove_match(c, t, &e); +        dbus_error_free(&e); +    } +    va_end(ap); + +    return -1; +} + +void pa_dbus_remove_matches(DBusConnection *c, ...) { +    const char *t; +    va_list ap; +    DBusError error; + +    pa_assert(c); + +    dbus_error_init(&error); + +    va_start(ap, c); +    while ((t = va_arg(ap, const char*))) { +        dbus_bus_remove_match(c, t, &error); +        dbus_error_free(&error); +    } +    va_end(ap); +} + +pa_dbus_pending *pa_dbus_pending_new( +        DBusConnection *c, +        DBusMessage *m, +        DBusPendingCall *pending, +        void *context_data, +        void *call_data) { + +    pa_dbus_pending *p; + +    pa_assert(pending); + +    p = pa_xnew(pa_dbus_pending, 1); +    p->connection = c; +    p->message = m; +    p->pending = pending; +    p->context_data = context_data; +    p->call_data = call_data; + +    PA_LLIST_INIT(pa_dbus_pending, p); + +    return p; +} + +void pa_dbus_pending_free(pa_dbus_pending *p) { +    pa_assert(p); + +    if (p->pending) { +        dbus_pending_call_cancel(p->pending); +        dbus_pending_call_unref(p->pending); +    } + +    if (p->message) +        dbus_message_unref(p->message); + +    pa_xfree(p); +} + +void pa_dbus_sync_pending_list(pa_dbus_pending **p) { +    pa_assert(p); + +    while (*p && dbus_connection_read_write_dispatch((*p)->connection, -1)) +        ; +} + +void pa_dbus_free_pending_list(pa_dbus_pending **p) { +    pa_dbus_pending *i; + +    pa_assert(p); + +    while ((i = *p)) { +        PA_LLIST_REMOVE(pa_dbus_pending, *p, i); +        pa_dbus_pending_free(i); +    } +} diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h new file mode 100644 index 00000000..9ff298d8 --- /dev/null +++ b/src/pulsecore/dbus-util.h @@ -0,0 +1,63 @@ +#ifndef foodbusutilhfoo +#define foodbusutilhfoo + +/*** +  This file is part of PulseAudio. + +  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. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/llist.h> +#include <pulse/mainloop-api.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); +void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn); + +DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn); + +int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) PA_GCC_SENTINEL; +void pa_dbus_remove_matches(DBusConnection *c,  ...) PA_GCC_SENTINEL; + +typedef struct pa_dbus_pending pa_dbus_pending; + +struct pa_dbus_pending { +    DBusConnection *connection; +    DBusMessage *message; +    DBusPendingCall *pending; + +    void *context_data; +    void *call_data; + +    PA_LLIST_FIELDS(pa_dbus_pending); +}; + +pa_dbus_pending *pa_dbus_pending_new(DBusConnection *c, DBusMessage *m, DBusPendingCall *pending, void *context_data, void *call_data); +void pa_dbus_pending_free(pa_dbus_pending *p); + +/* Sync up a list of pa_dbus_pending_call objects */ +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); + +#endif diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h index 22579376..2b18cf8d 100644 --- a/src/pulsecore/endianmacros.h +++ b/src/pulsecore/endianmacros.h @@ -45,27 +45,27 @@  #define PA_UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) (x) >> 24) | ((uint32_t) (x) << 24) | (((uint32_t) (x) & 0xFF00) << 8) | ((((uint32_t) (x)) >> 8) & 0xFF00) ) )  #endif -static inline uint32_t PA_READ24LE(const uint8_t *p) { +static inline uint32_t PA_READ24BE(const uint8_t *p) {      return          ((uint32_t) p[0] << 16) |          ((uint32_t) p[1] << 8) |          ((uint32_t) p[2]);  } -static inline uint32_t PA_READ24BE(const uint8_t *p) { +static inline uint32_t PA_READ24LE(const uint8_t *p) {      return          ((uint32_t) p[2] << 16) |          ((uint32_t) p[1] << 8) |          ((uint32_t) p[0]);  } -static inline void PA_WRITE24LE(uint8_t *p, uint32_t u) { +static inline void PA_WRITE24BE(uint8_t *p, uint32_t u) {      p[0] = (uint8_t) (u >> 16);      p[1] = (uint8_t) (u >> 8);      p[2] = (uint8_t) u;  } -static inline void PA_WRITE24BE(uint8_t *p, uint32_t u) { +static inline void PA_WRITE24LE(uint8_t *p, uint32_t u) {      p[2] = (uint8_t) (u >> 16);      p[1] = (uint8_t) (u >> 8);      p[0] = (uint8_t) u; diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c index e957c5ba..1fac97eb 100644 --- a/src/pulsecore/hashmap.c +++ b/src/pulsecore/hashmap.c @@ -237,6 +237,39 @@ at_end:      return NULL;  } +void *pa_hashmap_iterate_backwards(pa_hashmap *h, void **state, const void **key) { +    struct hashmap_entry *e; + +    pa_assert(h); +    pa_assert(state); + +    if (*state == (void*) -1) +        goto at_beginning; + +    if (!*state && !h->iterate_list_tail) +        goto at_beginning; + +    e = *state ? *state : h->iterate_list_tail; + +    if (e->iterate_previous) +        *state = e->iterate_previous; +    else +        *state = (void*) -1; + +    if (key) +        *key = e->key; + +    return e->value; + +at_beginning: +    *state = (void *) -1; + +    if (key) +        *key = NULL; + +    return NULL; +} +  void* pa_hashmap_first(pa_hashmap *h) {      pa_assert(h); @@ -246,6 +279,15 @@ void* pa_hashmap_first(pa_hashmap *h) {      return h->iterate_list_head->value;  } +void* pa_hashmap_last(pa_hashmap *h) { +    pa_assert(h); + +    if (!h->iterate_list_tail) +        return NULL; + +    return h->iterate_list_tail->value; +} +  void* pa_hashmap_steal_first(pa_hashmap *h) {      void *data; diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h index 08e18ead..ac2092a6 100644 --- a/src/pulsecore/hashmap.h +++ b/src/pulsecore/hashmap.h @@ -26,7 +26,8 @@  /* Simple Implementation of a hash table. Memory management is the   * user's job. It's a good idea to have the key pointer point to a - * string in the value data. */ + * string in the value data. The insertion order is preserved when + * iterating. */  typedef struct pa_hashmap pa_hashmap; @@ -59,10 +60,24 @@ pa_bool_t pa_hashmap_isempty(pa_hashmap *h);     returned. */  void *pa_hashmap_iterate(pa_hashmap *h, void **state, const void**key); +/* Same as pa_hashmap_iterate() but goes backwards */ +void *pa_hashmap_iterate_backwards(pa_hashmap *h, void **state, const void**key); +  /* Remove the oldest entry in the hashmap and return it */  void *pa_hashmap_steal_first(pa_hashmap *h);  /* Return the oldest entry in the hashmap */  void* pa_hashmap_first(pa_hashmap *h); +/* Return the newest entry in the hashmap */ +void* pa_hashmap_last(pa_hashmap *h); + +/* A macro to ease iteration through all entries */ +#define PA_HASHMAP_FOREACH(e, h, state) \ +    for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), NULL); (e); (e) = pa_hashmap_iterate((h), &(state), NULL)) + +/* A macro to ease iteration through all entries, backwards */ +#define PA_HASHMAP_FOREACH_BACKWARDS(e, h, state) \ +    for ((state) = NULL, (e) = pa_hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = pa_hashmap_iterate_backwards((h), &(state), NULL)) +  #endif diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c index 5f7a8665..a00116d1 100644 --- a/src/pulsecore/hook-list.c +++ b/src/pulsecore/hook-list.c @@ -121,3 +121,9 @@ pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data) {      return result;  } + +pa_bool_t pa_hook_is_firing(pa_hook *hook) { +    pa_assert(hook); + +    return hook->n_firing > 0; +} diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h index 8514cced..86ce9d25 100644 --- a/src/pulsecore/hook-list.h +++ b/src/pulsecore/hook-list.h @@ -71,4 +71,6 @@ void pa_hook_slot_free(pa_hook_slot *slot);  pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data); +pa_bool_t pa_hook_is_firing(pa_hook *hook); +  #endif diff --git a/src/pulsecore/idxset.c b/src/pulsecore/idxset.c index 352ac977..408011f6 100644 --- a/src/pulsecore/idxset.c +++ b/src/pulsecore/idxset.c @@ -453,3 +453,17 @@ pa_bool_t pa_idxset_isempty(pa_idxset *s) {      return s->n_entries == 0;  } + +pa_idxset *pa_idxset_copy(pa_idxset *s) { +    pa_idxset *copy; +    struct idxset_entry *i; + +    pa_assert(s); + +    copy = pa_idxset_new(s->hash_func, s->compare_func); + +    for (i = s->iterate_list_head; i; i = i->iterate_next) +        pa_idxset_put(copy, i->data, NULL); + +    return copy; +} diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h index 7531ea32..d1e68c5c 100644 --- a/src/pulsecore/idxset.h +++ b/src/pulsecore/idxset.h @@ -103,4 +103,11 @@ unsigned pa_idxset_size(pa_idxset*s);  /* Return TRUE of the idxset is empty */  pa_bool_t pa_idxset_isempty(pa_idxset *s); +/* Duplicate the idxset. This will not copy the actual indexes */ +pa_idxset *pa_idxset_copy(pa_idxset *s); + +/* A macro to ease iteration through all entries */ +#define PA_IDXSET_FOREACH(e, s, idx) \ +    for ((e) = pa_idxset_first((s), &(idx)); (e); (e) = pa_idxset_next((s), &(idx))) +  #endif diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c index 5c38d6e5..7afdb08c 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -57,6 +57,9 @@ struct pa_ioline {      pa_ioline_cb_t callback;      void *userdata; +    pa_ioline_drain_cb_t drain_callback; +    void *drain_userdata; +      pa_bool_t dead:1;      pa_bool_t defer_close:1;  }; @@ -81,6 +84,9 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) {      l->callback = NULL;      l->userdata = NULL; +    l->drain_callback = NULL; +    l->drain_userdata = NULL; +      l->mainloop = pa_iochannel_get_mainloop_api(io);      l->defer_event = l->mainloop->defer_new(l->mainloop, defer_callback, l); @@ -202,6 +208,17 @@ void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata      l->userdata = userdata;  } +void pa_ioline_set_drain_callback(pa_ioline*l, pa_ioline_drain_cb_t callback, void *userdata) { +    pa_assert(l); +    pa_assert(PA_REFCNT_VALUE(l) >= 1); + +    if (l->dead) +        return; + +    l->drain_callback = callback; +    l->drain_userdata = userdata; +} +  static void failure(pa_ioline *l, pa_bool_t process_leftover) {      pa_assert(l);      pa_assert(PA_REFCNT_VALUE(l) >= 1); @@ -266,7 +283,7 @@ static int do_read(pa_ioline *l) {      pa_assert(l);      pa_assert(PA_REFCNT_VALUE(l) >= 1); -    while (!l->dead && pa_iochannel_is_readable(l->io)) { +    while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) {          ssize_t r;          size_t len; @@ -331,12 +348,12 @@ static int do_write(pa_ioline *l) {      pa_assert(l);      pa_assert(PA_REFCNT_VALUE(l) >= 1); -    while (!l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length) { +    while (l->io && !l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length > 0) {          if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) <= 0) {              if (r < 0 && errno == EAGAIN) -                return 0; +                break;              if (r < 0 && errno != EPIPE)                  pa_log("write(): %s", pa_cstrerror(errno)); @@ -354,6 +371,9 @@ static int do_write(pa_ioline *l) {              l->wbuf_index = 0;      } +    if (l->wbuf_valid_length <= 0 && l->drain_callback) +        l->drain_callback(l, l->drain_userdata); +      return 0;  } @@ -423,3 +443,25 @@ void pa_ioline_printf(pa_ioline *l, const char *format, ...) {      pa_ioline_puts(l, t);      pa_xfree(t);  } + +pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l) { +    pa_iochannel *r; + +    pa_assert(l); + +    if (!l->io) +        return NULL; + +    r = l->io; +    l->io = NULL; + +    pa_iochannel_set_callback(r, NULL, NULL); + +    return r; +} + +pa_bool_t pa_ioline_is_drained(pa_ioline *l) { +    pa_assert(l); + +    return l->wbuf_valid_length <= 0; +} diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h index 9f32d60f..d973a3c7 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -32,6 +32,7 @@  typedef struct pa_ioline pa_ioline;  typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); +typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata);  pa_ioline* pa_ioline_new(pa_iochannel *io);  void pa_ioline_unref(pa_ioline *l); @@ -47,7 +48,17 @@ void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR(  /* Set the callback function that is called for every recieved line */  void pa_ioline_set_callback(pa_ioline*io, pa_ioline_cb_t callback, void *userdata); +/* Set the callback function that is called when everything has been written */ +void pa_ioline_set_drain_callback(pa_ioline*io, pa_ioline_drain_cb_t callback, void *userdata); +  /* Make sure to close the ioline object as soon as the send buffer is emptied */  void pa_ioline_defer_close(pa_ioline *io); +/* Returns TRUE when everything was written */ +pa_bool_t pa_ioline_is_drained(pa_ioline *io); + +/* Detaches from the iochannel and returns it. Data that has already + * been read will not be available in the detached iochannel */ +pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l); +  #endif diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h index 7b7ffa61..a3661397 100644 --- a/src/pulsecore/ipacl.h +++ b/src/pulsecore/ipacl.h @@ -1,5 +1,5 @@ -#ifndef fooparseaddrhfoo -#define fooparseaddrhfoo +#ifndef foopulsecoreipaclhfoo +#define foopulsecoreipaclhfoo  /***    This file is part of PulseAudio. diff --git a/src/pulsecore/llist.h b/src/pulsecore/llist.h index 77a1749f..43d1fbee 100644 --- a/src/pulsecore/llist.h +++ b/src/pulsecore/llist.h @@ -104,4 +104,10 @@          }                                                               \      } while (0) +#define PA_LLIST_FOREACH(i,head)                                        \ +    for (i = (head); i; i = i->next) + +#define PA_LLIST_FOREACH_FOR_DELETE(i,n,head)                           \ +    for (i = (head), n = i ? i->next : NULL; i; i = n, n = i ? i ->next : NULL) +  #endif diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c index d4d3b76e..8c21ee6c 100644 --- a/src/pulsecore/log.c +++ b/src/pulsecore/log.c @@ -38,6 +38,7 @@  #include <syslog.h>  #endif +#include <pulse/rtclock.h>  #include <pulse/utf8.h>  #include <pulse/xmalloc.h>  #include <pulse/util.h> @@ -45,7 +46,7 @@  #include <pulsecore/macro.h>  #include <pulsecore/core-util.h> -#include <pulsecore/rtclock.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/once.h>  #include <pulsecore/ratelimit.h> @@ -59,12 +60,13 @@  #define ENV_LOG_PRINT_META "PULSE_LOG_META"  #define ENV_LOG_PRINT_LEVEL "PULSE_LOG_LEVEL"  #define ENV_LOG_BACKTRACE "PULSE_LOG_BACKTRACE" +#define ENV_LOG_BACKTRACE_SKIP "PULSE_LOG_BACKTRACE_SKIP"  static char *ident = NULL; /* in local charset format */  static pa_log_target_t target = PA_LOG_STDERR, target_override;  static pa_bool_t target_override_set = FALSE;  static pa_log_level_t maximum_level = PA_LOG_ERROR, maximum_level_override = PA_LOG_ERROR; -static unsigned show_backtrace = 0, show_backtrace_override = 0; +static unsigned show_backtrace = 0, show_backtrace_override = 0, skip_backtrace = 0;  static pa_log_flags_t flags = 0, flags_override = 0;  #ifdef HAVE_SYSLOG_H @@ -128,13 +130,17 @@ void pa_log_set_show_backtrace(unsigned nlevels) {      show_backtrace = nlevels;  } +void pa_log_set_skip_backtrace(unsigned nlevels) { +    skip_backtrace = nlevels; +} +  #ifdef HAVE_EXECINFO_H  static char* get_backtrace(unsigned show_nframes) {      void* trace[32];      int n_frames;      char **symbols, *e, *r; -    unsigned j, n; +    unsigned j, n, s;      size_t a;      pa_assert(show_nframes > 0); @@ -149,14 +155,15 @@ static char* get_backtrace(unsigned show_nframes) {      if (!symbols)          return NULL; -    n = PA_MIN((unsigned) n_frames, show_nframes); +    s = skip_backtrace; +    n = PA_MIN((unsigned) n_frames, s + show_nframes);      a = 4; -    for (j = 0; j < n; j++) { -        if (j > 0) +    for (j = s; j < n; j++) { +        if (j > s)              a += 2; -        a += strlen(symbols[j]); +        a += strlen(pa_path_get_filename(symbols[j]));      }      r = pa_xnew(char, a); @@ -164,14 +171,18 @@ static char* get_backtrace(unsigned show_nframes) {      strcpy(r, " (");      e = r + 2; -    for (j = 0; j < n; j++) { -        if (j > 0) { +    for (j = s; j < n; j++) { +        const char *sym; + +        if (j > s) {              strcpy(e, "<<");              e += 2;          } -        strcpy(e, symbols[j]); -        e += strlen(symbols[j]); +        sym = pa_path_get_filename(symbols[j]); + +        strcpy(e, sym); +        e += strlen(sym);      }      strcpy(e, ")"); @@ -225,6 +236,13 @@ static void init_defaults(void) {          if (show_backtrace_override <= 0)              show_backtrace_override = 0;      } + +    if ((e = getenv(ENV_LOG_BACKTRACE_SKIP))) { +        skip_backtrace = (unsigned) atoi(e); + +        if (skip_backtrace <= 0) +            skip_backtrace = 0; +    }  }  void pa_log_levelv_meta( @@ -245,7 +263,7 @@ void pa_log_levelv_meta(      /* We don't use dynamic memory allocation here to minimize the hit       * in RT threads */ -    char text[4096], location[128], timestamp[32]; +    char text[16*1024], location[128], timestamp[32];      pa_assert(level < PA_LOG_LEVEL_MAX);      pa_assert(format); @@ -268,7 +286,7 @@ void pa_log_levelv_meta(      if ((_flags & PA_LOG_PRINT_META) && file && line > 0 && func)          pa_snprintf(location, sizeof(location), "[%s:%i %s()] ", file, line, func); -    else if (_flags & (PA_LOG_PRINT_META|PA_LOG_PRINT_FILE)) +    else if ((_flags & (PA_LOG_PRINT_META|PA_LOG_PRINT_FILE)) && file)          pa_snprintf(location, sizeof(location), "%s: ", pa_path_get_filename(file));      else          location[0] = 0; @@ -277,7 +295,7 @@ void pa_log_levelv_meta(          static pa_usec_t start, last;          pa_usec_t u, a, r; -        u = pa_rtclock_usec(); +        u = pa_rtclock_now();          PA_ONCE_BEGIN {              start = u; @@ -306,7 +324,7 @@ void pa_log_levelv_meta(  #endif      if (!pa_utf8_valid(text)) -        pa_log_level(level, __FILE__": invalid UTF-8 string following below:"); +        pa_logl(level, "Invalid UTF-8 string following below:");      for (t = text; t; t = n) {          if ((n = strchr(t, '\n'))) { diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h index 153e11e8..2f379f68 100644 --- a/src/pulsecore/log.h +++ b/src/pulsecore/log.h @@ -77,6 +77,9 @@ void pa_log_set_flags(pa_log_flags_t flags, pa_log_merge_t merge);  /* Enable backtrace */  void pa_log_set_show_backtrace(unsigned nlevels); +/* Skip the first backtrace frames */ +void pa_log_set_skip_backtrace(unsigned nlevels); +  void pa_log_level_meta(          pa_log_level_t level,          const char*file, @@ -110,6 +113,7 @@ void pa_log_levelv(  #define pa_log_notice(...) pa_log_level_meta(PA_LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__)  #define pa_log_warn(...)   pa_log_level_meta(PA_LOG_WARN,   __FILE__, __LINE__, __func__, __VA_ARGS__)  #define pa_log_error(...)  pa_log_level_meta(PA_LOG_ERROR,  __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_logl(level, ...)  pa_log_level_meta(level,  __FILE__, __LINE__, __func__, __VA_ARGS__)  #else diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h index 4c4f018a..9f346969 100644 --- a/src/pulsecore/ltdl-helper.h +++ b/src/pulsecore/ltdl-helper.h @@ -29,4 +29,3 @@ typedef void (*pa_void_func_t)(void);  pa_void_func_t pa_load_sym(lt_dlhandle handle, const char*module, const char *symbol);  #endif - diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h index 301d1d16..f1fd7d7f 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -29,6 +29,7 @@  #include <unistd.h>  #include <stdio.h>  #include <stdlib.h> +#include <string.h>  #include <pulse/gccmacro.h> @@ -57,18 +58,27 @@  #define PA_PAGE_SIZE ((size_t) 4096)  #endif +/* Rounds down */ +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*));  }  #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));  }  #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); +    return ((l + PA_PAGE_SIZE - 1) / PA_PAGE_SIZE) * PA_PAGE_SIZE;  }  #define PA_PAGE_ALIGN(x) (pa_page_align(x)) @@ -164,8 +174,8 @@ typedef int pa_bool_t;  #define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL) -/* An assert which guarantees side effects of x, i.e. is never - * optimized away */ +/* pa_assert_se() is an assert which guarantees side effects of x, + * i.e. is never optimized away, regardless of NDEBUG or FASTPATH. */  #define pa_assert_se(expr)                                              \      do {                                                                \          if (PA_UNLIKELY(!(expr))) {                                     \ @@ -174,18 +184,44 @@ typedef int pa_bool_t;          }                                                               \      } while (FALSE) -/* An assert that may be optimized away by defining NDEBUG */ +/* Does exactly nothing */ +#define pa_nop() do {} while (FALSE) + +/* pa_assert() is an assert that may be optimized away by defining + * NDEBUG. pa_assert_fp() is an assert that may be optimized away by + * defining FASTPATH. It is supposed to be used in inner loops. It's + * there for extra paranoia checking and should probably be removed in + * production builds. */  #ifdef NDEBUG -#define pa_assert(expr) do {} while (FALSE) +#define pa_assert(expr) pa_nop() +#define pa_assert_fp(expr) pa_nop() +#elif defined (FASTPATH) +#define pa_assert(expr) pa_assert_se(expr) +#define pa_assert_fp(expr) pa_nop()  #else  #define pa_assert(expr) pa_assert_se(expr) +#define pa_assert_fp(expr) pa_assert_se(expr)  #endif +#ifdef NDEBUG +#define pa_assert_not_reached() pa_nop() +#else  #define pa_assert_not_reached()                                         \      do {                                                                \          pa_log_error("Code should not be reached at %s:%u, function %s(). Aborting.", __FILE__, __LINE__, PA_PRETTY_FUNCTION); \          abort();                                                        \      } while (FALSE) +#endif + +/* A compile time assertion */ +#define pa_assert_cc(expr)                         \ +    do {                                           \ +        switch (0) {                               \ +            case 0:                                \ +            case !!(expr):                         \ +                ;                                  \ +        }                                          \ +    } while (FALSE)  #define PA_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))  #define PA_UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) @@ -226,7 +262,10 @@ typedef int pa_bool_t;  #define PA_DEBUG_TRAP raise(SIGTRAP)  #endif -typedef void (*pa_function_t) (...); +typedef void (*pa_function_t) (void); + +#define pa_memzero(x,l) (memset((x), 0, (l))) +#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))  /* 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 fbf0a470..2c3f98a5 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -45,6 +45,7 @@  #include <pulsecore/macro.h>  #include <pulsecore/flist.h>  #include <pulsecore/core-util.h> +#include <pulsecore/memtrap.h>  #include "memblock.h" @@ -91,6 +92,7 @@ struct pa_memblock {  struct pa_memimport_segment {      pa_memimport *import;      pa_shm memory; +    pa_memtrap *trap;      unsigned n_blocks;  }; @@ -255,7 +257,7 @@ 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_info("Pool full"); +            pa_log_debug("Pool full");              pa_atomic_inc(&p->stat.n_pool_full);              return NULL;          } @@ -507,13 +509,16 @@ static void memblock_free(pa_memblock *b) {              /* FIXME! This should be implemented lock-free */ -            segment = b->per_type.imported.segment; -            pa_assert(segment); -            import = segment->import; -            pa_assert(import); +            pa_assert_se(segment = b->per_type.imported.segment); +            pa_assert_se(import = segment->import);              pa_mutex_lock(import->mutex); -            pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id)); + +            pa_hashmap_remove( +                    import->blocks, +                    PA_UINT32_TO_PTR(b->per_type.imported.id)); + +            pa_assert(segment->n_blocks >= 1);              if (-- segment->n_blocks <= 0)                  segment_detach(segment); @@ -523,6 +528,7 @@ static void memblock_free(pa_memblock *b) {              if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0)                  pa_xfree(b); +              break;          } @@ -655,7 +661,8 @@ pa_memblock *pa_memblock_will_need(pa_memblock *b) {  /* Self-locked. This function is not multiple-caller safe */  static void memblock_replace_import(pa_memblock *b) { -    pa_memimport_segment *seg; +    pa_memimport_segment *segment; +    pa_memimport *import;      pa_assert(b);      pa_assert(b->type == PA_MEMBLOCK_IMPORTED); @@ -665,23 +672,22 @@ static void memblock_replace_import(pa_memblock *b) {      pa_atomic_dec(&b->pool->stat.n_imported);      pa_atomic_sub(&b->pool->stat.imported_size, (int) b->length); -    seg = b->per_type.imported.segment; -    pa_assert(seg); -    pa_assert(seg->import); +    pa_assert_se(segment = b->per_type.imported.segment); +    pa_assert_se(import = segment->import); -    pa_mutex_lock(seg->import->mutex); +    pa_mutex_lock(import->mutex);      pa_hashmap_remove( -            seg->import->blocks, +            import->blocks,              PA_UINT32_TO_PTR(b->per_type.imported.id));      memblock_make_local(b); -    if (-- seg->n_blocks <= 0) { -        pa_mutex_unlock(seg->import->mutex); -        segment_detach(seg); -    } else -        pa_mutex_unlock(seg->import->mutex); +    pa_assert(segment->n_blocks >= 1); +    if (-- segment->n_blocks <= 0) +        segment_detach(segment); + +    pa_mutex_unlock(import->mutex);  }  pa_mempool* pa_mempool_new(pa_bool_t shared, size_t size) { @@ -745,8 +751,47 @@ void pa_mempool_free(pa_mempool *p) {      pa_flist_free(p->free_slots, NULL);      if (pa_atomic_load(&p->stat.n_allocated) > 0) { -/*         raise(SIGTRAP);  */ -        pa_log_warn("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated)); + +        /* Ouch, somebody is retaining a memory block reference! */ + +#ifdef DEBUG_REF +        unsigned i; +        pa_flist *list; + +        /* Let's try to find at least one of those leaked memory blocks */ + +        list = pa_flist_new(p->n_blocks); + +        for (i = 0; i < (unsigned) pa_atomic_load(&p->n_init); i++) { +            struct mempool_slot *slot; +            pa_memblock *b, *k; + +            slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) i)); +            b = mempool_slot_data(slot); + +            while ((k = pa_flist_pop(p->free_slots))) { +                while (pa_flist_push(list, k) < 0) +                    ; + +                if (b == k) +                    break; +            } + +            if (!k) +                pa_log("REF: Leaked memory block %p", b); + +            while ((k = pa_flist_pop(list))) +                while (pa_flist_push(p->free_slots, k) < 0) +                    ; +        } + +        pa_flist_free(list, NULL); + +#endif + +        pa_log_error("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated)); + +/*         PA_DEBUG_TRAP; */      }      pa_shm_free(&p->memory); @@ -853,6 +898,7 @@ 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);      return seg; @@ -864,6 +910,10 @@ static void segment_detach(pa_memimport_segment *seg) {      pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id));      pa_shm_free(&seg->memory); + +    if (seg->trap) +        pa_memtrap_remove(seg->trap); +      pa_xfree(seg);  } diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index e2be42b3..77f9efc9 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -90,8 +90,8 @@ pa_memblockq* pa_memblockq_new(      pa_memblockq_set_maxlength(bq, maxlength);      pa_memblockq_set_tlength(bq, tlength); -    pa_memblockq_set_prebuf(bq, prebuf);      pa_memblockq_set_minreq(bq, minreq); +    pa_memblockq_set_prebuf(bq, prebuf);      pa_memblockq_set_maxrewind(bq, maxrewind);      pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", @@ -601,7 +601,7 @@ size_t pa_memblockq_missing(pa_memblockq *bq) {      return l >= bq->minreq ? l : 0;  } -void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) { +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, pa_bool_t account) {      int64_t old, delta;      pa_assert(bq); @@ -628,12 +628,14 @@ void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) {      delta = bq->write_index - old; -    if (delta >= (int64_t) bq->requested) { -        delta -= (int64_t) bq->requested; -        bq->requested = 0; -    } else if (delta >= 0) { -        bq->requested -= (size_t) delta; -        delta = 0; +    if (account) { +        if (delta >= (int64_t) bq->requested) { +            delta -= (int64_t) bq->requested; +            bq->requested = 0; +        } else if (delta >= 0) { +            bq->requested -= (size_t) delta; +            delta = 0; +        }      }      bq->missing -= delta; @@ -782,16 +784,13 @@ void pa_memblockq_set_maxlength(pa_memblockq *bq, size_t maxlength) {      if (bq->tlength > bq->maxlength)          pa_memblockq_set_tlength(bq, bq->maxlength); - -    if (bq->prebuf > bq->maxlength) -        pa_memblockq_set_prebuf(bq, bq->maxlength);  }  void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) {      size_t old_tlength;      pa_assert(bq); -    if (tlength <= 0) +    if (tlength <= 0 || tlength == (size_t) -1)          tlength = bq->maxlength;      old_tlength = bq->tlength; @@ -800,55 +799,72 @@ void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) {      if (bq->tlength > bq->maxlength)          bq->tlength = bq->maxlength; -    if (bq->prebuf > bq->tlength) -        pa_memblockq_set_prebuf(bq, bq->tlength); -      if (bq->minreq > bq->tlength)          pa_memblockq_set_minreq(bq, bq->tlength); +    if (bq->prebuf > bq->tlength+bq->base-bq->minreq) +        pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); +      bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength;  } +void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { +    pa_assert(bq); + +    bq->minreq = (minreq/bq->base)*bq->base; + +    if (bq->minreq > bq->tlength) +        bq->minreq = bq->tlength; + +    if (bq->minreq < bq->base) +        bq->minreq = bq->base; + +    if (bq->prebuf > bq->tlength+bq->base-bq->minreq) +        pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); +} +  void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) {      pa_assert(bq);      if (prebuf == (size_t) -1) -        prebuf = bq->tlength; +        prebuf = bq->tlength+bq->base-bq->minreq;      bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base;      if (prebuf > 0 && bq->prebuf < bq->base)          bq->prebuf = bq->base; -    if (bq->prebuf > bq->tlength) -        bq->prebuf = bq->tlength; +    if (bq->prebuf > bq->tlength+bq->base-bq->minreq) +        bq->prebuf = bq->tlength+bq->base-bq->minreq;      if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf)          bq->in_prebuf = FALSE; - -    if (bq->minreq > bq->prebuf) -        pa_memblockq_set_minreq(bq, bq->prebuf);  } -void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { +void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) {      pa_assert(bq); -    bq->minreq = (minreq/bq->base)*bq->base; - -    if (bq->minreq > bq->tlength) -        bq->minreq = bq->tlength; +    bq->maxrewind = (maxrewind/bq->base)*bq->base; +} -    if (bq->minreq > bq->prebuf) -        bq->minreq = bq->prebuf; +void pa_memblockq_apply_attr(pa_memblockq *bq, const pa_buffer_attr *a) { +    pa_assert(bq); +    pa_assert(a); -    if (bq->minreq < bq->base) -        bq->minreq = bq->base; +    pa_memblockq_set_maxlength(bq, a->maxlength); +    pa_memblockq_set_tlength(bq, a->tlength); +    pa_memblockq_set_prebuf(bq, a->prebuf); +    pa_memblockq_set_minreq(bq, a->minreq);  } -void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) { +void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a) {      pa_assert(bq); +    pa_assert(a); -    bq->maxrewind = (maxrewind/bq->base)*bq->base; +    a->maxlength = (uint32_t) pa_memblockq_get_maxlength(bq); +    a->tlength = (uint32_t) pa_memblockq_get_tlength(bq); +    a->prebuf = (uint32_t) pa_memblockq_get_prebuf(bq); +    a->minreq = (uint32_t) pa_memblockq_get_minreq(bq);  }  int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) { @@ -875,7 +891,7 @@ int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) {              pa_memblock_unref(chunk.memblock);          } else -            pa_memblockq_seek(bq, (int64_t) chunk.length, PA_SEEK_RELATIVE); +            pa_memblockq_seek(bq, (int64_t) chunk.length, PA_SEEK_RELATIVE, TRUE);          pa_memblockq_drop(bq, chunk.length);      } diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 0a74aa37..146d261b 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -85,7 +85,7 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *chunk);  int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk);  /* Manipulate the write pointer */ -void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek); +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, pa_bool_t account);  /* Return a copy of the next memory chunk in the queue. It is not   * removed from the queue. There are two reasons this function might @@ -158,6 +158,10 @@ void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq);  void pa_memblockq_set_maxrewind(pa_memblockq *memblockq, size_t maxrewind); /* Set the maximum history size */  void pa_memblockq_set_silence(pa_memblockq *memblockq, pa_memchunk *silence); +/* Apply the data from pa_buffer_attr */ +void pa_memblockq_apply_attr(pa_memblockq *memblockq, const pa_buffer_attr *a); +void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a); +  /* Call pa_memchunk_willneed() for every chunk in the queue from the current read pointer to the end */  void pa_memblockq_willneed(pa_memblockq *bq); @@ -175,5 +179,4 @@ pa_bool_t pa_memblockq_prebuf_active(pa_memblockq *bq);  /* Return how many items are currently stored in the queue */  unsigned pa_memblockq_get_nblocks(pa_memblockq *bq); -  #endif diff --git a/src/pulsecore/memtrap.c b/src/pulsecore/memtrap.c new file mode 100644 index 00000000..c647e507 --- /dev/null +++ b/src/pulsecore/memtrap.c @@ -0,0 +1,230 @@ +/*** +  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 <signal.h> +#include <sys/mman.h> + +/* This is deprecated on glibc but is still used by FreeBSD */ +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/aupdate.h> +#include <pulsecore/atomic.h> +#include <pulsecore/once.h> +#include <pulsecore/mutex.h> + +#include "memtrap.h" + +struct pa_memtrap { +    void *start; +    size_t size; +    pa_atomic_t bad; +    pa_memtrap *next[2], *prev[2]; +}; + +static pa_memtrap *memtraps[2] = { NULL, NULL }; +static pa_aupdate *aupdate; +static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT; /* only required to serialize access to the write side */ + +static void allocate_aupdate(void) { +    PA_ONCE_BEGIN { +        aupdate = pa_aupdate_new(); +    } PA_ONCE_END; +} + +pa_bool_t pa_memtrap_is_good(pa_memtrap *m) { +    pa_assert(m); + +    return !pa_atomic_load(&m->bad); +} + +static void sigsafe_error(const char *s) { +    (void) write(STDERR_FILENO, s, strlen(s)); +} + +static void signal_handler(int sig, siginfo_t* si, void *data) { +    unsigned j; +    pa_memtrap *m; +    void *r; + +    j = pa_aupdate_read_begin(aupdate); + +    for (m = memtraps[j]; m; m = m->next[j]) +        if (si->si_addr >= m->start && +            (uint8_t*) si->si_addr < (uint8_t*) m->start + m->size) +            break; + +    if (!m) +        goto fail; + +    pa_atomic_store(&m->bad, 1); + +    /* Remap anonymous memory into the bad segment */ +    if ((r = mmap(m->start, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { +        sigsafe_error("mmap() failed.\n"); +        goto fail; +    } + +    pa_assert(r == m->start); + +    pa_aupdate_read_end(aupdate); +    return; + +fail: +    pa_aupdate_read_end(aupdate); + +    sigsafe_error("Failed to handle SIGBUS.\n"); +    abort(); +} + +static void memtrap_link(pa_memtrap *m, unsigned j) { +    pa_assert(m); + +    m->prev[j] = NULL; +    m->next[j] = memtraps[j]; +    memtraps[j] = m; +} + +static void memtrap_unlink(pa_memtrap *m, unsigned j) { +    pa_assert(m); + +    if (m->next[j]) +        m->next[j]->prev[j] = m->prev[j]; + +    if (m->prev[j]) +        m->prev[j]->next[j] = m->next[j]; +    else +        memtraps[j] = m->next[j]; +} + +pa_memtrap* pa_memtrap_add(const void *start, size_t size) { +    pa_memtrap *m = NULL; +    unsigned j; +    pa_mutex *mx; + +    pa_assert(start); +    pa_assert(size > 0); + +    start = PA_PAGE_ALIGN_PTR(start); +    size = PA_PAGE_ALIGN(size); + +    m = pa_xnew(pa_memtrap, 1); +    m->start = (void*) start; +    m->size = size; +    pa_atomic_store(&m->bad, 0); + +    allocate_aupdate(); + +    mx = pa_static_mutex_get(&mutex, FALSE, TRUE); +    pa_mutex_lock(mx); + +    j = pa_aupdate_write_begin(aupdate); +    memtrap_link(m, j); +    j = pa_aupdate_write_swap(aupdate); +    memtrap_link(m, j); +    pa_aupdate_write_end(aupdate); + +    pa_mutex_unlock(mx); + +    return m; +} + +void pa_memtrap_remove(pa_memtrap *m) { +    unsigned j; +    pa_mutex *mx; + +    pa_assert(m); + +    allocate_aupdate(); + +    mx = pa_static_mutex_get(&mutex, FALSE, TRUE); +    pa_mutex_lock(mx); + +    j = pa_aupdate_write_begin(aupdate); +    memtrap_unlink(m, j); +    j = pa_aupdate_write_swap(aupdate); +    memtrap_unlink(m, j); +    pa_aupdate_write_end(aupdate); + +    pa_mutex_unlock(mx); + +    pa_xfree(m); +} + +pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) { +    unsigned j; +    pa_mutex *mx; + +    pa_assert(m); + +    pa_assert(start); +    pa_assert(size > 0); + +    start = PA_PAGE_ALIGN_PTR(start); +    size = PA_PAGE_ALIGN(size); + +    allocate_aupdate(); + +    mx = pa_static_mutex_get(&mutex, FALSE, TRUE); +    pa_mutex_lock(mx); + +    j = pa_aupdate_write_begin(aupdate); + +    if (m->start == start && m->size == size) +        goto unlock; + +    memtrap_unlink(m, j); +    j = pa_aupdate_write_swap(aupdate); + +    m->start = (void*) start; +    m->size = size; +    pa_atomic_store(&m->bad, 0); + +    j = pa_aupdate_write_swap(aupdate); +    memtrap_link(m, j); + +unlock: +    pa_aupdate_write_end(aupdate); + +    pa_mutex_unlock(mx); + +    return m; +} + +void pa_memtrap_install(void) { +    struct sigaction sa; + +    allocate_aupdate(); + +    memset(&sa, 0, sizeof(sa)); +    sa.sa_sigaction = signal_handler; +    sa.sa_flags = SA_RESTART|SA_SIGINFO; + +    pa_assert_se(sigaction(SIGBUS, &sa, NULL) == 0); +} diff --git a/src/pulsecore/memtrap.h b/src/pulsecore/memtrap.h new file mode 100644 index 00000000..fa38da58 --- /dev/null +++ b/src/pulsecore/memtrap.h @@ -0,0 +1,51 @@ +#ifndef foopulsecorememtraphfoo +#define foopulsecorememtraphfoo + +/*** +  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. +***/ + +#include <sys/types.h> + +#include <pulsecore/macro.h> + +/* This subsystem will trap SIGBUS on specific memory regions. The + * regions will be remapped to anonymous memory (i.e. writable NUL + * bytes) on SIGBUS, so that execution of the main program can + * continue though with memory having changed beneath its hands. With + * pa_memtrap_is_good() it is possible to query if a memory region is + * still 'good' i.e. no SIGBUS has happened yet for it. + * + * Intended usage is to handle memory mapped in which is controlled by + * other processes that might execute ftruncate() or when mapping inb + * hardware resources that might get invalidated when unplugged. */ + +typedef struct pa_memtrap pa_memtrap; + +pa_memtrap* pa_memtrap_add(const void *start, size_t size); +pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size); + +void pa_memtrap_remove(pa_memtrap *m); + +pa_bool_t pa_memtrap_is_good(pa_memtrap *m); + +void pa_memtrap_install(void); + +#endif diff --git a/src/pulsecore/mime-type.c b/src/pulsecore/mime-type.c new file mode 100644 index 00000000..b9fe9444 --- /dev/null +++ b/src/pulsecore/mime-type.c @@ -0,0 +1,182 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2005-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/core-util.h> + +#include "mime-type.h" + +pa_bool_t pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm) { + +    pa_assert(pa_channel_map_compatible(cm, ss)); + +    switch (ss->format) { +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_U8: + +            if (ss->rate != 8000 && +                ss->rate != 11025 && +                ss->rate != 16000 && +                ss->rate != 22050 && +                ss->rate != 24000 && +                ss->rate != 32000 && +                ss->rate != 44100 && +                ss->rate != 48000) +                return FALSE; + +            if (ss->channels != 1 && +                ss->channels != 2) +                return FALSE; + +            if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) || +                (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT))) +                return FALSE; + +            return TRUE; + +        case PA_SAMPLE_ULAW: + +            if (ss->rate != 8000) +                return FALSE; + +            if (ss->channels != 1) +                return FALSE; + +            if (cm->map[0] != PA_CHANNEL_POSITION_MONO) +                return FALSE; + +            return TRUE; + +        default: +            return FALSE; +    } +} + +void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm) { + +    pa_assert(pa_channel_map_compatible(cm, ss)); + +    /* Turns the sample type passed in into the next 'better' one that +     * can be encoded for HTTP. If there is no 'better' one we pick +     * the 'best' one that is 'worse'. */ + +    if (ss->channels > 2) +        ss->channels = 2; + +    if (ss->rate > 44100) +        ss->rate = 48000; +    else if (ss->rate > 32000) +        ss->rate = 44100; +    else if (ss->rate > 24000) +        ss->rate = 32000; +    else if (ss->rate > 22050) +        ss->rate = 24000; +    else if (ss->rate > 16000) +        ss->rate = 22050; +    else if (ss->rate > 11025) +        ss->rate = 16000; +    else if (ss->rate > 8000) +        ss->rate = 11025; +    else +        ss->rate = 8000; + +    switch (ss->format) { +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_S24LE: +        case PA_SAMPLE_S24_32LE: +        case PA_SAMPLE_S24_32BE: +        case PA_SAMPLE_S32LE: +        case PA_SAMPLE_S32BE: +        case PA_SAMPLE_FLOAT32LE: +        case PA_SAMPLE_FLOAT32BE: +            ss->format = PA_SAMPLE_S24BE; +            break; + +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S16LE: +            ss->format = PA_SAMPLE_S16BE; +            break; + +        case PA_SAMPLE_ULAW: +        case PA_SAMPLE_ALAW: + +            if (ss->rate == 8000 && ss->channels == 1) +                ss->format = PA_SAMPLE_ULAW; +            else +                ss->format = PA_SAMPLE_S16BE; +            break; + +        case PA_SAMPLE_U8: +            ss->format = PA_SAMPLE_U8; +            break; + +        case PA_SAMPLE_MAX: +        case PA_SAMPLE_INVALID: +            pa_assert_not_reached(); +    } + +    pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT); + +    pa_assert(pa_sample_spec_is_mime(ss, cm)); +} + +char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) { +    pa_assert(pa_channel_map_compatible(cm, ss)); + +    if (!pa_sample_spec_is_mime(ss, cm)) +        return NULL; + +    switch (ss->format) { + +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_U8: +            /* Stupid UPnP implementations (PS3...) choke on spaces in +             * the mime type, that's why we write only ';' here, +             * instead of '; '. */ +            return pa_sprintf_malloc("audio/%s;rate=%u;channels=%u", +                                     ss->format == PA_SAMPLE_S16BE ? "L16" : +                                     (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"), +                                     ss->rate, ss->channels); + +        case PA_SAMPLE_ULAW: +            return pa_xstrdup("audio/basic"); + +        default: +            pa_assert_not_reached(); +    } + +    pa_assert(pa_sample_spec_valid(ss)); +} + +char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm) { +    pa_sample_spec ss = *_ss; +    pa_channel_map cm = *_cm; + +    pa_sample_spec_mimefy(&ss, &cm); + +    return pa_sample_spec_to_mime_type(&ss, &cm); +} diff --git a/src/pulsecore/mime-type.h b/src/pulsecore/mime-type.h new file mode 100644 index 00000000..db77379b --- /dev/null +++ b/src/pulsecore/mime-type.h @@ -0,0 +1,37 @@ +#ifndef foopulsecoremimetypehfoo +#define foopulsecoremimetypehfoo +/*** +  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 <pulsecore/macro.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> + +pa_bool_t pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm); +void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm); +char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm); +char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm); + +#endif diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c index 73c67a8b..c7d734d9 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -84,8 +84,11 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {          KEY,          VALUE_START,          VALUE_SIMPLE, +        VALUE_SIMPLE_ESCAPED,          VALUE_DOUBLE_QUOTES, -        VALUE_TICKS +        VALUE_DOUBLE_QUOTES_ESCAPED, +        VALUE_TICKS, +        VALUE_TICKS_ESCAPED      } state;      const char *p, *key = NULL, *value = NULL; @@ -131,9 +134,16 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {                      value = p+1;                      value_len = 0;                  } else if (isspace(*p)) { -                    if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0) +                    if (add_key_value(map, +                                      pa_xstrndup(key, key_len), +                                      pa_xstrdup(""), +                                      valid_keys) < 0)                          goto fail;                      state = WHITESPACE; +                } else if (*p == '\\') { +                    state = VALUE_SIMPLE_ESCAPED; +                    value = p; +                    value_len = 1;                  } else {                      state = VALUE_SIMPLE;                      value = p; @@ -143,30 +153,63 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {              case VALUE_SIMPLE:                  if (isspace(*p)) { -                    if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) +                    if (add_key_value(map, +                                      pa_xstrndup(key, key_len), +                                      pa_unescape(pa_xstrndup(value, value_len)), +                                      valid_keys) < 0)                          goto fail;                      state = WHITESPACE; +                } else if (*p == '\\') { +                    state = VALUE_SIMPLE_ESCAPED; +                    value_len++;                  } else                      value_len++;                  break; +            case VALUE_SIMPLE_ESCAPED: +                state = VALUE_SIMPLE; +                value_len++; +                break; +              case VALUE_DOUBLE_QUOTES:                  if (*p == '"') { -                    if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) +                    if (add_key_value(map, +                                      pa_xstrndup(key, key_len), +                                      pa_unescape(pa_xstrndup(value, value_len)), +                                      valid_keys) < 0)                          goto fail;                      state = WHITESPACE; +                } else if (*p == '\\') { +                    state = VALUE_DOUBLE_QUOTES_ESCAPED; +                    value_len++;                  } else                      value_len++;                  break; +            case VALUE_DOUBLE_QUOTES_ESCAPED: +                state = VALUE_DOUBLE_QUOTES; +                value_len++; +                break; +              case VALUE_TICKS:                  if (*p == '\'') { -                    if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) +                    if (add_key_value(map, +                                      pa_xstrndup(key, key_len), +                                      pa_unescape(pa_xstrndup(value, value_len)), +                                      valid_keys) < 0)                          goto fail;                      state = WHITESPACE; +                } else if (*p == '\\') { +                    state = VALUE_TICKS_ESCAPED; +                    value_len++;                  } else                      value_len++;                  break; + +            case VALUE_TICKS_ESCAPED: +                state = VALUE_TICKS; +                value_len++; +                break;          }      } @@ -352,3 +395,23 @@ int pa_modargs_get_sample_spec_and_channel_map(      return 0;  } + +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m) { +    const char *v; +    pa_proplist *n; + +    pa_assert(ma); +    pa_assert(name); +    pa_assert(p); + +    if (!(v = pa_modargs_get_value(ma, name, NULL))) +        return 0; + +    if (!(n = pa_proplist_from_string(v))) +        return -1; + +    pa_proplist_update(p, m, n); +    pa_proplist_free(n); + +    return 0; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index 809fb27e..b3125b10 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -58,4 +58,6 @@ structure if no channel_map is found, using pa_channel_map_init_auto() */  int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *ss, pa_channel_map *map, pa_channel_map_def_t def); +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m); +  #endif diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c index 00fb9c43..b5ee9f56 100644 --- a/src/pulsecore/modinfo.c +++ b/src/pulsecore/modinfo.c @@ -38,6 +38,7 @@  #define PA_SYMBOL_DESCRIPTION "pa__get_description"  #define PA_SYMBOL_USAGE "pa__get_usage"  #define PA_SYMBOL_VERSION "pa__get_version" +#define PA_SYMBOL_DEPRECATED "pa__get_deprecated"  #define PA_SYMBOL_LOAD_ONCE "pa__load_once"  pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { @@ -61,6 +62,9 @@ pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) {      if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_VERSION)))          i->version = pa_xstrdup(func()); +    if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DEPRECATED))) +        i->deprecated = pa_xstrdup(func()); +      if ((func2 = (pa_bool_t (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_LOAD_ONCE)))          i->load_once = func2(); @@ -91,5 +95,6 @@ void pa_modinfo_free(pa_modinfo *i) {      pa_xfree(i->description);      pa_xfree(i->usage);      pa_xfree(i->version); +    pa_xfree(i->deprecated);      pa_xfree(i);  } diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h index 407e602a..baad0de7 100644 --- a/src/pulsecore/modinfo.h +++ b/src/pulsecore/modinfo.h @@ -30,6 +30,7 @@ typedef struct pa_modinfo {      char *description;      char *usage;      char *version; +    char *deprecated;      pa_bool_t load_once;  } pa_modinfo; diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c index 42fd912c..5bcdd898 100644 --- a/src/pulsecore/module.c +++ b/src/pulsecore/module.c @@ -48,10 +48,12 @@  #define PA_SYMBOL_DONE "pa__done"  #define PA_SYMBOL_LOAD_ONCE "pa__load_once"  #define PA_SYMBOL_GET_N_USED "pa__get_n_used" +#define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated"  pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {      pa_module *m = NULL;      pa_bool_t (*load_once)(void); +    const char* (*get_deprecated)(void);      pa_modinfo *mi;      pa_assert(c); @@ -89,6 +91,13 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {          }      } +    if ((get_deprecated = (const char* (*) (void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_DEPRECATE))) { +        const char *t; + +        if ((t = get_deprecated())) +            pa_log_warn("%s is deprecated: %s", name, t); +    } +      if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) {          pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name);          goto fail; diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h index 3f697348..af89d793 100644 --- a/src/pulsecore/module.h +++ b/src/pulsecore/module.h @@ -78,6 +78,10 @@ int pa_module_get_n_used(pa_module*m);      const char * pa__get_version(void) { return s; }            \      struct __stupid_useless_struct_to_allow_trailing_semicolon +#define PA_MODULE_DEPRECATED(s)                                 \ +    const char * pa__get_deprecated(void) { return s; }         \ +    struct __stupid_useless_struct_to_allow_trailing_semicolon +  #define PA_MODULE_LOAD_ONCE(b)                                  \      pa_bool_t pa__load_once(void) { return b; }                 \      struct __stupid_useless_struct_to_allow_trailing_semicolon diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c index b3e5256a..0ff4bee6 100644 --- a/src/pulsecore/mutex-posix.c +++ b/src/pulsecore/mutex-posix.c @@ -153,6 +153,8 @@ pa_mutex* pa_static_mutex_get(pa_static_mutex *s, pa_bool_t recursive, pa_bool_t      if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m)))          return m; +    pa_mutex_free(m); +      /* Him, filling in failed, so someone else must have filled in       * already */      pa_assert_se(m = pa_atomic_ptr_load(&s->ptr)); diff --git a/src/pulsecore/mutex.h b/src/pulsecore/mutex.h index a4dd6738..b1edc132 100644 --- a/src/pulsecore/mutex.h +++ b/src/pulsecore/mutex.h @@ -46,10 +46,14 @@ void pa_cond_free(pa_cond *c);  void pa_cond_signal(pa_cond *c, int broadcast);  int pa_cond_wait(pa_cond *c, pa_mutex *m); +/* Static mutexes are basically just atomically updated pointers to pa_mutex objects */ +  typedef struct pa_static_mutex {      pa_atomic_ptr_t ptr;  } pa_static_mutex; +#define PA_STATIC_MUTEX_INIT { PA_ATOMIC_PTR_INIT(NULL) } +  pa_mutex* pa_static_mutex_get(pa_static_mutex *m, pa_bool_t recursive, pa_bool_t inherit_priority);  #endif diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 6951e10a..f49abb09 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -152,7 +152,7 @@ enum {      /* Supported since protocol v14 (0.9.12) */      PA_COMMAND_EXTENSION, -    /* Supported since protocol v15 (0.9.15*/ +    /* Supported since protocol v15 (0.9.15) */      PA_COMMAND_GET_CARD_INFO,      PA_COMMAND_GET_CARD_INFO_LIST,      PA_COMMAND_SET_CARD_PROFILE, @@ -161,6 +161,14 @@ enum {      PA_COMMAND_PLAYBACK_STREAM_EVENT,      PA_COMMAND_RECORD_STREAM_EVENT, +    /* SERVER->CLIENT */ +    PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED, +    PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED, + +    /* Supported since protocol v16 (0.9.16) */ +    PA_COMMAND_SET_SINK_PORT, +    PA_COMMAND_SET_SOURCE_PORT, +      PA_COMMAND_MAX  }; diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c index 8fd05fb6..f3ead9c5 100644 --- a/src/pulsecore/object.c +++ b/src/pulsecore/object.c @@ -24,6 +24,8 @@  #include <config.h>  #endif +#include <pulsecore/core-util.h> +  #include "object.h"  pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) { @@ -66,5 +68,5 @@ void pa_object_unref(pa_object *o) {  int pa_object_check_type(const char *type_name) {      pa_assert(type_name); -    return strcmp(type_name, "pa_object") == 0; +    return pa_streq(type_name, "pa_object");  } diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c index c5cd7fe7..44cd9a05 100644 --- a/src/pulsecore/parseaddr.c +++ b/src/pulsecore/parseaddr.c @@ -25,6 +25,8 @@  #include <string.h>  #include <stdlib.h> +#include <arpa/inet.h> +#include <sys/socket.h>  #include <pulse/xmalloc.h>  #include <pulse/util.h> @@ -87,13 +89,15 @@ int pa_parse_address(const char *name, pa_parsed_address *ret_p) {      ret_p->type = PA_PARSED_ADDRESS_TCP_AUTO;      if (*name == '{') { -        char hn[256], *pfx; -        /* The URL starts with a host specification for detecting local connections */ +        char *id, *pfx; -        if (!pa_get_host_name(hn, sizeof(hn))) +        /* The URL starts with a host id for detecting local connections */ +        if (!(id = pa_machine_id()))              return -1; -        pfx = pa_sprintf_malloc("{%s}", hn); +        pfx = pa_sprintf_malloc("{%s}", id); +        pa_xfree(id); +          if (!pa_startswith(name, pfx)) {              pa_xfree(pfx);              /* Not local */ @@ -129,3 +133,17 @@ int pa_parse_address(const char *name, pa_parsed_address *ret_p) {      return 0;  } + +pa_bool_t pa_is_ip_address(const char *a) { +    char buf[INET6_ADDRSTRLEN]; + +    pa_assert(a); + +    if (inet_pton(AF_INET6, a, buf) >= 1) +        return TRUE; + +    if (inet_pton(AF_INET, a, buf) >= 1) +        return TRUE; + +    return FALSE; +} diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h index 5fbcb9a7..a1071b0a 100644 --- a/src/pulsecore/parseaddr.h +++ b/src/pulsecore/parseaddr.h @@ -1,5 +1,5 @@ -#ifndef fooparseaddrhfoo -#define fooparseaddrhfoo +#ifndef foopulsecoreparseaddrhfoo +#define foopulsecoreparseaddrhfoo  /***    This file is part of PulseAudio. @@ -24,6 +24,8 @@  #include <inttypes.h> +#include <pulsecore/macro.h> +  typedef enum pa_parsed_address_type {      PA_PARSED_ADDRESS_UNIX,      PA_PARSED_ADDRESS_TCP4, @@ -39,4 +41,6 @@ typedef struct pa_parsed_address {  int pa_parse_address(const char *a, pa_parsed_address *ret_p); +pa_bool_t pa_is_ip_address(const char *a); +  #endif diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index 305941a3..fc8ce76f 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -27,6 +27,7 @@  #include <stdio.h>  #include <stdlib.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h> @@ -37,6 +38,7 @@  #include <pulsecore/macro.h>  #include <pulsecore/refcnt.h>  #include <pulsecore/flist.h> +#include <pulsecore/core-rtclock.h>  #include "pdispatch.h" @@ -165,7 +167,20 @@ static const char *command_names[PA_COMMAND_MAX] = {      [PA_COMMAND_STARTED] = "STARTED",      /* Supported since protocol v14 (0.9.12) */ -    [PA_COMMAND_EXTENSION] = "EXTENSION" +    [PA_COMMAND_EXTENSION] = "EXTENSION", + + +    [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", + +    [PA_COMMAND_CLIENT_EVENT] = "GET_CLIENT_EVENT", +    [PA_COMMAND_PLAYBACK_STREAM_EVENT] = "PLAYBACK_STREAM_EVENT", +    [PA_COMMAND_RECORD_STREAM_EVENT] = "RECORD_STREAM_EVENT", + +    /* SERVER->CLIENT */ +    [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = "PLAYBACK_BUFFER_ATTR_CHANGED", +    [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = "RECORD_BUFFER_ATTR_CHANGED"  };  #endif @@ -191,6 +206,7 @@ struct pa_pdispatch {      pa_pdispatch_drain_callback drain_callback;      void *drain_userdata;      const pa_creds *creds; +    pa_bool_t use_rtclock:1;  };  static void reply_info_free(struct reply_info *r) { @@ -207,7 +223,7 @@ static void reply_info_free(struct reply_info *r) {          pa_xfree(r);  } -pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, const pa_pdispatch_cb_t*table, unsigned entries) { +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); @@ -222,6 +238,7 @@ pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, const pa_pdispatch_cb_      pd->drain_callback = NULL;      pd->drain_userdata = NULL;      pd->creds = NULL; +    pd->use_rtclock = use_rtclock;      return pd;  } @@ -291,7 +308,7 @@ int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds,      if (command >= PA_COMMAND_MAX || !(p = command_names[command]))          pa_snprintf((char*) (p = t), sizeof(t), "%u", command); -    pa_log("[%p] Recieved opcode <%s>", pd, p); +    pa_log("[%p] Received opcode <%s>", pd, p);  }  #endif @@ -312,7 +329,7 @@ int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds,          (*c)(pd, command, tag, ts, userdata);      } else { -        pa_log("Recieved unsupported command %u", command); +        pa_log("Received unsupported command %u", command);          goto finish;      } @@ -329,7 +346,7 @@ finish:      return ret;  } -static void timeout_callback(pa_mainloop_api*m, pa_time_event*e, const struct timeval *tv, void *userdata) { +static void timeout_callback(pa_mainloop_api*m, pa_time_event*e, const struct timeval *t, void *userdata) {      struct reply_info*r = userdata;      pa_assert(r); @@ -358,10 +375,7 @@ void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa      r->free_cb = free_cb;      r->tag = tag; -    pa_gettimeofday(&tv); -    tv.tv_sec += timeout; - -    pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop, &tv, 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);  } diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h index 5c31d80e..dae475af 100644 --- a/src/pulsecore/pdispatch.h +++ b/src/pulsecore/pdispatch.h @@ -37,7 +37,7 @@ 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); -pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, 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); diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c index af5f0aa6..d9769bc7 100644 --- a/src/pulsecore/proplist-util.c +++ b/src/pulsecore/proplist-util.c @@ -168,20 +168,20 @@ void pa_init_proplist(pa_proplist *p) {      }      if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_USER)) { -        char t[64]; -        if (pa_get_user_name(t, sizeof(t))) { -            char *c = pa_utf8_filter(t); -            pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, c); -            pa_xfree(c); +        char *u; + +        if ((u = pa_get_user_name_malloc())) { +            pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, u); +            pa_xfree(u);          }      }      if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_HOST)) { -        char t[64]; -        if (pa_get_host_name(t, sizeof(t))) { -            char *c = pa_utf8_filter(t); -            pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, c); -            pa_xfree(c); +        char *h; + +        if ((h = pa_get_host_name_malloc())) { +            pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, h); +            pa_xfree(h);          }      } @@ -231,12 +231,11 @@ void pa_init_proplist(pa_proplist *p) {      }      if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID)) { -        const char *t; +        char *s; -        if ((t = getenv("XDG_SESSION_COOKIE"))) { -            char *c = pa_utf8_filter(t); -            pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID, c); -            pa_xfree(c); +        if ((s = pa_session_id())) { +            pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID, s); +            pa_xfree(s);          }      }  } diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 2b80c65f..f64552aa 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -30,6 +30,7 @@  #include <stdlib.h>  #include <limits.h> +#include <pulse/rtclock.h>  #include <pulse/sample.h>  #include <pulse/timeval.h>  #include <pulse/utf8.h> @@ -63,7 +64,7 @@  #define MAX_CONNECTIONS 64  /* Kick a client if it doesn't authenticate within this time */ -#define AUTH_TIMEOUT 5 +#define AUTH_TIMEOUT (5*PA_USEC_PER_SEC)  #define DEFAULT_COOKIE_FILE ".esd_auth" @@ -562,7 +563,7 @@ static int esd_proto_get_latency(connection *c, esd_proto_t request, const void      pa_sink *sink;      int32_t latency; -    connection_ref(c); +    connection_assert_ref(c);      pa_assert(!data);      pa_assert(length == 0); @@ -575,6 +576,7 @@ static int esd_proto_get_latency(connection *c, esd_proto_t request, const void      latency = PA_MAYBE_INT32_SWAP(c->swap_byte_order, latency);      connection_write(c, &latency, sizeof(int32_t)); +      return 0;  } @@ -583,7 +585,7 @@ static int esd_proto_server_info(connection *c, esd_proto_t request, const void      int32_t response;      pa_sink *sink; -    connection_ref(c); +    connection_assert_ref(c);      pa_assert(data);      pa_assert(length == sizeof(int32_t)); @@ -611,7 +613,7 @@ static int esd_proto_all_info(connection *c, esd_proto_t request, const void *da      unsigned nsamples;      char terminator[sizeof(int32_t)*6+ESD_NAME_MAX]; -    connection_ref(c); +    connection_assert_ref(c);      pa_assert(data);      pa_assert(length == sizeof(int32_t)); @@ -637,7 +639,8 @@ static int esd_proto_all_info(connection *c, esd_proto_t request, const void *da          pa_assert(t >= k*2+s);          if (conn->sink_input) { -            pa_cvolume volume = *pa_sink_input_get_volume(conn->sink_input); +            pa_cvolume volume; +            pa_sink_input_get_volume(conn->sink_input, &volume, TRUE);              rate = (int32_t) conn->sink_input->sample_spec.rate;              lvolume = (int32_t) ((volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM);              rvolume = (int32_t) ((volume.values[volume.channels == 2 ? 1 : 0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); @@ -777,7 +780,7 @@ static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void *          volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE;          volume.channels = conn->sink_input->sample_spec.channels; -        pa_sink_input_set_volume(conn->sink_input, &volume, TRUE); +        pa_sink_input_set_volume(conn->sink_input, &volume, TRUE, TRUE);          ok = 1;      } else          ok = 0; @@ -945,10 +948,10 @@ static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const      connection_write(c, &ok, sizeof(int32_t));      if (request == ESD_PROTO_STANDBY) -        ok = pa_sink_suspend_all(c->protocol->core, TRUE) >= 0; +        ok = pa_sink_suspend_all(c->protocol->core, TRUE, PA_SUSPEND_USER) >= 0;      else {          pa_assert(request == ESD_PROTO_RESUME); -        ok = pa_sink_suspend_all(c->protocol->core, FALSE) >= 0; +        ok = pa_sink_suspend_all(c->protocol->core, FALSE, PA_SUSPEND_USER) >= 0;      }      connection_write(c, &ok, sizeof(int32_t)); @@ -1105,8 +1108,7 @@ static int do_read(connection *c) {              pa_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, c->client->proplist, &idx);              pa_memblock_unref(c->scache.memchunk.memblock); -            c->scache.memchunk.memblock = NULL; -            c->scache.memchunk.index = c->scache.memchunk.length = 0; +            pa_memchunk_reset(&c->scache.memchunk);              pa_xfree(c->scache.name);              c->scache.name = NULL; @@ -1458,11 +1460,10 @@ static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {  /*** entry points ***/ -static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void auth_timeout(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      connection *c = CONNECTION(userdata);      pa_assert(m); -    pa_assert(tv);      connection_assert_ref(c);      pa_assert(c->auth_timeout_event == e); @@ -1552,12 +1553,9 @@ void pa_esound_protocol_connect(pa_esound_protocol *p, pa_iochannel *io, pa_esou          c->authorized = TRUE;      } -    if (!c->authorized) { -        struct timeval tv; -        pa_gettimeofday(&tv); -        tv.tv_sec += AUTH_TIMEOUT; -        c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, auth_timeout, c); -    } else +    if (!c->authorized) +        c->auth_timeout_event = pa_core_rttime_new(p->core, pa_rtclock_now() + AUTH_TIMEOUT, auth_timeout, c); +    else          c->auth_timeout_event = NULL;      c->defer_event = p->core->mainloop->defer_new(p->core->mainloop, defer_callback, c); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index f3b93819..5220cc91 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -1,7 +1,7 @@  /***    This file is part of PulseAudio. -  Copyright 2005-2006 Lennart Poettering +  Copyright 2005-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 @@ -26,36 +26,68 @@  #include <stdlib.h>  #include <stdio.h>  #include <string.h> +#include <errno.h>  #include <pulse/util.h>  #include <pulse/xmalloc.h> +#include <pulse/timeval.h>  #include <pulsecore/ioline.h> +#include <pulsecore/thread-mq.h>  #include <pulsecore/macro.h>  #include <pulsecore/log.h>  #include <pulsecore/namereg.h>  #include <pulsecore/cli-text.h>  #include <pulsecore/shared.h> +#include <pulsecore/core-error.h> +#include <pulsecore/mime-type.h>  #include "protocol-http.h"  /* Don't allow more than this many concurrent connections */  #define MAX_CONNECTIONS 10 -#define internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL) -  #define URL_ROOT "/"  #define URL_CSS "/style"  #define URL_STATUS "/status" +#define URL_LISTEN "/listen" +#define URL_LISTEN_SOURCE "/listen/source/" + +#define MIME_HTML "text/html; charset=utf-8" +#define MIME_TEXT "text/plain; charset=utf-8" +#define MIME_CSS "text/css" + +#define HTML_HEADER(t)                                                  \ +    "<?xml version=\"1.0\"?>\n"                                         \ +    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \ +    "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"                   \ +    "        <head>\n"                                                  \ +    "                <title>"t"</title>\n"                              \ +    "                <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \ +    "        </head>\n"                                                 \ +    "        <body>\n" + +#define HTML_FOOTER                                                     \ +    "        </body>\n"                                                 \ +    "</html>\n" + +#define RECORD_BUFFER_SECONDS (5) +#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC) + +enum state { +    STATE_REQUEST_LINE, +    STATE_MIME_HEADER, +    STATE_DATA +};  struct connection {      pa_http_protocol *protocol; +    pa_iochannel *io;      pa_ioline *line; -    enum { -        REQUEST_LINE, -        MIME_HEADER, -        DATA -    } state; +    pa_memblockq *output_memblockq; +    pa_source_output *source_output; +    pa_client *client; +    enum state state;      char *url;      pa_module *module;  }; @@ -65,58 +97,500 @@ struct pa_http_protocol {      pa_core *core;      pa_idxset *connections; + +    pa_strlist *servers;  }; -static void http_response(struct connection *c, int code, const char *msg, const char *mime) { -    char s[256]; +enum { +    SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +/* Called from main context */ +static void connection_unlink(struct connection *c) { +    pa_assert(c); + +    if (c->source_output) { +        pa_source_output_unlink(c->source_output); +        c->source_output->userdata = NULL; +        pa_source_output_unref(c->source_output); +    } + +    if (c->client) +        pa_client_free(c->client); + +    pa_xfree(c->url); + +    if (c->line) +        pa_ioline_unref(c->line); + +    if (c->io) +        pa_iochannel_free(c->io); + +    if (c->output_memblockq) +        pa_memblockq_free(c->output_memblockq); + +    pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + +    pa_xfree(c); +} + +/* Called from main context */ +static int do_write(struct connection *c) { +    pa_memchunk chunk; +    ssize_t r; +    void *p; + +    pa_assert(c); + +    if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) +        return 0; + +    pa_assert(chunk.memblock); +    pa_assert(chunk.length > 0); + +    p = pa_memblock_acquire(chunk.memblock); +    r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); +    pa_memblock_release(chunk.memblock); + +    pa_memblock_unref(chunk.memblock); + +    if (r < 0) { + +        if (errno == EINTR || errno == EAGAIN) +            return 0; + +        pa_log("write(): %s", pa_cstrerror(errno)); +        return -1; +    } + +    pa_memblockq_drop(c->output_memblockq, (size_t) r); + +    return 0; +} + +/* Called from main context */ +static void do_work(struct connection *c) { +    pa_assert(c); + +    if (pa_iochannel_is_hungup(c->io)) +        goto fail; + +    if (pa_iochannel_is_writable(c->io)) +        if (do_write(c) < 0) +            goto fail; + +    return; + +fail: +    connection_unlink(c); +} + +/* Called from thread context, except when it is not */ +static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { +    pa_source_output *o = PA_SOURCE_OUTPUT(m); +    struct connection *c; + +    pa_source_output_assert_ref(o); + +    if (!(c = o->userdata)) +        return -1; + +    switch (code) { + +        case SOURCE_OUTPUT_MESSAGE_POST_DATA: +            /* While this function is usually called from IO thread +             * context, this specific command is not! */ +            pa_memblockq_push_align(c->output_memblockq, chunk); +            do_work(c); +            break; + +        default: +            return pa_source_output_process_msg(m, code, userdata, offset, chunk); +    } + +    return 0; +} + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { +    struct connection *c; + +    pa_source_output_assert_ref(o); +    pa_assert_se(c = o->userdata); +    pa_assert(chunk); + +    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +/* Called from main context */ +static void source_output_kill_cb(pa_source_output *o) { +    struct connection*c; + +    pa_source_output_assert_ref(o); +    pa_assert_se(c = o->userdata); + +    connection_unlink(c); +} + +/* Called from main context */ +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { +    struct connection*c; + +    pa_source_output_assert_ref(o); +    pa_assert_se(c = o->userdata); + +    return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** client callbacks ***/ +static void client_kill_cb(pa_client *client) { +    struct connection*c; + +    pa_assert(client); +    pa_assert_se(c = client->userdata); + +    connection_unlink(c); +} + +/*** pa_iochannel callbacks ***/ +static void io_callback(pa_iochannel*io, void *userdata) { +    struct connection *c = userdata; + +    pa_assert(c); +    pa_assert(io); + +    do_work(c); +} + +static char *escape_html(const char *t) { +    pa_strbuf *sb; +    const char *p, *e; + +    sb = pa_strbuf_new(); + +    for (e = p = t; *p; p++) { + +        if (*p == '>' || *p == '<' || *p == '&') { + +            if (p > e) { +                pa_strbuf_putsn(sb, e, p-e); +                e = p + 1; +            } + +            if (*p == '>') +                pa_strbuf_puts(sb, ">"); +            else if (*p == '<') +                pa_strbuf_puts(sb, "<"); +            else +                pa_strbuf_puts(sb, "&"); +        } +    } + +    if (p > e) +        pa_strbuf_putsn(sb, e, p-e); + +    return pa_strbuf_tostring_free(sb); +} + +static void http_response( +        struct connection *c, +        int code, +        const char *msg, +        const char *mime) { + +    char *s;      pa_assert(c);      pa_assert(msg);      pa_assert(mime); -    pa_snprintf(s, sizeof(s), -             "HTTP/1.0 %i %s\n" -             "Connection: close\n" -             "Content-Type: %s\n" -             "Cache-Control: no-cache\n" -             "Expires: 0\n" -             "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" -             "\n", code, msg, mime); - +    s = pa_sprintf_malloc( +            "HTTP/1.0 %i %s\n" +            "Connection: close\n" +            "Content-Type: %s\n" +            "Cache-Control: no-cache\n" +            "Expires: 0\n" +            "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" +            "\n", code, msg, mime);      pa_ioline_puts(c->line, s); +    pa_xfree(s);  } -static void http_message(struct connection *c, int code, const char *msg, const char *text) { -    char s[256]; +static void html_response( +        struct connection *c, +        int code, +        const char *msg, +        const char *text) { + +    char *s;      pa_assert(c); -    http_response(c, code, msg, "text/html"); +    http_response(c, code, msg, MIME_HTML);      if (!text)          text = msg; -    pa_snprintf(s, sizeof(s), -             "<?xml version=\"1.0\"?>\n" -             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" -             "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>%s</title></head>\n" -             "<body>%s</body></html>\n", -             text, text); +    s = pa_sprintf_malloc( +            HTML_HEADER("%s") +            "%s" +            HTML_FOOTER, +            text, text);      pa_ioline_puts(c->line, s); +    pa_xfree(s); +      pa_ioline_defer_close(c->line);  } +static void html_print_field(pa_ioline *line, const char *left, const char *right) { +    char *eleft, *eright; + +    eleft = escape_html(left); +    eright = escape_html(right); + +    pa_ioline_printf(line, +                     "<tr><td><b>%s</b></td>" +                     "<td>%s</td></tr>\n", eleft, eright); + +    pa_xfree(eleft); +    pa_xfree(eright); +} + +static void handle_root(struct connection *c) { +    char *t; -static void connection_unlink(struct connection *c) {      pa_assert(c); -    if (c->url) -        pa_xfree(c->url); +    http_response(c, 200, "OK", MIME_HTML); -    pa_idxset_remove_by_data(c->protocol->connections, c, NULL); +    pa_ioline_puts(c->line, +                   HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) +                   "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" +                   "<table>\n"); + +    t = pa_get_user_name_malloc(); +    html_print_field(c->line, "User Name:", t); +    pa_xfree(t); + +    t = pa_get_host_name_malloc(); +    html_print_field(c->line, "Host name:", t); +    pa_xfree(t); + +    t = pa_machine_id(); +    html_print_field(c->line, "Machine ID:", t); +    pa_xfree(t); + +    t = pa_uname_string(); +    html_print_field(c->line, "System:", t); +    pa_xfree(t); + +    t = pa_sprintf_malloc("%lu", (unsigned long) getpid()); +    html_print_field(c->line, "Process ID:", t); +    pa_xfree(t); + +    pa_ioline_puts(c->line, +                   "</table>\n" +                   "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n" +                   "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n" +                   HTML_FOOTER); + +    pa_ioline_defer_close(c->line); +} + +static void handle_css(struct connection *c) { +    pa_assert(c); + +    http_response(c, 200, "OK", MIME_CSS); + +    pa_ioline_puts(c->line, +                   "body { color: black; background-color: white; }\n" +                   "a:link, a:visited { color: #900000; }\n" +                   "div.news-date { font-size: 80%; font-style: italic; }\n" +                   "pre { background-color: #f0f0f0; padding: 0.4cm; }\n" +                   ".grey { color: #8f8f8f; font-size: 80%; }" +                   "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" +                   "td { padding-left:10px; padding-right:10px; }\n"); + +    pa_ioline_defer_close(c->line); +} + +static void handle_status(struct connection *c) { +    char *r; + +    pa_assert(c); + +    http_response(c, 200, "OK", MIME_TEXT); +    r = pa_full_status_string(c->protocol->core); +    pa_ioline_puts(c->line, r); +    pa_xfree(r); + +    pa_ioline_defer_close(c->line); +} + +static void handle_listen(struct connection *c) { +    pa_source *source; +    pa_sink *sink; +    uint32_t idx; + +    http_response(c, 200, "OK", MIME_HTML); + +    pa_ioline_puts(c->line, +                   HTML_HEADER("Listen") +                   "<h2>Sinks</h2>\n" +                   "<p>\n"); + +    PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) { +        char *t, *m; + +        t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); +        m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + +        pa_ioline_printf(c->line, +                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", +                         sink->monitor_source->name, m, t); + +        pa_xfree(t); +        pa_xfree(m); +    } + +    pa_ioline_puts(c->line, +                   "</p>\n" +                   "<h2>Sources</h2>\n" +                   "<p>\n"); + +    PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { +        char *t, *m; + +        if (source->monitor_of) +            continue; + +        t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); +        m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + +        pa_ioline_printf(c->line, +                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", +                         source->name, m, t); + +        pa_xfree(m); +        pa_xfree(t); + +    } + +    pa_ioline_puts(c->line, +                   "</p>\n" +                   HTML_FOOTER); + +    pa_ioline_defer_close(c->line); +} + +static void line_drain_callback(pa_ioline *l, void *userdata) { +    struct connection *c; + +    pa_assert(l); +    pa_assert_se(c = userdata); + +    /* We don't need the line reader anymore, instead we need a real +     * binary io channel */ +    pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line)); +    pa_iochannel_set_callback(c->io, io_callback, c); + +    pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));      pa_ioline_unref(c->line); -    pa_xfree(c); +    c->line = NULL; +} + +static void handle_listen_prefix(struct connection *c, const char *source_name) { +    pa_source *source; +    pa_source_output_new_data data; +    pa_sample_spec ss; +    pa_channel_map cm; +    char *t; +    size_t l; + +    pa_assert(c); +    pa_assert(source_name); + +    pa_assert(c->line); +    pa_assert(!c->io); + +    if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) { +        html_response(c, 404, "Source not found", NULL); +        return; +    } + +    ss = source->sample_spec; +    cm = source->channel_map; + +    pa_sample_spec_mimefy(&ss, &cm); + +    pa_source_output_new_data_init(&data); +    data.driver = __FILE__; +    data.module = c->module; +    data.client = c->client; +    data.source = source; +    pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); +    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_data_done(&data); + +    if (!c->source_output) { +        html_response(c, 403, "Cannot create source output", NULL); +        return; +    } + +    c->source_output->parent.process_msg = source_output_process_msg; +    c->source_output->push = source_output_push_cb; +    c->source_output->kill = source_output_kill_cb; +    c->source_output->get_latency = source_output_get_latency_cb; +    c->source_output->userdata = c; + +    pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + +    l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); +    c->output_memblockq = pa_memblockq_new( +            0, +            l, +            0, +            pa_frame_size(&ss), +            1, +            0, +            0, +            NULL); + +    pa_source_output_put(c->source_output); + +    t = pa_sample_spec_to_mime_type(&ss, &cm); +    http_response(c, 200, "OK", t); +    pa_xfree(t); + +    pa_ioline_set_callback(c->line, NULL, NULL); + +    if (pa_ioline_is_drained(c->line)) +        line_drain_callback(c->line, c); +    else +        pa_ioline_set_drain_callback(c->line, line_drain_callback, c); +} + +static void handle_url(struct connection *c) { +    pa_assert(c); + +    pa_log_debug("Request for %s", c->url); + +    if (pa_streq(c->url, URL_ROOT)) +        handle_root(c); +    else if (pa_streq(c->url, URL_CSS)) +        handle_css(c); +    else if (pa_streq(c->url, URL_STATUS)) +        handle_status(c); +    else if (pa_streq(c->url, URL_LISTEN)) +        handle_listen(c); +    else if (pa_startswith(c->url, URL_LISTEN_SOURCE)) +        handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1); +    else +        html_response(c, 404, "Not Found", NULL);  }  static void line_callback(pa_ioline *line, const char *s, void *userdata) { @@ -131,93 +605,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {      }      switch (c->state) { -        case REQUEST_LINE: { -            if (memcmp(s, "GET ", 4)) +        case STATE_REQUEST_LINE: { +            if (!pa_startswith(s, "GET "))                  goto fail;              s +=4;              c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); -            c->state = MIME_HEADER; +            c->state = STATE_MIME_HEADER;              break; -          } -        case MIME_HEADER: { +        case STATE_MIME_HEADER: {              /* Ignore MIME headers */              if (strcspn(s, " \r\n") != 0)                  break;              /* We're done */ -            c->state = DATA; - -            pa_log_info("request for %s", c->url); - -            if (!strcmp(c->url, URL_ROOT)) { -                char txt[256]; -                pa_sink *def_sink; -                pa_source *def_source; -                http_response(c, 200, "OK", "text/html"); - -                pa_ioline_puts(c->line, -                               "<?xml version=\"1.0\"?>\n" -                               "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" -                               "<html xmlns=\"http://www.w3.org/1999/xhtml\"><title>"PACKAGE_NAME" "PACKAGE_VERSION"</title>\n" -                               "<link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/></head><body>\n"); - -                pa_ioline_puts(c->line, -                               "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" -                               "<table>"); - -#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "<tr><td><b>%s</b></td><td>%s</td></tr>\n",(a),(b)) - -                PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt))); -                PRINTF_FIELD("Host name:", pa_get_host_name(txt, sizeof(txt))); -                PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec)); - -                def_sink = pa_namereg_get_default_sink(c->protocol->core); -                def_source = pa_namereg_get_default_source(c->protocol->core); - -                PRINTF_FIELD("Default Sink:", def_sink ? def_sink->name : "n/a"); -                PRINTF_FIELD("Default Source:", def_source ? def_source->name : "n/a"); - -                pa_ioline_puts(c->line, "</table>"); - -                pa_ioline_puts(c->line, "<p><a href=\"/status\">Click here</a> for an extensive server status report.</p>"); - -                pa_ioline_puts(c->line, "</body></html>\n"); - -                pa_ioline_defer_close(c->line); -            } else if (!strcmp(c->url, URL_CSS)) { -                http_response(c, 200, "OK", "text/css"); - -                pa_ioline_puts(c->line, -                               "body { color: black; background-color: white; margin: 0.5cm; }\n" -                               "a:link, a:visited { color: #900000; }\n" -                               "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n" -                               "h1 { color: #00009F; }\n" -                               "h2 { color: #00009F; }\n" -                               "ul { margin-left: .5cm; }\n" -                               "ol { margin-left: .5cm; }\n" -                               "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n" -                               ".grey { color: #afafaf; }\n" -                               "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" -                               "td { padding-left:10px; padding-right:10px;  }\n"); - -                pa_ioline_defer_close(c->line); -            } else if (!strcmp(c->url, URL_STATUS)) { -                char *r; - -                http_response(c, 200, "OK", "text/plain"); -                r = pa_full_status_string(c->protocol->core); -                pa_ioline_puts(c->line, r); -                pa_xfree(r); - -                pa_ioline_defer_close(c->line); -            } else -                http_message(c, 404, "Not Found", NULL); +            c->state = STATE_DATA; +            handle_url(c);              break;          } @@ -228,11 +636,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {      return;  fail: -    internal_server_error(c); +    html_response(c, 500, "Internal Server Error", NULL);  }  void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {      struct connection *c; +    pa_client_new_data client_data; +    char pname[128];      pa_assert(p);      pa_assert(io); @@ -244,26 +654,46 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *          return;      } -    c = pa_xnew(struct connection, 1); +    c = pa_xnew0(struct connection, 1);      c->protocol = p; -    c->line = pa_ioline_new(io); -    c->state = REQUEST_LINE; -    c->url = NULL; +    c->state = STATE_REQUEST_LINE;      c->module = m; +    c->line = pa_ioline_new(io);      pa_ioline_set_callback(c->line, line_callback, c); +    pa_client_new_data_init(&client_data); +    client_data.module = c->module; +    client_data.driver = __FILE__; +    pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); +    pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname); +    pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname); +    c->client = pa_client_new(p->core, &client_data); +    pa_client_new_data_done(&client_data); + +    if (!c->client) +        goto fail; + +    c->client->kill = client_kill_cb; +    c->client->userdata = c; +      pa_idxset_put(p->connections, c, NULL); + +    return; + +fail: +    if (c) +        connection_unlink(c);  }  void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {      struct connection *c; -    void *state = NULL; +    uint32_t idx;      pa_assert(p);      pa_assert(m); -    while ((c = pa_idxset_iterate(p->connections, &state, NULL))) +    PA_IDXSET_FOREACH(c, p->connections, idx)          if (c->module == m)              connection_unlink(c);  } @@ -273,7 +703,7 @@ static pa_http_protocol* http_protocol_new(pa_core *c) {      pa_assert(c); -    p = pa_xnew(pa_http_protocol, 1); +    p = pa_xnew0(pa_http_protocol, 1);      PA_REFCNT_INIT(p);      p->core = c;      p->connections = pa_idxset_new(NULL, NULL); @@ -315,7 +745,32 @@ void pa_http_protocol_unref(pa_http_protocol *p) {      pa_idxset_free(p->connections, NULL, NULL); +    pa_strlist_free(p->servers); +      pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0);      pa_xfree(p);  } + +void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) { +    pa_assert(p); +    pa_assert(PA_REFCNT_VALUE(p) >= 1); +    pa_assert(name); + +    p->servers = pa_strlist_prepend(p->servers, name); +} + +void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) { +    pa_assert(p); +    pa_assert(PA_REFCNT_VALUE(p) >= 1); +    pa_assert(name); + +    p->servers = pa_strlist_remove(p->servers, name); +} + +pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) { +    pa_assert(p); +    pa_assert(PA_REFCNT_VALUE(p) >= 1); + +    return p->servers; +} diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h index 40b3d82c..f7517e81 100644 --- a/src/pulsecore/protocol-http.h +++ b/src/pulsecore/protocol-http.h @@ -26,7 +26,7 @@  #include <pulsecore/module.h>  #include <pulsecore/modargs.h>  #include <pulsecore/iochannel.h> - +#include <pulsecore/strlist.h>  typedef struct pa_http_protocol pa_http_protocol; @@ -36,4 +36,8 @@ void pa_http_protocol_unref(pa_http_protocol *p);  void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m);  void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m); +void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name); +void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name); +pa_strlist *pa_http_protocol_servers(pa_http_protocol *p); +  #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 50a9191b..96184bd2 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -29,6 +29,7 @@  #include <stdlib.h>  #include <unistd.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/version.h>  #include <pulse/utf8.h> @@ -61,7 +62,7 @@  #include "protocol-native.h"  /* Kick a client if it doesn't authenticate within this time */ -#define AUTH_TIMEOUT 60 +#define AUTH_TIMEOUT (60 * PA_USEC_PER_SEC)  /* Don't accept more connection than this */  #define MAX_CONNECTIONS 64 @@ -81,8 +82,20 @@ typedef struct record_stream {      pa_source_output *source_output;      pa_memblockq *memblockq; -    size_t fragment_size; -    pa_usec_t source_latency; + +    pa_bool_t adjust_latency:1; +    pa_bool_t early_requests:1; + +    pa_buffer_attr buffer_attr; + +    pa_atomic_t on_the_fly; +    pa_usec_t configured_source_latency; +    size_t drop_initial; + +    /* Only updated after SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY */ +    size_t on_the_fly_snapshot; +    pa_usec_t current_monitor_latency; +    pa_usec_t current_source_latency;  } record_stream;  PA_DECLARE_CLASS(record_stream); @@ -105,18 +118,24 @@ typedef struct playback_stream {      pa_sink_input *sink_input;      pa_memblockq *memblockq; + +    pa_bool_t adjust_latency:1; +    pa_bool_t early_requests:1; +      pa_bool_t is_underrun:1;      pa_bool_t drain_request:1;      uint32_t drain_tag;      uint32_t syncid;      pa_atomic_t missing; -    size_t minreq; -    pa_usec_t sink_latency; +    pa_usec_t configured_sink_latency; +    pa_buffer_attr buffer_attr;      /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */      int64_t read_index, write_index;      size_t render_memblockq_length; +    pa_usec_t current_sink_latency; +    uint64_t playing_for, underrun_for;  } playback_stream;  PA_DECLARE_CLASS(playback_stream); @@ -174,13 +193,18 @@ struct pa_native_protocol {  };  enum { +    SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +enum {      SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */      SINK_INPUT_MESSAGE_DRAIN, /* disabled prebuf, get playback started. */      SINK_INPUT_MESSAGE_FLUSH,      SINK_INPUT_MESSAGE_TRIGGER,      SINK_INPUT_MESSAGE_SEEK,      SINK_INPUT_MESSAGE_PREBUF_FORCE, -    SINK_INPUT_MESSAGE_UPDATE_LATENCY +    SINK_INPUT_MESSAGE_UPDATE_LATENCY, +    SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR  };  enum { @@ -188,7 +212,8 @@ enum {      PLAYBACK_STREAM_MESSAGE_UNDERFLOW,      PLAYBACK_STREAM_MESSAGE_OVERFLOW,      PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, -    PLAYBACK_STREAM_MESSAGE_STARTED +    PLAYBACK_STREAM_MESSAGE_STARTED, +    PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH  };  enum { @@ -203,7 +228,7 @@ enum {  static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);  static void sink_input_kill_cb(pa_sink_input *i);  static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend); -static void sink_input_moved_cb(pa_sink_input *i); +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest);  static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes);  static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes);  static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes); @@ -215,11 +240,12 @@ static void playback_stream_request_bytes(struct playback_stream*s);  static void source_output_kill_cb(pa_source_output *o);  static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk);  static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend); -static void source_output_moved_cb(pa_source_output *o); +static void source_output_moving_cb(pa_source_output *o, pa_source *dest);  static pa_usec_t source_output_get_latency_cb(pa_source_output *o);  static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl);  static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +static int source_output_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);  static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); @@ -259,6 +285,7 @@ static void command_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t  static void command_remove_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_ERROR] = NULL, @@ -355,11 +382,15 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_SET_CARD_PROFILE] = command_set_card_profile, +    [PA_COMMAND_SET_SINK_PORT] = command_set_sink_or_source_port, +    [PA_COMMAND_SET_SOURCE_PORT] = command_set_sink_or_source_port, +      [PA_COMMAND_EXTENSION] = command_extension  };  /* structure management */ +/* Called from main context */  static void upload_stream_unlink(upload_stream *s) {      pa_assert(s); @@ -371,6 +402,7 @@ static void upload_stream_unlink(upload_stream *s) {      upload_stream_unref(s);  } +/* Called from main context */  static void upload_stream_free(pa_object *o) {      upload_stream *s = UPLOAD_STREAM(o);      pa_assert(s); @@ -388,6 +420,7 @@ static void upload_stream_free(pa_object *o) {      pa_xfree(s);  } +/* Called from main context */  static upload_stream* upload_stream_new(          pa_native_connection *c,          const pa_sample_spec *ss, @@ -420,6 +453,7 @@ static upload_stream* upload_stream_new(      return s;  } +/* Called from main context */  static void record_stream_unlink(record_stream *s) {      pa_assert(s); @@ -437,6 +471,7 @@ static void record_stream_unlink(record_stream *s) {      record_stream_unref(s);  } +/* Called from main context */  static void record_stream_free(pa_object *o) {      record_stream *s = RECORD_STREAM(o);      pa_assert(s); @@ -447,6 +482,7 @@ static void record_stream_free(pa_object *o) {      pa_xfree(s);  } +/* Called from main context */  static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {      record_stream *s = RECORD_STREAM(o);      record_stream_assert_ref(s); @@ -458,6 +494,10 @@ static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, i          case RECORD_STREAM_MESSAGE_POST_DATA: +            /* We try to keep up to date with how many bytes are +             * currently on the fly */ +            pa_atomic_sub(&s->on_the_fly, chunk->length); +              if (pa_memblockq_push_align(s->memblockq, chunk) < 0) {  /*                 pa_log_warn("Failed to push data into output queue."); */                  return -1; @@ -472,35 +512,34 @@ static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, i      return 0;  } -static void fix_record_buffer_attr_pre( -        record_stream *s, -        pa_bool_t adjust_latency, -        pa_bool_t early_requests, -        uint32_t *maxlength, -        uint32_t *fragsize) { +/* Called from main context */ +static void fix_record_buffer_attr_pre(record_stream *s) {      size_t frame_size;      pa_usec_t orig_fragsize_usec, fragsize_usec, source_usec;      pa_assert(s); -    pa_assert(maxlength); -    pa_assert(fragsize); + +    /* This function will be called from the main thread, before as +     * well as after the source output has been activated using +     * pa_source_output_put()! That means it may not touch any +     * ->thread_info data! */      frame_size = pa_frame_size(&s->source_output->sample_spec); -    if (*maxlength == (uint32_t) -1 || *maxlength > MAX_MEMBLOCKQ_LENGTH) -        *maxlength = MAX_MEMBLOCKQ_LENGTH; -    if (*maxlength <= 0) -        *maxlength = (uint32_t) frame_size; +    if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH) +        s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH; +    if (s->buffer_attr.maxlength <= 0) +        s->buffer_attr.maxlength = (uint32_t) frame_size; -    if (*fragsize == (uint32_t) -1) -        *fragsize = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec); -    if (*fragsize <= 0) -        *fragsize = (uint32_t) frame_size; +    if (s->buffer_attr.fragsize == (uint32_t) -1) +        s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec); +    if (s->buffer_attr.fragsize <= 0) +        s->buffer_attr.fragsize = (uint32_t) frame_size; -    orig_fragsize_usec = fragsize_usec = pa_bytes_to_usec(*fragsize, &s->source_output->sample_spec); +    orig_fragsize_usec = fragsize_usec = pa_bytes_to_usec(s->buffer_attr.fragsize, &s->source_output->sample_spec); -    if (early_requests) { +    if (s->early_requests) {          /* In early request mode we need to emulate the classic           * fragment-based playback model. We do this setting the source @@ -508,7 +547,7 @@ static void fix_record_buffer_attr_pre(          source_usec = fragsize_usec; -    } else if (adjust_latency) { +    } else if (s->adjust_latency) {          /* So, the user asked us to adjust the latency according to           * what the source can provide. Half the latency will be @@ -522,73 +561,69 @@ static void fix_record_buffer_attr_pre(          /* Ok, the user didn't ask us to adjust the latency, hence we           * don't */ -        source_usec = 0; +        source_usec = (pa_usec_t) -1;      } -    if (source_usec > 0) -        s->source_latency = pa_source_output_set_requested_latency(s->source_output, source_usec); +    if (source_usec != (pa_usec_t) -1) +        s->configured_source_latency = pa_source_output_set_requested_latency(s->source_output, source_usec);      else -        s->source_latency = 0; +        s->configured_source_latency = 0; -    if (early_requests) { +    if (s->early_requests) {          /* Ok, we didn't necessarily get what we were asking for, so           * let's tell the user */ -        fragsize_usec = s->source_latency; +        fragsize_usec = s->configured_source_latency; -    } else if (adjust_latency) { +    } else if (s->adjust_latency) {          /* Now subtract what we actually got */ -        if (fragsize_usec >= s->source_latency*2) -            fragsize_usec -= s->source_latency; +        if (fragsize_usec >= s->configured_source_latency*2) +            fragsize_usec -= s->configured_source_latency;          else -            fragsize_usec = s->source_latency; +            fragsize_usec = s->configured_source_latency;      }      if (pa_usec_to_bytes(orig_fragsize_usec, &s->source_output->sample_spec) !=          pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec)) -        *fragsize = (uint32_t) pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec); +        s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec); -    if (*fragsize <= 0) -        *fragsize = (uint32_t) frame_size; +    if (s->buffer_attr.fragsize <= 0) +        s->buffer_attr.fragsize = (uint32_t) frame_size;  } -static void fix_record_buffer_attr_post( -        record_stream *s, -        uint32_t *maxlength, -        uint32_t *fragsize) { - +/* Called from main context */ +static void fix_record_buffer_attr_post(record_stream *s) {      size_t base;      pa_assert(s); -    pa_assert(maxlength); -    pa_assert(fragsize); -    *maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); +    /* This function will be called from the main thread, before as +     * well as after the source output has been activated using +     * pa_source_output_put()! That means it may not touch and +     * ->thread_info data! */      base = pa_frame_size(&s->source_output->sample_spec); -    s->fragment_size = (*fragsize/base)*base; -    if (s->fragment_size <= 0) -        s->fragment_size = base; - -    if (s->fragment_size > *maxlength) -        s->fragment_size = *maxlength; +    s->buffer_attr.fragsize = (s->buffer_attr.fragsize/base)*base; +    if (s->buffer_attr.fragsize <= 0) +        s->buffer_attr.fragsize = base; -    *fragsize = (uint32_t) s->fragment_size; +    if (s->buffer_attr.fragsize > s->buffer_attr.maxlength) +        s->buffer_attr.fragsize = s->buffer_attr.maxlength;  } +/* Called from main context */  static record_stream* record_stream_new(          pa_native_connection *c,          pa_source *source,          pa_sample_spec *ss,          pa_channel_map *map,          pa_bool_t peak_detect, -        uint32_t *maxlength, -        uint32_t *fragsize, +        pa_buffer_attr *attr,          pa_source_output_flags_t flags,          pa_proplist *p,          pa_bool_t adjust_latency, @@ -603,7 +638,6 @@ static record_stream* record_stream_new(      pa_assert(c);      pa_assert(ss); -    pa_assert(maxlength);      pa_assert(p);      pa_assert(ret); @@ -632,20 +666,25 @@ static record_stream* record_stream_new(      s->parent.process_msg = record_stream_process_msg;      s->connection = c;      s->source_output = source_output; +    s->buffer_attr = *attr; +    s->adjust_latency = adjust_latency; +    s->early_requests = early_requests; +    pa_atomic_store(&s->on_the_fly, 0); +    s->source_output->parent.process_msg = source_output_process_msg;      s->source_output->push = source_output_push_cb;      s->source_output->kill = source_output_kill_cb;      s->source_output->get_latency = source_output_get_latency_cb; -    s->source_output->moved = source_output_moved_cb; +    s->source_output->moving = source_output_moving_cb;      s->source_output->suspend = source_output_suspend_cb;      s->source_output->send_event = source_output_send_event_cb;      s->source_output->userdata = s; -    fix_record_buffer_attr_pre(s, adjust_latency, early_requests, maxlength, fragsize); +    fix_record_buffer_attr_pre(s);      s->memblockq = pa_memblockq_new(              0, -            *maxlength, +            s->buffer_attr.maxlength,              0,              base = pa_frame_size(&source_output->sample_spec),              1, @@ -653,7 +692,8 @@ static record_stream* record_stream_new(              0,              NULL); -    fix_record_buffer_attr_post(s, maxlength, fragsize); +    pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); +    fix_record_buffer_attr_post(s);      *ss = s->source_output->sample_spec;      *map = s->source_output->channel_map; @@ -661,14 +701,15 @@ static record_stream* record_stream_new(      pa_idxset_put(c->record_streams, s, &s->index);      pa_log_info("Final latency %0.2f ms = %0.2f ms + %0.2f ms", -                ((double) pa_bytes_to_usec(s->fragment_size, &source_output->sample_spec) + (double) s->source_latency) / PA_USEC_PER_MSEC, -                (double) pa_bytes_to_usec(s->fragment_size, &source_output->sample_spec) / PA_USEC_PER_MSEC, -                (double) s->source_latency / PA_USEC_PER_MSEC); +                ((double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) + (double) s->configured_source_latency) / PA_USEC_PER_MSEC, +                (double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) / PA_USEC_PER_MSEC, +                (double) s->configured_source_latency / PA_USEC_PER_MSEC);      pa_source_output_put(s->source_output);      return s;  } +/* Called from main context */  static void record_stream_send_killed(record_stream *r) {      pa_tagstruct *t;      record_stream_assert_ref(r); @@ -680,6 +721,7 @@ static void record_stream_send_killed(record_stream *r) {      pa_pstream_send_tagstruct(r->connection->pstream, t);  } +/* Called from main context */  static void playback_stream_unlink(playback_stream *s) {      pa_assert(s); @@ -700,6 +742,7 @@ static void playback_stream_unlink(playback_stream *s) {      playback_stream_unref(s);  } +/* Called from main context */  static void playback_stream_free(pa_object* o) {      playback_stream *s = PLAYBACK_STREAM(o);      pa_assert(s); @@ -710,6 +753,7 @@ static void playback_stream_free(pa_object* o) {      pa_xfree(s);  } +/* Called from main context */  static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {      playback_stream *s = PLAYBACK_STREAM(o);      playback_stream_assert_ref(s); @@ -720,24 +764,21 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,      switch (code) {          case PLAYBACK_STREAM_MESSAGE_REQUEST_DATA: {              pa_tagstruct *t; -            uint32_t l = 0; +            int l = 0;              for (;;) { -                if ((l = (uint32_t) pa_atomic_load(&s->missing)) <= 0) -                    break; +                if ((l = pa_atomic_load(&s->missing)) <= 0) +                    return 0; -                if (pa_atomic_cmpxchg(&s->missing, (int) l, 0)) +                if (pa_atomic_cmpxchg(&s->missing, l, 0))                      break;              } -            if (l <= 0) -                break; -              t = pa_tagstruct_new(NULL, 0);              pa_tagstruct_putu32(t, PA_COMMAND_REQUEST);              pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */              pa_tagstruct_putu32(t, s->index); -            pa_tagstruct_putu32(t, l); +            pa_tagstruct_putu32(t, (uint32_t) l);              pa_pstream_send_tagstruct(s->connection->pstream, t);  /*             pa_log("Requesting %lu bytes", (unsigned long) l); */ @@ -775,7 +816,7 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,              if (s->connection->version >= 13) {                  pa_tagstruct *t; -                /* Notify the user we're overflowed*/ +                /* Notify the user we started playback */                  t = pa_tagstruct_new(NULL, 0);                  pa_tagstruct_putu32(t, PA_COMMAND_STARTED);                  pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ @@ -788,67 +829,79 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,          case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK:              pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata));              break; + +        case PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH: { +            pa_tagstruct *t; + +            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); + +            break; +        }      }      return 0;  } -static void fix_playback_buffer_attr_pre( -        playback_stream *s, -        pa_bool_t adjust_latency, -        pa_bool_t early_requests, -        uint32_t *maxlength, -        uint32_t *tlength, -        uint32_t* prebuf, -        uint32_t* minreq) { - -    size_t frame_size; +/* Called from main context */ +static void fix_playback_buffer_attr(playback_stream *s) { +    size_t frame_size, max_prebuf;      pa_usec_t orig_tlength_usec, tlength_usec, orig_minreq_usec, minreq_usec, sink_usec;      pa_assert(s); -    pa_assert(maxlength); -    pa_assert(tlength); -    pa_assert(prebuf); -    pa_assert(minreq); + +    /* This function will be called from the main thread, before as +     * well as after the sink input has been activated using +     * pa_sink_input_put()! That means it may not touch any +     * ->thread_info data, such as the memblockq! */      frame_size = pa_frame_size(&s->sink_input->sample_spec); -    if (*maxlength == (uint32_t) -1 || *maxlength > MAX_MEMBLOCKQ_LENGTH) -        *maxlength = MAX_MEMBLOCKQ_LENGTH; -    if (*maxlength <= 0) -        *maxlength = (uint32_t) frame_size; +    if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH) +        s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH; +    if (s->buffer_attr.maxlength <= 0) +        s->buffer_attr.maxlength = (uint32_t) frame_size; -    if (*tlength == (uint32_t) -1) -        *tlength = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); -    if (*tlength <= 0) -        *tlength = (uint32_t) frame_size; +    if (s->buffer_attr.tlength == (uint32_t) -1) +        s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); +    if (s->buffer_attr.tlength <= 0) +        s->buffer_attr.tlength = (uint32_t) frame_size; -    if (*minreq == (uint32_t) -1) -        *minreq = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); -    if (*minreq <= 0) -        *minreq = (uint32_t) frame_size; +    if (s->buffer_attr.minreq == (uint32_t) -1) +        s->buffer_attr.minreq = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); +    if (s->buffer_attr.minreq <= 0) +        s->buffer_attr.minreq = (uint32_t) frame_size; -    if (*tlength < *minreq+frame_size) -        *tlength = *minreq+(uint32_t) frame_size; +    if (s->buffer_attr.tlength < s->buffer_attr.minreq+frame_size) +        s->buffer_attr.tlength = s->buffer_attr.minreq+(uint32_t) frame_size; -    orig_tlength_usec = tlength_usec = pa_bytes_to_usec(*tlength, &s->sink_input->sample_spec); -    orig_minreq_usec = minreq_usec = pa_bytes_to_usec(*minreq, &s->sink_input->sample_spec); +    orig_tlength_usec = tlength_usec = pa_bytes_to_usec(s->buffer_attr.tlength, &s->sink_input->sample_spec); +    orig_minreq_usec = minreq_usec = pa_bytes_to_usec(s->buffer_attr.minreq, &s->sink_input->sample_spec);      pa_log_info("Requested tlength=%0.2f ms, minreq=%0.2f ms",                  (double) tlength_usec / PA_USEC_PER_MSEC,                  (double) minreq_usec / PA_USEC_PER_MSEC); -    if (early_requests) { +    if (s->early_requests) {          /* In early request mode we need to emulate the classic           * fragment-based playback model. We do this setting the sink           * latency to the fragment size. */          sink_usec = minreq_usec; -          pa_log_debug("Early requests mode enabled, configuring sink latency to minreq."); -    } else if (adjust_latency) { +    } else if (s->adjust_latency) {          /* So, the user asked us to adjust the latency of the stream           * buffer according to the what the sink can provide. The @@ -888,80 +941,60 @@ static void fix_playback_buffer_attr_pre(          pa_log_debug("Traditional mode enabled, modifying sink usec only for compat with minreq.");      } -    s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec); +    s->configured_sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec); -    if (early_requests) { +    if (s->early_requests) {          /* Ok, we didn't necessarily get what we were asking for, so           * let's tell the user */ -        minreq_usec = s->sink_latency; +        minreq_usec = s->configured_sink_latency; -    } else if (adjust_latency) { +    } else if (s->adjust_latency) {          /* Ok, we didn't necessarily get what we were asking for, so           * let's subtract from what we asked for for the remaining           * buffer space */ -        if (tlength_usec >= s->sink_latency) -            tlength_usec -= s->sink_latency; +        if (tlength_usec >= s->configured_sink_latency) +            tlength_usec -= s->configured_sink_latency;      }      /* FIXME: This is actually larger than necessary, since not all of       * the sink latency is actually rewritable. */ -    if (tlength_usec < s->sink_latency + 2*minreq_usec) -        tlength_usec = s->sink_latency + 2*minreq_usec; +    if (tlength_usec < s->configured_sink_latency + 2*minreq_usec) +        tlength_usec = s->configured_sink_latency + 2*minreq_usec;      if (pa_usec_to_bytes_round_up(orig_tlength_usec, &s->sink_input->sample_spec) !=          pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec)) -        *tlength = (uint32_t) pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec); +        s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec);      if (pa_usec_to_bytes(orig_minreq_usec, &s->sink_input->sample_spec) !=          pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec)) -        *minreq = (uint32_t) pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec); +        s->buffer_attr.minreq = (uint32_t) pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec); -    if (*minreq <= 0) { -        *minreq = (uint32_t) frame_size; -        *tlength += (uint32_t) frame_size*2; +    if (s->buffer_attr.minreq <= 0) { +        s->buffer_attr.minreq = (uint32_t) frame_size; +        s->buffer_attr.tlength += (uint32_t) frame_size*2;      } -    if (*tlength <= *minreq) -        *tlength = *minreq*2 + (uint32_t) frame_size; - -    if (*prebuf == (uint32_t) -1 || *prebuf > *tlength) -        *prebuf = *tlength; -} - -static void fix_playback_buffer_attr_post( -        playback_stream *s, -        uint32_t *maxlength, -        uint32_t *tlength, -        uint32_t* prebuf, -        uint32_t* minreq) { - -    pa_assert(s); -    pa_assert(maxlength); -    pa_assert(tlength); -    pa_assert(prebuf); -    pa_assert(minreq); +    if (s->buffer_attr.tlength <= s->buffer_attr.minreq) +        s->buffer_attr.tlength = s->buffer_attr.minreq*2 + (uint32_t) frame_size; -    *maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); -    *tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq); -    *prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq); -    *minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq); +    max_prebuf = s->buffer_attr.tlength + (uint32_t)frame_size - s->buffer_attr.minreq; -    s->minreq = *minreq; +    if (s->buffer_attr.prebuf == (uint32_t) -1 || +        s->buffer_attr.prebuf > max_prebuf) +        s->buffer_attr.prebuf = max_prebuf;  } +/* Called from main context */  static playback_stream* playback_stream_new(          pa_native_connection *c,          pa_sink *sink,          pa_sample_spec *ss,          pa_channel_map *map, -        uint32_t *maxlength, -        uint32_t *tlength, -        uint32_t *prebuf, -        uint32_t *minreq, +        pa_buffer_attr *a,          pa_cvolume *volume,          pa_bool_t muted,          pa_bool_t muted_set, @@ -982,10 +1015,6 @@ static playback_stream* playback_stream_new(      pa_assert(c);      pa_assert(ss); -    pa_assert(maxlength); -    pa_assert(tlength); -    pa_assert(prebuf); -    pa_assert(minreq);      pa_assert(missing);      pa_assert(p);      pa_assert(ret); @@ -1042,6 +1071,9 @@ static playback_stream* playback_stream_new(      s->is_underrun = TRUE;      s->drain_request = FALSE;      pa_atomic_store(&s->missing, 0); +    s->buffer_attr = *a; +    s->adjust_latency = adjust_latency; +    s->early_requests = early_requests;      s->sink_input->parent.process_msg = sink_input_process_msg;      s->sink_input->pop = sink_input_pop_cb; @@ -1049,28 +1081,28 @@ static playback_stream* playback_stream_new(      s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;      s->sink_input->update_max_request = sink_input_update_max_request_cb;      s->sink_input->kill = sink_input_kill_cb; -    s->sink_input->moved = sink_input_moved_cb; +    s->sink_input->moving = sink_input_moving_cb;      s->sink_input->suspend = sink_input_suspend_cb;      s->sink_input->send_event = sink_input_send_event_cb;      s->sink_input->userdata = s;      start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; -    fix_playback_buffer_attr_pre(s, adjust_latency, early_requests, maxlength, tlength, prebuf, minreq); -    pa_sink_input_get_silence(sink_input, &silence); +    fix_playback_buffer_attr(s); +    pa_sink_input_get_silence(sink_input, &silence);      s->memblockq = pa_memblockq_new(              start_index, -            *maxlength, -            *tlength, +            s->buffer_attr.maxlength, +            s->buffer_attr.tlength,              pa_frame_size(&sink_input->sample_spec), -            *prebuf, -            *minreq, +            s->buffer_attr.prebuf, +            s->buffer_attr.minreq,              0,              &silence); -      pa_memblock_unref(silence.memblock); -    fix_playback_buffer_attr_post(s, maxlength, tlength, prebuf, minreq); + +    pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);      *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq); @@ -1080,18 +1112,19 @@ static playback_stream* playback_stream_new(      pa_idxset_put(c->output_streams, s, &s->index);      pa_log_info("Final latency %0.2f ms = %0.2f ms + 2*%0.2f ms + %0.2f ms", -                ((double) pa_bytes_to_usec(*tlength, &sink_input->sample_spec) + (double) s->sink_latency) / PA_USEC_PER_MSEC, -                (double) pa_bytes_to_usec(*tlength-*minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC, -                (double) pa_bytes_to_usec(*minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC, -                (double) s->sink_latency / PA_USEC_PER_MSEC); +                ((double) pa_bytes_to_usec(s->buffer_attr.tlength, &sink_input->sample_spec) + (double) s->configured_sink_latency) / PA_USEC_PER_MSEC, +                (double) pa_bytes_to_usec(s->buffer_attr.tlength-s->buffer_attr.minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC, +                (double) pa_bytes_to_usec(s->buffer_attr.minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC, +                (double) s->configured_sink_latency / PA_USEC_PER_MSEC);      pa_sink_input_put(s->sink_input);      return s;  } -/* Called from thread context */ +/* Called from IO context */  static void playback_stream_request_bytes(playback_stream *s) { -    size_t m, previous_missing; +    size_t m, minreq; +    int previous_missing;      playback_stream_assert_ref(s); @@ -1102,14 +1135,16 @@ static void playback_stream_request_bytes(playback_stream *s) {  /*     pa_log("request_bytes(%lu)", (unsigned long) m); */ -    previous_missing = (size_t) pa_atomic_add(&s->missing, (int) m); +    previous_missing = pa_atomic_add(&s->missing, (int) m); +    minreq = pa_memblockq_get_minreq(s->memblockq);      if (pa_memblockq_prebuf_active(s->memblockq) || -        (previous_missing < s->minreq && previous_missing+m >= s->minreq)) +        (previous_missing < (int) minreq && previous_missing + (int) m >= (int) minreq))          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;      playback_stream_assert_ref(p); @@ -1121,6 +1156,7 @@ static void playback_stream_send_killed(playback_stream *p) {      pa_pstream_send_tagstruct(p->connection->pstream, t);  } +/* Called from main context */  static int native_connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {      pa_native_connection *c = PA_NATIVE_CONNECTION(o);      pa_native_connection_assert_ref(c); @@ -1142,6 +1178,7 @@ static int native_connection_process_msg(pa_msgobject *o, int code, void*userdat      return 0;  } +/* Called from main context */  static void native_connection_unlink(pa_native_connection *c) {      record_stream *r;      output_stream *o; @@ -1181,6 +1218,7 @@ static void native_connection_unlink(pa_native_connection *c) {      pa_native_connection_unref(c);  } +/* Called from main context */  static void native_connection_free(pa_object *o) {      pa_native_connection *c = PA_NATIVE_CONNECTION(o); @@ -1198,6 +1236,7 @@ static void native_connection_free(pa_object *o) {      pa_xfree(c);  } +/* Called from main context */  static void native_connection_send_memblock(pa_native_connection *c) {      uint32_t start;      record_stream *r; @@ -1217,8 +1256,8 @@ static void native_connection_send_memblock(pa_native_connection *c) {          if (pa_memblockq_peek(r->memblockq,  &chunk) >= 0) {              pa_memchunk schunk = chunk; -            if (schunk.length > r->fragment_size) -                schunk.length = r->fragment_size; +            if (schunk.length > r->buffer_attr.fragsize) +                schunk.length = r->buffer_attr.fragsize;              pa_pstream_send_memblock(c->pstream, r->index, 0, PA_SEEK_RELATIVE, &schunk); @@ -1232,6 +1271,7 @@ static void native_connection_send_memblock(pa_native_connection *c) {  /*** sink input callbacks ***/ +/* Called from thread context */  static void handle_seek(playback_stream *s, int64_t indexw) {      playback_stream_assert_ref(s); @@ -1284,7 +1324,12 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int              int64_t windex;              windex = pa_memblockq_get_write_index(s->memblockq); -            pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata)); + +            /* The client side is incapable of accounting correctly +             * for seeks of a type != PA_SEEK_RELATIVE. We need to be +             * able to deal with that. */ + +            pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata), PA_PTR_TO_UINT(userdata) == PA_SEEK_RELATIVE);              handle_seek(s, windex);              return 0; @@ -1302,7 +1347,7 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int              if (pa_memblockq_push_align(s->memblockq, chunk) < 0) {                  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); +                pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, TRUE);              }              handle_seek(s, windex); @@ -1371,10 +1416,14 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int          }          case SINK_INPUT_MESSAGE_UPDATE_LATENCY: - +            /* Atomically get a snapshot of all timing parameters... */              s->read_index = pa_memblockq_get_read_index(s->memblockq);              s->write_index = pa_memblockq_get_write_index(s->memblockq);              s->render_memblockq_length = pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq); +            s->current_sink_latency = pa_sink_get_latency_within_thread(s->sink_input->sink); +            s->underrun_for = s->sink_input->thread_info.underrun_for; +            s->playing_for = s->sink_input->thread_info.playing_for; +              return 0;          case PA_SINK_INPUT_MESSAGE_SET_STATE: { @@ -1399,6 +1448,12 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int               * latency added by the resampler */              break;          } + +        case SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR: { +            pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); +            pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); +            return 0; +        }      }      return pa_sink_input_process_msg(o, code, userdata, offset, chunk); @@ -1418,7 +1473,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk      if (pa_memblockq_is_readable(s->memblockq))          s->is_underrun = FALSE;      else { -/*         pa_log("%s, UNDERRUN: %lu", pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME), (unsigned long) pa_memblockq_get_length(s->memblockq)); */ +        if (!s->is_underrun) +            pa_log_debug("Underrun on '%s', %lu bytes in queue.", pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), (unsigned long) pa_memblockq_get_length(s->memblockq));          if (s->drain_request && pa_sink_input_safe_to_remove(i)) {              s->drain_request = FALSE; @@ -1447,6 +1503,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk      return 0;  } +/* Called from thread context */  static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {      playback_stream *s; @@ -1461,6 +1518,7 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {      pa_memblockq_rewind(s->memblockq, nbytes);  } +/* Called from thread context */  static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {      playback_stream *s; @@ -1471,18 +1529,30 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {      pa_memblockq_set_maxrewind(s->memblockq, nbytes);  } +/* Called from thread context */  static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {      playback_stream *s; -    size_t tlength; +    size_t new_tlength, old_tlength;      pa_sink_input_assert_ref(i);      s = PLAYBACK_STREAM(i->userdata);      playback_stream_assert_ref(s); -    tlength = nbytes+2*pa_memblockq_get_minreq(s->memblockq); +    old_tlength = pa_memblockq_get_tlength(s->memblockq); +    new_tlength = nbytes+2*pa_memblockq_get_minreq(s->memblockq); + +    if (old_tlength < new_tlength) { +        pa_log_debug("max_request changed, trying to update from %zu to %zu.", old_tlength, new_tlength); +        pa_memblockq_set_tlength(s->memblockq, new_tlength); +        new_tlength = pa_memblockq_get_tlength(s->memblockq); -    if (pa_memblockq_get_tlength(s->memblockq) < tlength) -        pa_memblockq_set_tlength(s->memblockq, tlength); +        if (new_tlength == old_tlength) +            pa_log_debug("Failed to increase tlength"); +        else { +            pa_log_debug("Notifying client about increased tlength"); +            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH, NULL, pa_memblockq_get_tlength(s->memblockq), NULL, NULL); +        } +    }  }  /* Called from main context */ @@ -1539,26 +1609,17 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend) {  }  /* Called from main context */ -static void sink_input_moved_cb(pa_sink_input *i) { +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {      playback_stream *s;      pa_tagstruct *t; -    uint32_t maxlength, tlength, prebuf, minreq;      pa_sink_input_assert_ref(i);      s = PLAYBACK_STREAM(i->userdata);      playback_stream_assert_ref(s); -    maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); -    tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq); -    prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq); -    minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq); - -    fix_playback_buffer_attr_pre(s, TRUE, FALSE, &maxlength, &tlength, &prebuf, &minreq); -    pa_memblockq_set_maxlength(s->memblockq, maxlength); -    pa_memblockq_set_tlength(s->memblockq, tlength); -    pa_memblockq_set_prebuf(s->memblockq, prebuf); -    pa_memblockq_set_minreq(s->memblockq, minreq); -    fix_playback_buffer_attr_post(s, &maxlength, &tlength, &prebuf, &minreq); +    fix_playback_buffer_attr(s); +    pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); +    pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);      if (s->connection->version < 12)        return; @@ -1567,16 +1628,16 @@ static void sink_input_moved_cb(pa_sink_input *i) {      pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED);      pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */      pa_tagstruct_putu32(t, s->index); -    pa_tagstruct_putu32(t, i->sink->index); -    pa_tagstruct_puts(t, i->sink->name); -    pa_tagstruct_put_boolean(t, pa_sink_get_state(i->sink) == PA_SINK_SUSPENDED); +    pa_tagstruct_putu32(t, dest->index); +    pa_tagstruct_puts(t, dest->name); +    pa_tagstruct_put_boolean(t, pa_sink_get_state(dest) == PA_SINK_SUSPENDED);      if (s->connection->version >= 13) { -        pa_tagstruct_putu32(t, maxlength); -        pa_tagstruct_putu32(t, tlength); -        pa_tagstruct_putu32(t, prebuf); -        pa_tagstruct_putu32(t, minreq); -        pa_tagstruct_put_usec(t, s->sink_latency); +        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); @@ -1585,6 +1646,27 @@ static void sink_input_moved_cb(pa_sink_input *i) {  /*** source_output callbacks ***/  /* Called from thread context */ +static int source_output_process_msg(pa_msgobject *_o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { +    pa_source_output *o = PA_SOURCE_OUTPUT(_o); +    record_stream *s; + +    pa_source_output_assert_ref(o); +    s = RECORD_STREAM(o->userdata); +    record_stream_assert_ref(s); + +    switch (code) { +        case SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY: +            /* Atomically get a snapshot of all timing parameters... */ +            s->current_monitor_latency = o->source->monitor_of ? pa_sink_get_latency_within_thread(o->source->monitor_of) : 0; +            s->current_source_latency = pa_source_get_latency_within_thread(o->source); +            s->on_the_fly_snapshot = pa_atomic_load(&s->on_the_fly); +            return 0; +    } + +    return pa_source_output_process_msg(_o, code, userdata, offset, chunk); +} + +/* Called from thread context */  static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {      record_stream *s; @@ -1593,6 +1675,7 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)      record_stream_assert_ref(s);      pa_assert(chunk); +    pa_atomic_add(&s->on_the_fly, chunk->length);      pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), RECORD_STREAM_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);  } @@ -1661,21 +1744,18 @@ static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend) {  }  /* Called from main context */ -static void source_output_moved_cb(pa_source_output *o) { +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {      record_stream *s;      pa_tagstruct *t; -    uint32_t maxlength, fragsize;      pa_source_output_assert_ref(o);      s = RECORD_STREAM(o->userdata);      record_stream_assert_ref(s); -    fragsize = (uint32_t) s->fragment_size; -    maxlength = (uint32_t) pa_memblockq_get_length(s->memblockq); - -    fix_record_buffer_attr_pre(s, TRUE, FALSE, &maxlength, &fragsize); -    pa_memblockq_set_maxlength(s->memblockq, maxlength); -    fix_record_buffer_attr_post(s, &maxlength, &fragsize); +    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); +    fix_record_buffer_attr_post(s);      if (s->connection->version < 12)        return; @@ -1684,14 +1764,14 @@ static void source_output_moved_cb(pa_source_output *o) {      pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED);      pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */      pa_tagstruct_putu32(t, s->index); -    pa_tagstruct_putu32(t, o->source->index); -    pa_tagstruct_puts(t, o->source->name); -    pa_tagstruct_put_boolean(t, pa_source_get_state(o->source) == PA_SOURCE_SUSPENDED); +    pa_tagstruct_putu32(t, dest->index); +    pa_tagstruct_puts(t, dest->name); +    pa_tagstruct_put_boolean(t, pa_source_get_state(dest) == PA_SOURCE_SUSPENDED);      if (s->connection->version >= 13) { -        pa_tagstruct_putu32(t, maxlength); -        pa_tagstruct_putu32(t, fragsize); -        pa_tagstruct_put_usec(t, s->source_latency); +        pa_tagstruct_putu32(t, s->buffer_attr.maxlength); +        pa_tagstruct_putu32(t, s->buffer_attr.fragsize); +        pa_tagstruct_put_usec(t, s->configured_source_latency);      }      pa_pstream_send_tagstruct(s->connection->pstream, t); @@ -1723,7 +1803,8 @@ static pa_tagstruct *reply_new(uint32_t tag) {  static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);      playback_stream *s; -    uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid, missing; +    uint32_t sink_index, syncid, missing; +    pa_buffer_attr attr;      const char *name = NULL, *sink_name;      pa_sample_spec ss;      pa_channel_map map; @@ -1752,6 +1833,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u      pa_native_connection_assert_ref(c);      pa_assert(t); +    memset(&attr, 0, sizeof(attr));      if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) ||          pa_tagstruct_get( @@ -1760,11 +1842,11 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u                  PA_TAG_CHANNEL_MAP, &map,                  PA_TAG_U32, &sink_index,                  PA_TAG_STRING, &sink_name, -                PA_TAG_U32, &maxlength, +                PA_TAG_U32, &attr.maxlength,                  PA_TAG_BOOLEAN, &corked, -                PA_TAG_U32, &tlength, -                PA_TAG_U32, &prebuf, -                PA_TAG_U32, &minreq, +                PA_TAG_U32, &attr.tlength, +                PA_TAG_U32, &attr.prebuf, +                PA_TAG_U32, &attr.minreq,                  PA_TAG_U32, &syncid,                  PA_TAG_CVOLUME, &volume,                  PA_TAG_INVALID) < 0) { @@ -1875,7 +1957,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u       * flag. For older versions we synthesize it here */      muted_set = muted_set || muted; -    s = playback_stream_new(c, sink, &ss, &map, &maxlength, &tlength, &prebuf, &minreq, 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, &ret);      pa_proplist_free(p);      CHECK_VALIDITY(c->pstream, s, tag, ret); @@ -1891,10 +1973,10 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u      if (c->version >= 9) {          /* Since 0.9.0 we support sending the buffer metrics back to the client */ -        pa_tagstruct_putu32(reply, (uint32_t) maxlength); -        pa_tagstruct_putu32(reply, (uint32_t) tlength); -        pa_tagstruct_putu32(reply, (uint32_t) prebuf); -        pa_tagstruct_putu32(reply, (uint32_t) minreq); +        pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength); +        pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.tlength); +        pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.prebuf); +        pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.minreq);      }      if (c->version >= 12) { @@ -1912,7 +1994,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u      }      if (c->version >= 13) -        pa_tagstruct_put_usec(reply, s->sink_latency); +        pa_tagstruct_put_usec(reply, s->configured_sink_latency);      pa_pstream_send_tagstruct(c->pstream, reply);  } @@ -1978,7 +2060,7 @@ static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t t  static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);      record_stream *s; -    uint32_t maxlength, fragment_size; +    pa_buffer_attr attr;      uint32_t source_index;      const char *name = NULL, *source_name;      pa_sample_spec ss; @@ -2008,14 +2090,16 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin      pa_native_connection_assert_ref(c);      pa_assert(t); +    memset(&attr, 0, sizeof(attr)); +      if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) ||          pa_tagstruct_get_sample_spec(t, &ss) < 0 ||          pa_tagstruct_get_channel_map(t, &map) < 0 ||          pa_tagstruct_getu32(t, &source_index) < 0 ||          pa_tagstruct_gets(t, &source_name) < 0 || -        pa_tagstruct_getu32(t, &maxlength) < 0 || +        pa_tagstruct_getu32(t, &attr.maxlength) < 0 ||          pa_tagstruct_get_boolean(t, &corked) < 0 || -        pa_tagstruct_getu32(t, &fragment_size) < 0) { +        pa_tagstruct_getu32(t, &attr.fragsize) < 0) {          protocol_error(c);          return;      } @@ -2125,7 +2209,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin          (dont_inhibit_auto_suspend ? PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) |          (fail_on_suspend ? PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND : 0); -    s = record_stream_new(c, source, &ss, &map, peak_detect, &maxlength, &fragment_size, flags, p, adjust_latency, direct_on_input, early_requests, &ret); +    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);      CHECK_VALIDITY(c->pstream, s, tag, ret); @@ -2138,8 +2222,8 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin      if (c->version >= 9) {          /* Since 0.9 we support sending the buffer metrics back to the client */ -        pa_tagstruct_putu32(reply, (uint32_t) maxlength); -        pa_tagstruct_putu32(reply, (uint32_t) fragment_size); +        pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength); +        pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.fragsize);      }      if (c->version >= 12) { @@ -2157,7 +2241,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin      }      if (c->version >= 13) -        pa_tagstruct_put_usec(reply, s->source_latency); +        pa_tagstruct_put_usec(reply, s->configured_source_latency);      pa_pstream_send_tagstruct(c->pstream, reply);  } @@ -2444,7 +2528,6 @@ static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uin      playback_stream *s;      struct timeval tv, now;      uint32_t idx; -    pa_usec_t latency;      pa_native_connection_assert_ref(c);      pa_assert(t); @@ -2460,25 +2543,27 @@ static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uin      s = pa_idxset_get_by_index(c->output_streams, idx);      CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);      CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); -    CHECK_VALIDITY(c->pstream, pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0, tag, PA_ERR_NOENTITY) - -    reply = reply_new(tag); -    latency = pa_sink_get_latency(s->sink_input->sink); -    latency += pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sample_spec); - -    pa_tagstruct_put_usec(reply, latency); +    /* Get an atomic snapshot of all timing parameters */ +    pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0); +    reply = reply_new(tag); +    pa_tagstruct_put_usec(reply, +                          s->current_sink_latency + +                          pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sample_spec));      pa_tagstruct_put_usec(reply, 0); -    pa_tagstruct_put_boolean(reply, s->sink_input->thread_info.playing_for > 0); +    pa_tagstruct_put_boolean(reply, +                             s->playing_for > 0 && +                             pa_sink_get_state(s->sink_input->sink) == PA_SINK_RUNNING && +                             pa_sink_input_get_state(s->sink_input) == PA_SINK_INPUT_RUNNING);      pa_tagstruct_put_timeval(reply, &tv);      pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now));      pa_tagstruct_puts64(reply, s->write_index);      pa_tagstruct_puts64(reply, s->read_index);      if (c->version >= 13) { -        pa_tagstruct_putu64(reply, s->sink_input->thread_info.underrun_for); -        pa_tagstruct_putu64(reply, s->sink_input->thread_info.playing_for); +        pa_tagstruct_putu64(reply, s->underrun_for); +        pa_tagstruct_putu64(reply, s->playing_for);      }      pa_pstream_send_tagstruct(c->pstream, reply); @@ -2505,10 +2590,17 @@ static void command_get_record_latency(pa_pdispatch *pd, uint32_t command, uint3      s = pa_idxset_get_by_index(c->record_streams, idx);      CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); +    /* Get an atomic snapshot of all timing parameters */ +    pa_assert_se(pa_asyncmsgq_send(s->source_output->source->asyncmsgq, PA_MSGOBJECT(s->source_output), SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0); +      reply = reply_new(tag); -    pa_tagstruct_put_usec(reply, s->source_output->source->monitor_of ? pa_sink_get_latency(s->source_output->source->monitor_of) : 0); -    pa_tagstruct_put_usec(reply, pa_source_get_latency(s->source_output->source)); -    pa_tagstruct_put_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_RUNNING); +    pa_tagstruct_put_usec(reply, s->current_monitor_latency); +    pa_tagstruct_put_usec(reply, +                          s->current_source_latency + +                          pa_bytes_to_usec(s->on_the_fly_snapshot, &s->source_output->sample_spec)); +    pa_tagstruct_put_boolean(reply, +                             pa_source_get_state(s->source_output->source) == PA_SOURCE_RUNNING && +                             pa_source_output_get_state(s->source_output) == PA_SOURCE_OUTPUT_RUNNING);      pa_tagstruct_put_timeval(reply, &tv);      pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now));      pa_tagstruct_puts64(reply, pa_memblockq_get_write_index(s->memblockq)); @@ -2597,7 +2689,9 @@ static void command_finish_upload_stream(pa_pdispatch *pd, uint32_t command, uin      CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);      CHECK_VALIDITY(c->pstream, upload_stream_isinstance(s), tag, PA_ERR_NOENTITY); -    if (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, s->proplist, &idx) < 0) +    if (!s->memchunk.memblock) +        pa_pstream_send_error(c->pstream, tag, PA_ERR_TOOLARGE); +    else if (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, s->proplist, &idx) < 0)          pa_pstream_send_error(c->pstream, tag, PA_ERR_INTERNAL);      else          pa_pstream_send_simple_ack(c->pstream, tag); @@ -2732,7 +2826,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), +        PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE, 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, @@ -2754,6 +2848,23 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin          pa_tagstruct_putu32(t, sink->n_volume_steps);          pa_tagstruct_putu32(t, sink->card ? sink->card->index : PA_INVALID_INDEX);      } + +    if (c->version >= 16) { +        pa_tagstruct_putu32(t, sink->ports ? pa_hashmap_size(sink->ports) : 0); + +        if (sink->ports) { +            void *state; +            pa_device_port *p; + +            PA_HASHMAP_FOREACH(p, sink->ports, state) { +                pa_tagstruct_puts(t, p->name); +                pa_tagstruct_puts(t, p->description); +                pa_tagstruct_putu32(t, p->priority); +            } +        } + +        pa_tagstruct_puts(t, sink->active_port ? sink->active_port->name : NULL); +    }  }  static void source_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source *source) { @@ -2794,6 +2905,24 @@ static void source_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_s          pa_tagstruct_putu32(t, source->n_volume_steps);          pa_tagstruct_putu32(t, source->card ? source->card->index : PA_INVALID_INDEX);      } + +    if (c->version >= 16) { + +        pa_tagstruct_putu32(t, source->ports ? pa_hashmap_size(source->ports) : 0); + +        if (source->ports) { +            void *state; +            pa_device_port *p; + +            PA_HASHMAP_FOREACH(p, source->ports, state) { +                pa_tagstruct_puts(t, p->name); +                pa_tagstruct_puts(t, p->description); +                pa_tagstruct_putu32(t, p->priority); +            } +        } + +        pa_tagstruct_puts(t, source->active_port ? source->active_port->name : NULL); +    }  }  static void client_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_client *client) { @@ -2856,6 +2985,7 @@ static void module_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_m  static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sink_input *s) {      pa_sample_spec fixed_ss;      pa_usec_t sink_latency; +    pa_cvolume v;      pa_assert(t);      pa_sink_input_assert_ref(s); @@ -2869,7 +2999,7 @@ static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t,      pa_tagstruct_putu32(t, s->sink->index);      pa_tagstruct_put_sample_spec(t, &fixed_ss);      pa_tagstruct_put_channel_map(t, &s->channel_map); -    pa_tagstruct_put_cvolume(t, pa_sink_input_get_volume(s)); +    pa_tagstruct_put_cvolume(t, pa_sink_input_get_volume(s, &v, TRUE));      pa_tagstruct_put_usec(t, pa_sink_input_get_latency(s, &sink_latency));      pa_tagstruct_put_usec(t, sink_latency);      pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s))); @@ -3094,10 +3224,10 @@ static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t t  static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);      pa_tagstruct *reply; -    char txt[256];      pa_sink *def_sink;      pa_source *def_source;      pa_sample_spec fixed_ss; +    char *h, *u;      pa_native_connection_assert_ref(c);      pa_assert(t); @@ -3112,8 +3242,14 @@ static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t      reply = reply_new(tag);      pa_tagstruct_puts(reply, PACKAGE_NAME);      pa_tagstruct_puts(reply, PACKAGE_VERSION); -    pa_tagstruct_puts(reply, pa_get_user_name(txt, sizeof(txt))); -    pa_tagstruct_puts(reply, pa_get_host_name(txt, sizeof(txt))); + +    u = pa_get_user_name_malloc(); +    pa_tagstruct_puts(reply, u); +    pa_xfree(u); + +    h = pa_get_host_name_malloc(); +    pa_tagstruct_puts(reply, h); +    pa_xfree(h);      fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec);      pa_tagstruct_put_sample_spec(reply, &fixed_ss); @@ -3234,11 +3370,11 @@ 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); +        pa_sink_set_volume(sink, &volume, TRUE, TRUE, TRUE, TRUE);      else if (source) -        pa_source_set_volume(source, &volume); +        pa_source_set_volume(source, &volume, TRUE);      else if (si) -        pa_sink_input_set_volume(si, &volume, TRUE); +        pa_sink_input_set_volume(si, &volume, TRUE, TRUE);      pa_pstream_send_simple_ack(c->pstream, tag);  } @@ -3306,9 +3442,9 @@ static void command_set_mute(      CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY);      if (sink) -        pa_sink_set_mute(sink, mute); +        pa_sink_set_mute(sink, mute, TRUE);      else if (source) -        pa_source_set_mute(source, mute); +        pa_source_set_mute(source, mute, TRUE);      else if (si)          pa_sink_input_set_mute(si, mute, TRUE); @@ -3435,12 +3571,14 @@ static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint  static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);      uint32_t idx; -    uint32_t maxlength, tlength, prebuf, minreq, fragsize; +    pa_buffer_attr a;      pa_tagstruct *reply;      pa_native_connection_assert_ref(c);      pa_assert(t); +    memset(&a, 0, sizeof(a)); +      if (pa_tagstruct_getu32(t, &idx) < 0) {          protocol_error(c);          return; @@ -3458,10 +3596,10 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u          if (pa_tagstruct_get(                      t, -                    PA_TAG_U32, &maxlength, -                    PA_TAG_U32, &tlength, -                    PA_TAG_U32, &prebuf, -                    PA_TAG_U32, &minreq, +                    PA_TAG_U32, &a.maxlength, +                    PA_TAG_U32, &a.tlength, +                    PA_TAG_U32, &a.prebuf, +                    PA_TAG_U32, &a.minreq,                      PA_TAG_INVALID) < 0 ||              (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) ||              (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) || @@ -3470,21 +3608,21 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u              return;          } -        fix_playback_buffer_attr_pre(s, adjust_latency, early_requests, &maxlength, &tlength, &prebuf, &minreq); -        pa_memblockq_set_maxlength(s->memblockq, maxlength); -        pa_memblockq_set_tlength(s->memblockq, tlength); -        pa_memblockq_set_prebuf(s->memblockq, prebuf); -        pa_memblockq_set_minreq(s->memblockq, minreq); -        fix_playback_buffer_attr_post(s, &maxlength, &tlength, &prebuf, &minreq); +        s->adjust_latency = adjust_latency; +        s->early_requests = early_requests; +        s->buffer_attr = a; + +        fix_playback_buffer_attr(s); +        pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR, NULL, 0, NULL) == 0);          reply = reply_new(tag); -        pa_tagstruct_putu32(reply, maxlength); -        pa_tagstruct_putu32(reply, tlength); -        pa_tagstruct_putu32(reply, prebuf); -        pa_tagstruct_putu32(reply, minreq); +        pa_tagstruct_putu32(reply, s->buffer_attr.maxlength); +        pa_tagstruct_putu32(reply, s->buffer_attr.tlength); +        pa_tagstruct_putu32(reply, s->buffer_attr.prebuf); +        pa_tagstruct_putu32(reply, s->buffer_attr.minreq);          if (c->version >= 13) -            pa_tagstruct_put_usec(reply, s->sink_latency); +            pa_tagstruct_put_usec(reply, s->configured_sink_latency);      } else {          record_stream *s; @@ -3496,8 +3634,8 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u          if (pa_tagstruct_get(                      t, -                    PA_TAG_U32, &maxlength, -                    PA_TAG_U32, &fragsize, +                    PA_TAG_U32, &a.maxlength, +                    PA_TAG_U32, &a.fragsize,                      PA_TAG_INVALID) < 0 ||              (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) ||              (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) || @@ -3506,16 +3644,21 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u              return;          } -        fix_record_buffer_attr_pre(s, adjust_latency, early_requests, &maxlength, &fragsize); -        pa_memblockq_set_maxlength(s->memblockq, maxlength); -        fix_record_buffer_attr_post(s, &maxlength, &fragsize); +        s->adjust_latency = adjust_latency; +        s->early_requests = early_requests; +        s->buffer_attr = a; + +        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); +        fix_record_buffer_attr_post(s);          reply = reply_new(tag); -        pa_tagstruct_putu32(reply, maxlength); -        pa_tagstruct_putu32(reply, fragsize); +        pa_tagstruct_putu32(reply, s->buffer_attr.maxlength); +        pa_tagstruct_putu32(reply, s->buffer_attr.fragsize);          if (c->version >= 13) -            pa_tagstruct_put_usec(reply, s->source_latency); +            pa_tagstruct_put_usec(reply, s->configured_source_latency);      }      pa_pstream_send_tagstruct(c->pstream, reply); @@ -3997,7 +4140,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa              pa_log_debug("%s all sinks", b ? "Suspending" : "Resuming"); -            if (pa_sink_suspend_all(c->protocol->core, b) < 0) { +            if (pa_sink_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) {                  pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);                  return;              } @@ -4011,7 +4154,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa              CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); -            if (pa_sink_suspend(sink, b) < 0) { +            if (pa_sink_suspend(sink, b, PA_SUSPEND_USER) < 0) {                  pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);                  return;              } @@ -4024,7 +4167,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa              pa_log_debug("%s all sources", b ? "Suspending" : "Resuming"); -            if (pa_source_suspend_all(c->protocol->core, b) < 0) { +            if (pa_source_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) {                  pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);                  return;              } @@ -4039,7 +4182,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa              CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); -            if (pa_source_suspend(source, b) < 0) { +            if (pa_source_suspend(source, b, PA_SUSPEND_USER) < 0) {                  pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);                  return;              } @@ -4094,6 +4237,7 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_      uint32_t idx = PA_INVALID_INDEX;      const char *name = NULL, *profile = NULL;      pa_card *card = NULL; +    int ret;      pa_native_connection_assert_ref(c);      pa_assert(t); @@ -4119,11 +4263,69 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_      CHECK_VALIDITY(c->pstream, card, tag, PA_ERR_NOENTITY); -    if (pa_card_set_profile(card, profile) < 0) { -        pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); +    if ((ret = pa_card_set_profile(card, profile, TRUE)) < 0) { +        pa_pstream_send_error(c->pstream, tag, -ret); +        return; +    } + +    pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { +    pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); +    uint32_t idx = PA_INVALID_INDEX; +    const char *name = NULL, *port = NULL; +    int ret; + +    pa_native_connection_assert_ref(c); +    pa_assert(t); + +    if (pa_tagstruct_getu32(t, &idx) < 0 || +        pa_tagstruct_gets(t, &name) < 0 || +        pa_tagstruct_gets(t, &port) < 0 || +        !pa_tagstruct_eof(t)) { +        protocol_error(c);          return;      } +    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, 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); + +    if (command == PA_COMMAND_SET_SINK_PORT) { +        pa_sink *sink; + +        if (idx != PA_INVALID_INDEX) +            sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); +        else +            sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK); + +        CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + +        if ((ret = pa_sink_set_port(sink, port, TRUE)) < 0) { +            pa_pstream_send_error(c->pstream, tag, -ret); +            return; +        } +    } else { +        pa_source *source; + +        pa_assert(command = PA_COMMAND_SET_SOURCE_PORT); + +        if (idx != PA_INVALID_INDEX) +            source = pa_idxset_get_by_index(c->protocol->core->sources, idx); +        else +            source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE); + +        CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + +        if ((ret = pa_source_set_port(source, port, TRUE)) < 0) { +            pa_pstream_send_error(c->pstream, tag, -ret); +            return; +        } +    } +      pa_pstream_send_simple_ack(c->pstream, tag);  } @@ -4151,7 +4353,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o      pa_native_connection_assert_ref(c);      if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) { -        pa_log("client sent block for invalid stream."); +        pa_log_debug("Client sent block for invalid stream.");          /* Ignoring */          return;      } @@ -4280,11 +4482,10 @@ static void client_send_event_cb(pa_client *client, const char*event, pa_proplis  /*** module entry points ***/ -static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *t, void *userdata) {      pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);      pa_assert(m); -    pa_assert(tv);      pa_native_connection_assert_ref(c);      pa_assert(c->auth_timeout_event == e); @@ -4342,12 +4543,9 @@ void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_nati          c->authorized = TRUE;      } -    if (!c->authorized) { -        struct timeval tv; -        pa_gettimeofday(&tv); -        tv.tv_sec += AUTH_TIMEOUT; -        c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, auth_timeout, c); -    } else +    if (!c->authorized) +        c->auth_timeout_event = pa_core_rttime_new(p->core, pa_rtclock_now() + AUTH_TIMEOUT, auth_timeout, c); +    else          c->auth_timeout_event = NULL;      c->is_local = pa_iochannel_socket_is_local(io); @@ -4366,7 +4564,7 @@ void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_nati      pa_pstream_set_revoke_callback(c->pstream, pstream_revoke_callback, c);      pa_pstream_set_release_callback(c->pstream, pstream_release_callback, c); -    c->pdispatch = pa_pdispatch_new(p->core->mainloop, command_table, PA_COMMAND_MAX); +    c->pdispatch = pa_pdispatch_new(p->core->mainloop, TRUE, command_table, PA_COMMAND_MAX);      c->record_streams = pa_idxset_new(NULL, NULL);      c->output_streams = pa_idxset_new(NULL, NULL); diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index 44fe5973..776d74b6 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -130,7 +130,7 @@ static void connection_unlink(connection *c) {          c->io = NULL;      } -    pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); +    pa_idxset_remove_by_data(c->protocol->connections, c, NULL);      c->protocol = NULL;      connection_unref(c);  } diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c index c92b5baf..34b9eaad 100644 --- a/src/pulsecore/pstream.c +++ b/src/pulsecore/pstream.c @@ -683,7 +683,7 @@ static int do_read(pa_pstream *p) {          flags = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]);          if (!p->use_shm && (flags & PA_FLAG_SHMMASK) != 0) { -            pa_log_warn("Recieved SHM frame on a socket where SHM is disabled."); +            pa_log_warn("Received SHM frame on a socket where SHM is disabled.");              return -1;          } @@ -713,7 +713,7 @@ static int do_read(pa_pstream *p) {          length = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]);          if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) { -            pa_log_warn("Recieved invalid frame size: %lu", (unsigned long) length); +            pa_log_warn("Received invalid frame size: %lu", (unsigned long) length);              return -1;          } @@ -742,7 +742,7 @@ static int do_read(pa_pstream *p) {              if ((flags & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA) {                  if (length != sizeof(p->read.shm_info)) { -                    pa_log_warn("Recieved SHM memblock frame with Invalid frame length."); +                    pa_log_warn("Received SHM memblock frame with Invalid frame length.");                      return -1;                  } @@ -757,7 +757,7 @@ static int do_read(pa_pstream *p) {                  p->read.data = NULL;              } else { -                pa_log_warn("Recieved memblock frame with invalid flags value."); +                pa_log_warn("Received memblock frame with invalid flags value.");                  return -1;              }          } diff --git a/src/pulsecore/ratelimit.c b/src/pulsecore/ratelimit.c index 29e6fb10..844dd77d 100644 --- a/src/pulsecore/ratelimit.c +++ b/src/pulsecore/ratelimit.c @@ -23,13 +23,14 @@  #include <config.h>  #endif -#include <pulsecore/rtclock.h> +#include <pulse/rtclock.h> +  #include <pulsecore/log.h>  #include <pulsecore/mutex.h>  #include "ratelimit.h" -static pa_static_mutex mutex; +static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT;  /* Modelled after Linux' lib/ratelimit.c by Dave Young   * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */ @@ -38,7 +39,7 @@ pa_bool_t pa_ratelimit_test(pa_ratelimit *r) {      pa_usec_t now;      pa_mutex *m; -    now = pa_rtclock_usec(); +    now = pa_rtclock_now();      m = pa_static_mutex_get(&mutex, FALSE, FALSE);      pa_mutex_lock(m); diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h index 1e988326..782436b5 100644 --- a/src/pulsecore/refcnt.h +++ b/src/pulsecore/refcnt.h @@ -23,23 +23,59 @@  ***/  #include <pulsecore/atomic.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +/* #define DEBUG_REF */  #define PA_REFCNT_DECLARE \      pa_atomic_t _ref -#define PA_REFCNT_INIT(p) \ -    pa_atomic_store(&(p)->_ref, 1) +#define PA_REFCNT_VALUE(p) \ +    pa_atomic_load(&(p)->_ref)  #define PA_REFCNT_INIT_ZERO(p) \      pa_atomic_store(&(p)->_ref, 0) +#ifndef DEBUG_REF + +#define PA_REFCNT_INIT(p) \ +    pa_atomic_store(&(p)->_ref, 1) +  #define PA_REFCNT_INC(p) \      pa_atomic_inc(&(p)->_ref)  #define PA_REFCNT_DEC(p) \      (pa_atomic_dec(&(p)->_ref)-1) -#define PA_REFCNT_VALUE(p) \ -    pa_atomic_load(&(p)->_ref) +#else + +/* If you need to debug ref counting problems define DEBUG_REF and + * set $PULSE_LOG_BACKTRACE=5 or suchlike in the shell when running + * PA */ + +#define PA_REFCNT_INIT(p)                       \ +    do {                                        \ +        pa_atomic_store(&(p)->_ref, 1);         \ +        pa_log("REF: Init %p", p);              \ +    } while (FALSE) + +#define PA_REFCNT_INC(p)                        \ +    do {                                        \ +        pa_atomic_inc(&(p)->_ref);              \ +        pa_log("REF: Inc %p", p);               \ +    } while (FALSE)                             \ + +#define PA_REFCNT_DEC(p)                        \ +    ({                                          \ +        int _j = (pa_atomic_dec(&(p)->_ref)-1); \ +        if (_j <= 0)                            \ +            pa_log("REF: Done %p", p);          \ +        else                                    \ +            pa_log("REF: Dec %p", p);           \ +        _j;                                     \ +     }) + +#endif  #endif diff --git a/src/pulsecore/rtkit.c b/src/pulsecore/rtkit.c new file mode 100644 index 00000000..aecc4e32 --- /dev/null +++ b/src/pulsecore/rtkit.c @@ -0,0 +1,189 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** +  Copyright 2009 Lennart Poettering + +  Permission is hereby granted, free of charge, to any person +  obtaining a copy of this software and associated documentation files +  (the "Software"), to deal in the Software without restriction, +  including without limitation the rights to use, copy, modify, merge, +  publish, distribute, sublicense, and/or sell copies of the Software, +  and to permit persons to whom the Software is furnished to do so, +  subject to the following conditions: + +  The above copyright notice and this permission notice shall be +  included in all copies or substantial portions of the Software. + +  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +  SOFTWARE. +***/ + +#include <errno.h> + +#include "rtkit.h" + +#ifdef __linux__ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/syscall.h> + +static pid_t _gettid(void) { +        return (pid_t) syscall(SYS_gettid); +} + +static int translate_error(const char *name) { +        if (strcmp(name, DBUS_ERROR_NO_MEMORY) == 0) +                return -ENOMEM; +        if (strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0 || +            strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0) +                return -ENOENT; +        if (strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0 || +            strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0) +                return -EACCES; + +        return -EIO; +} + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) { +        DBusMessage *m = NULL, *r = NULL; +        dbus_uint64_t u64; +        dbus_uint32_t u32; +        DBusError error; +        int ret; + +        dbus_error_init(&error); + +        if (thread == 0) +                thread = _gettid(); + +        if (!(m = dbus_message_new_method_call( +                              RTKIT_SERVICE_NAME, +                              RTKIT_OBJECT_PATH, +                              "org.freedesktop.RealtimeKit1", +                              "MakeThreadRealtime"))) { +                ret = -ENOMEM; +                goto finish; +        } + +        u64 = (dbus_uint64_t) thread; +        u32 = (dbus_uint32_t) priority; + +        if (!dbus_message_append_args( +                            m, +                            DBUS_TYPE_UINT64, &u64, +                            DBUS_TYPE_UINT32, &u32, +                            DBUS_TYPE_INVALID)) { +                ret = -ENOMEM; +                goto finish; +        } + +        if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { +                ret = translate_error(error.name); +                goto finish; +        } + + +        if (dbus_set_error_from_message(&error, r)) { +                ret = translate_error(error.name); +                goto finish; +        } + +        ret = 0; + +finish: + +        if (m) +                dbus_message_unref(m); + +        if (r) +                dbus_message_unref(r); + +        dbus_error_free(&error); + +        return ret; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) { +        DBusMessage *m = NULL, *r = NULL; +        dbus_uint64_t u64; +        dbus_int32_t s32; +        DBusError error; +        int ret; + +        dbus_error_init(&error); + +        if (thread == 0) +                thread = _gettid(); + +        if (!(m = dbus_message_new_method_call( +                              RTKIT_SERVICE_NAME, +                              RTKIT_OBJECT_PATH, +                              "org.freedesktop.RealtimeKit1", +                              "MakeThreadHighPriority"))) { +                ret = -ENOMEM; +                goto finish; +        } + +        u64 = (dbus_uint64_t) thread; +        s32 = (dbus_int32_t) nice_level; + +        if (!dbus_message_append_args( +                            m, +                            DBUS_TYPE_UINT64, &u64, +                            DBUS_TYPE_INT32, &s32, +                            DBUS_TYPE_INVALID)) { +                ret = -ENOMEM; +                goto finish; +        } + + + +        if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { +                ret = translate_error(error.name); +                goto finish; +        } + + +        if (dbus_set_error_from_message(&error, r)) { +                ret = translate_error(error.name); +                goto finish; +        } + +        ret = 0; + +finish: + +        if (m) +                dbus_message_unref(m); + +        if (r) +                dbus_message_unref(r); + +        dbus_error_free(&error); + +        return ret; +} + +#else + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) { +        return -ENOTSUP; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) { +        return -ENOTSUP; +} + +#endif diff --git a/src/pulsecore/rtkit.h b/src/pulsecore/rtkit.h new file mode 100644 index 00000000..2081b4e9 --- /dev/null +++ b/src/pulsecore/rtkit.h @@ -0,0 +1,62 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foortkithfoo +#define foortkithfoo + +/*** +  Copyright 2009 Lennart Poettering + +  Permission is hereby granted, free of charge, to any person +  obtaining a copy of this software and associated documentation files +  (the "Software"), to deal in the Software without restriction, +  including without limitation the rights to use, copy, modify, merge, +  publish, distribute, sublicense, and/or sell copies of the Software, +  and to permit persons to whom the Software is furnished to do so, +  subject to the following conditions: + +  The above copyright notice and this permission notice shall be +  included in all copies or substantial portions of the Software. + +  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +  SOFTWARE. +***/ + +#include <sys/types.h> +#include <dbus/dbus.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reference implementation for a client for + * RealtimeKit. You don't have to use this, but if do, just copy these + * sources into your repository */ + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" + +/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { + * .sched_priority = priority }). 'thread' needs to be a kernel thread + * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the + * current thread is used. The returned value is a negative errno + * style error code, or 0 on success. */ +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority); + +/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread, + * nice_level). 'thread' needs to be a kernel thread id as returned by + * gettid(), not a pthread_t! If 'thread' is 0 the current thread is + * used. The returned value is a negative errno style error code, or 0 + * on success.*/ +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c index dd3e240a..c6f1ef8c 100644 --- a/src/pulsecore/rtpoll.c +++ b/src/pulsecore/rtpoll.c @@ -30,10 +30,6 @@  #include <string.h>  #include <errno.h> -#ifdef __linux__ -#include <sys/utsname.h> -#endif -  #ifdef HAVE_POLL_H  #include <poll.h>  #else @@ -42,12 +38,12 @@  #include <pulse/xmalloc.h>  #include <pulse/timeval.h> +#include <pulse/rtclock.h>  #include <pulsecore/core-error.h> -#include <pulsecore/rtclock.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/macro.h>  #include <pulsecore/llist.h> -#include <pulsecore/rtsig.h>  #include <pulsecore/flist.h>  #include <pulsecore/core-util.h>  #include <pulsecore/winsock.h> @@ -59,6 +55,8 @@  /* #define DEBUG_TIMING */  struct pa_rtpoll { +    void *userdata; +      struct pollfd *pollfd, *pollfd2;      unsigned n_pollfd_alloc, n_pollfd_used; @@ -67,24 +65,11 @@ struct pa_rtpoll {      pa_usec_t elapse;      pa_bool_t timer_enabled:1; -    pa_bool_t scan_for_dead:1;      pa_bool_t running:1; -    pa_bool_t installed:1; +    pa_bool_t scan_for_dead:1;      pa_bool_t rebuild_needed:1;      pa_bool_t quit:1; -#if defined(HAVE_PPOLL) && defined(__linux__) -    pa_bool_t use_ppoll:1; -    pa_bool_t use_signals:1; - -    pa_bool_t timer_armed:1; -    int rtsig; -    sigset_t sigset_unblocked; -    timer_t timer; -#endif - -    void *userdata; -  #ifdef DEBUG_TIMING      pa_usec_t timestamp;      pa_usec_t slept, awake; @@ -117,8 +102,6 @@ struct pa_rtpoll_item {  PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); -static void signal_handler_noop(int s) { /* write(2, "signal\n", 7); */ } -  static int item_compare(const void *_a, const void *_b) {      const pa_rtpoll_item *a = _a, *b = _b; @@ -140,21 +123,6 @@ pa_rtpoll *pa_rtpoll_new(void) {      p->userdata = NULL; -#if defined(HAVE_PPOLL) && defined(__linux__) -    /* ppoll() is broken on Linux < 2.6.16. Don't use it. */ -    p->use_ppoll = pa_linux_newer_than(2, 6, 16); - -    /* Starting with Linux 2.6.28 ppoll() does no longer round up -     * timeouts to multiple of HZ, hence using signal based timers is -     * no longer necessary. */ -    p->use_signals = p->use_ppoll && !pa_linux_newer_than(2, 6, 28); - -    p->rtsig = -1; -    sigemptyset(&p->sigset_unblocked); -    p->timer = (timer_t) -1; -    p->timer_armed = FALSE; -#endif -      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); @@ -166,58 +134,18 @@ pa_rtpoll *pa_rtpoll_new(void) {      p->timer_enabled = FALSE;      p->running = FALSE; -    p->installed = 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_usec(); +    p->timestamp = pa_rtclock_now();      p->slept = p->awake = 0;  #endif -    return p; -} - -void pa_rtpoll_install(pa_rtpoll *p) { -    pa_assert(p); -    pa_assert(!p->installed); - -    p->installed = TRUE; - -#if defined(HAVE_PPOLL) && defined(__linux__) - -    if (!p->use_signals) -        return; - -    if ((p->rtsig = pa_rtsig_get_for_thread()) < 0) { -        pa_log_warn("Failed to reserve POSIX realtime signal."); -        return; -    } - -    pa_log_debug("Acquired POSIX realtime signal %s", pa_sig2str(p->rtsig)); - -    { -        sigset_t ss; -        struct sigaction sa; - -        pa_assert_se(sigemptyset(&ss) == 0); -        pa_assert_se(sigaddset(&ss, p->rtsig) == 0); -        pa_assert_se(pthread_sigmask(SIG_BLOCK, &ss, &p->sigset_unblocked) == 0); -        pa_assert_se(sigdelset(&p->sigset_unblocked, p->rtsig) == 0); - -        memset(&sa, 0, sizeof(sa)); -        sa.sa_handler = signal_handler_noop; -        pa_assert_se(sigemptyset(&sa.sa_mask) == 0); - -        pa_assert_se(sigaction(p->rtsig, &sa, NULL) == 0); - -        /* We never reset the signal handler. Why should we? */ -    } +    PA_LLIST_HEAD_INIT(pa_rtpoll_item, p->items); -#endif +    return p;  }  static void rtpoll_rebuild(pa_rtpoll *p) { @@ -295,11 +223,6 @@ void pa_rtpoll_free(pa_rtpoll *p) {      pa_xfree(p->pollfd);      pa_xfree(p->pollfd2); -#if defined(HAVE_PPOLL) && defined(__linux__) -    if (p->timer != (timer_t) -1) -        timer_delete(p->timer); -#endif -      if (p->prioq)          pa_prioq_free(p->prioq, NULL, NULL); @@ -358,7 +281,7 @@ static pa_bool_t next_elapse(pa_rtpoll *p, pa_usec_t *usec) {      return FALSE;  } -int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) { +int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait_op) {      pa_rtpoll_item *i;      int r = 0;      pa_usec_t timeout; @@ -366,14 +289,16 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {      pa_assert(p);      pa_assert(!p->running); -    pa_assert(p->installed);      p->running = TRUE;      /* First, let's do some work */ -    for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { +    PA_LLIST_FOREACH(i, p->items) {          int k; +        if (i->priority >= PA_RTPOLL_NEVER) +            break; +          if (i->dead)              continue; @@ -392,9 +317,12 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {      }      /* Now let's prepare for entering the sleep */ -    for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { +    PA_LLIST_FOREACH(i, p->items) {          int k = 0; +        if (i->priority >= PA_RTPOLL_NEVER) +            break; +          if (i->dead)              continue; @@ -430,13 +358,13 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {      timeout_valid = FALSE;      /* Calculate timeout */ -    if (wait && !p->quit) { +    if (wait_op && !p->quit) {          pa_usec_t elapse;          if (next_elapse(p, &elapse)) {              pa_usec_t now; -            now = pa_rtclock_usec(); +            now = pa_rtclock_now();              timeout = now >= elapse ? 0 : elapse - now;              timeout_valid = TRUE;          } @@ -444,36 +372,29 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {  #ifdef DEBUG_TIMING      { -        pa_usec_t now = pa_rtclock_usec(); +        pa_usec_t now = pa_rtclock_now();          p->awake = now - p->timestamp;          p->timestamp = now;      }  #endif      /* OK, now let's sleep */ -#ifdef HAVE_PPOLL - -#ifdef __linux__ -    if (p->use_ppoll) -#endif      { +#ifdef HAVE_PPOLL          struct timespec ts; -        pa_timespec_store(&ts, timeout);          r = ppoll(p->pollfd, p->n_pollfd_used, -                  (!wait || p->quit || timeout_valid) ? &ts : NULL, -                  p->rtsig < 0 ? NULL : &p->sigset_unblocked); -    } -#ifdef __linux__ -    else -#endif - -#endif +                  (!wait_op || p->quit || timeout_valid) ? +                  pa_timespec_store(&ts, timeout) : NULL, NULL); +#else          r = poll(p->pollfd, p->n_pollfd_used, -                 (!wait || p->quit || timeout_valid) ? (int) (timeout / PA_USEC_PER_MSEC) : -1); +                 (!wait_op || p->quit || timeout_valid) ? +                 (int) (timeout / PA_USEC_PER_MSEC) : -1); +#endif +    }  #ifdef DEBUG_TIMING      { -        pa_usec_t now = pa_rtclock_usec(); +        pa_usec_t now = pa_rtclock_now();          p->slept = now - p->timestamp;          p->timestamp = now; @@ -493,7 +414,9 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {      }      /* Let's tell everyone that we left the sleep */ -    for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { +    PA_LLIST_FOREACH(i, p->items) { +        if (i->priority >= PA_RTPOLL_NEVER) +            break;          if (i->dead)              continue; @@ -513,86 +436,22 @@ finish:          p->scan_for_dead = FALSE; -        for (i = p->items; i; i = n) { -            n = i->next; - +        PA_LLIST_FOREACH_FOR_DELETE(i, n, p->items)              if (i->dead)                  rtpoll_item_destroy(i); -        }      }      return r < 0 ? r : !p->quit;  } -static void update_timer(pa_rtpoll *p) { -    pa_assert(p); - -#if defined(HAVE_PPOLL) && defined(__linux__) - -    if (!p->use_signals) -        return; - -    if (p->timer == (timer_t) -1) { -        struct sigevent se; - -        memset(&se, 0, sizeof(se)); -        se.sigev_notify = SIGEV_SIGNAL; -        se.sigev_signo = p->rtsig; - -        if (timer_create(CLOCK_MONOTONIC, &se, &p->timer) < 0) -            if (timer_create(CLOCK_REALTIME, &se, &p->timer) < 0) { -                pa_log_warn("Failed to allocate POSIX timer: %s", pa_cstrerror(errno)); -                p->timer = (timer_t) -1; -            } -    } - -    if (p->timer != (timer_t) -1) { -        struct itimerspec its; -        struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; -        sigset_t ss; -        pa_usec_t elapse; - -        if (p->timer_armed) { -            /* First disarm timer */ -            memset(&its, 0, sizeof(its)); -            pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0); - -            /* Remove a signal that might be waiting in the signal q */ -            pa_assert_se(sigemptyset(&ss) == 0); -            pa_assert_se(sigaddset(&ss, p->rtsig) == 0); -            sigtimedwait(&ss, NULL, &ts); -        } - -        /* And install the new timer */ -        if (next_elapse(p, &elapse)) { -            memset(&its, 0, sizeof(its)); - -            pa_timespec_store(&its.it_value, elapse); - -            /* Make sure that 0,0 is not understood as -             * "disarming" */ -            if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) -                its.it_value.tv_nsec = 1; - -            pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0); -        } - -        p->timer_armed = p->timer_enabled; -    } - -#endif -} -  void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec) {      pa_assert(p);      if (p->timer_enabled && p->elapse == usec) -        return +        return;      p->elapse = usec;      p->timer_enabled = TRUE; - -    update_timer(p);  }  void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) { @@ -601,7 +460,7 @@ void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) {      /* Scheduling a timeout for more than an hour is very very suspicious */      pa_assert(usec <= PA_USEC_PER_SEC*60ULL*60ULL); -    pa_rtpoll_set_timer_absolute(p, pa_rtclock_usec() + usec); +    pa_rtpoll_set_timer_absolute(p, pa_rtclock_now() + usec);  }  void pa_rtpoll_disable_timer(pa_rtpoll *p) { @@ -612,11 +471,8 @@ void pa_rtpoll_disable_timer(pa_rtpoll *p) {      p->elapse = 0;      p->timer_enabled = FALSE; - -    update_timer(p);  } -  void pa_rtpoll_set_userdata(pa_rtpoll *p, void *userdata) {      pa_assert(p); @@ -651,7 +507,7 @@ pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsi      i->after_cb = NULL;      i->work_cb = NULL; -    for (j = p->items; j; j = j->next) { +    PA_LLIST_FOREACH(j, p->items) {          if (prio <= j->priority)              break; @@ -720,8 +576,6 @@ void pa_rtpoll_item_set_timer_absolute(pa_rtpoll_item *i, pa_usec_t usec){          pa_prioq_reshuffle(i->rtpoll->prioq, i->prioq_item);      else          i->prioq_item = pa_prioq_put(i->rtpoll->prioq, i); - -    update_timer(i->rtpoll);  }  void pa_rtpoll_item_set_timer_relative(pa_rtpoll_item *i, pa_usec_t usec) { @@ -730,7 +584,7 @@ void pa_rtpoll_item_set_timer_relative(pa_rtpoll_item *i, pa_usec_t usec) {      /* Scheduling a timeout for more than an hour is very very suspicious */      pa_assert(usec <= PA_USEC_PER_SEC*60ULL*60ULL); -    pa_rtpoll_item_set_timer_absolute(i, pa_rtclock_usec() + usec); +    pa_rtpoll_item_set_timer_absolute(i, pa_rtclock_now() + usec);  }  void pa_rtpoll_item_disable_timer(pa_rtpoll_item *i) { @@ -746,8 +600,6 @@ void pa_rtpoll_item_disable_timer(pa_rtpoll_item *i) {          pa_prioq_remove(i->rtpoll->prioq, i->prioq_item);          i->prioq_item = NULL;      } - -    update_timer(i->rtpoll);  }  void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i)) { diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h index 8c3cc70d..0373d0cb 100644 --- a/src/pulsecore/rtpoll.h +++ b/src/pulsecore/rtpoll.h @@ -62,9 +62,6 @@ typedef enum pa_rtpoll_priority {  pa_rtpoll *pa_rtpoll_new(void);  void pa_rtpoll_free(pa_rtpoll *p); -/* Install the rtpoll in the current thread */ -void pa_rtpoll_install(pa_rtpoll *p); -  /* Sleep on the rtpoll until the time event, or any of the fd events   * is triggered. If "wait" is 0 we don't sleep but only update the   * struct pollfd. Returns negative on error, positive if the loop diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c index 3a9b384d..5b8ccf59 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -831,9 +831,9 @@ void pa_volume_memchunk(              calc_linear_integer_volume(linear, volume); -            e = (uint8_t*) ptr + c->length/3; +            e = (uint8_t*) ptr + c->length; -            for (channel = 0, d = ptr; d < e; d++) { +            for (channel = 0, d = ptr; d < e; d += 3) {                  int64_t t;                  t = (int64_t)((int32_t) (PA_READ24NE(d) << 8)); @@ -854,9 +854,9 @@ void pa_volume_memchunk(              calc_linear_integer_volume(linear, volume); -            e = (uint8_t*) ptr + c->length/3; +            e = (uint8_t*) ptr + c->length; -            for (channel = 0, d = ptr; d < e; d++) { +            for (channel = 0, d = ptr; d < e; d += 3) {                  int64_t t;                  t = (int64_t)((int32_t) (PA_READ24RE(d) << 8)); @@ -1181,6 +1181,8 @@ pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool,              case PA_SAMPLE_S32BE:              case PA_SAMPLE_S24LE:              case PA_SAMPLE_S24BE: +            case PA_SAMPLE_S24_32LE: +            case PA_SAMPLE_S24_32BE:              case PA_SAMPLE_FLOAT32LE:              case PA_SAMPLE_FLOAT32BE:                  cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0); @@ -1189,6 +1191,8 @@ pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool,                  cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b);                  cache->blocks[PA_SAMPLE_S24LE] = pa_memblock_ref(b);                  cache->blocks[PA_SAMPLE_S24BE] = pa_memblock_ref(b); +                cache->blocks[PA_SAMPLE_S24_32LE] = pa_memblock_ref(b); +                cache->blocks[PA_SAMPLE_S24_32BE] = pa_memblock_ref(b);                  cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b);                  cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b);                  break; diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h index 79af9efc..6a306c11 100644 --- a/src/pulsecore/sample-util.h +++ b/src/pulsecore/sample-util.h @@ -25,6 +25,7 @@  #include <pulse/sample.h>  #include <pulse/volume.h> +#include <pulse/channelmap.h>  #include <pulsecore/memblock.h>  #include <pulsecore/memchunk.h> @@ -85,4 +86,62 @@ 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); +#define PA_CHANNEL_POSITION_MASK_LEFT                                   \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT)           \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT)          \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT)          \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT)     \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT))     \ + +#define PA_CHANNEL_POSITION_MASK_RIGHT                                  \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT)          \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT)         \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT)         \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT)    \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT)) + +#define PA_CHANNEL_POSITION_MASK_CENTER                                 \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER)         \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER)        \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER)         \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER)   \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_FRONT                                  \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT)           \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT)        \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER)       \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT)     \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT)    \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_REAR                                   \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT)            \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT)         \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER)        \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)      \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT)     \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER                     \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT)            \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT)         \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_TOP                                    \ +    (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER)           \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT)     \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT)    \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER)   \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)      \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT)     \ +     | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_ALL            \ +    ((pa_channel_position_mask_t) (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_MAX)-1)) +  #endif diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c index 307ce7b7..43b8cb3e 100644 --- a/src/pulsecore/sconv-s16le.c +++ b/src/pulsecore/sconv-s16le.c @@ -370,7 +370,7 @@ void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b) {      pa_assert(b);      for (; n > 0; n--) { -        *b = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8) >> 16); +        *b = (int16_t) (((int32_t) (UINT32_FROM(*a) << 8)) >> 16);          a++;          b++;      } @@ -416,8 +416,8 @@ void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b) {      pa_assert(b);      for (; n > 0; n--) { -        int32_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8)); -        *b = ((float) s) / 0x7FFFFFFF; +        int32_t s = (int32_t) (UINT32_FROM(*a) << 8); +        *b = (float) s / (float) 0x7FFFFFFF;          a ++;          b ++;      } @@ -428,8 +428,8 @@ void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b) {      pa_assert(b);      for (; n > 0; n--) { -        int32_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8)); -        float k = ((float) s) / 0x7FFFFFFF; +        int32_t s = (int32_t) (UINT32_FROM(*a) << 8); +        float k = (float) s / (float) 0x7FFFFFFF;          *b = PA_FLOAT32_SWAP(k);          a ++;          b ++; diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c index 29a9a453..d89f4283 100644 --- a/src/pulsecore/sconv.c +++ b/src/pulsecore/sconv.c @@ -75,7 +75,7 @@ static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) {      pa_assert(b);      for (; n > 0; n--, a++, b++) -        *b = (uint8_t) (*a / 0x100 + 0x80); +        *b = (uint8_t) ((uint16_t) *a >> 8) + (uint8_t) 0x80U;  }  /* float32 */ diff --git a/src/pulsecore/semaphore-posix.c b/src/pulsecore/semaphore-posix.c index 616d897d..2aa1bce9 100644 --- a/src/pulsecore/semaphore-posix.c +++ b/src/pulsecore/semaphore-posix.c @@ -65,3 +65,25 @@ void pa_semaphore_wait(pa_semaphore *s) {      pa_assert(ret == 0);  } + +pa_semaphore* pa_static_semaphore_get(pa_static_semaphore *s, unsigned value) { +    pa_semaphore *m; + +    pa_assert(s); + +    /* First, check if already initialized and short cut */ +    if ((m = pa_atomic_ptr_load(&s->ptr))) +        return m; + +    /* OK, not initialized, so let's allocate, and fill in */ +    m = pa_semaphore_new(value); +    if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m))) +        return m; + +    pa_semaphore_free(m); + +    /* Him, filling in failed, so someone else must have filled in +     * already */ +    pa_assert_se(m = pa_atomic_ptr_load(&s->ptr)); +    return m; +} diff --git a/src/pulsecore/semaphore.h b/src/pulsecore/semaphore.h index dc3ca6a5..2bf81496 100644 --- a/src/pulsecore/semaphore.h +++ b/src/pulsecore/semaphore.h @@ -22,6 +22,9 @@    USA.  ***/ +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> +  typedef struct pa_semaphore pa_semaphore;  pa_semaphore* pa_semaphore_new(unsigned value); @@ -30,4 +33,16 @@ void pa_semaphore_free(pa_semaphore *m);  void pa_semaphore_post(pa_semaphore *m);  void pa_semaphore_wait(pa_semaphore *m); +/* Static semaphores are basically just atomically updated pointers to + * pa_semaphore objects */ + +typedef struct pa_static_semaphore { +    pa_atomic_ptr_t ptr; +} pa_static_semaphore; + +#define PA_STATIC_SEMAPHORE_INIT { PA_ATOMIC_PTR_INIT(NULL) } + +/* When you call this make sure to pass always the same value parameter! */ +pa_semaphore* pa_static_semaphore_get(pa_static_semaphore *m, unsigned value); +  #endif diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index b8c5f786..6e428426 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -39,6 +39,11 @@  #include <sys/mman.h>  #endif +/* This is deprecated on glibc but is still used by FreeBSD */ +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif +  #include <pulse/xmalloc.h>  #include <pulse/gccmacro.h> @@ -69,7 +74,10 @@  #define SHM_MARKER ((int) 0xbeefcafe)  /* We now put this SHM marker at the end of each segment. It's - * optional, to not require a reboot when upgrading, though */ + * optional, to not require a reboot when upgrading, though. Note that + * on multiarch systems 32bit and 64bit processes might access this + * region simultaneously. The header fields need to be independant + * from the process' word with */  struct shm_marker {      pa_atomic_t marker; /* 0xbeefcafe */      pa_atomic_t pid; @@ -79,6 +87,8 @@ struct shm_marker {      uint64_t _reserved4;  } PA_GCC_PACKED; +#define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker)) +  static char *segment_name(char *fn, size_t l, unsigned id) {      pa_snprintf(fn, l, "/pulse-shm-%u", id);      return fn; @@ -97,8 +107,8 @@ int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) {       * ones */      pa_shm_cleanup(); -    /* Round up to make it aligned */ -    size = PA_ALIGN(size); +    /* Round up to make it page aligned */ +    size = PA_PAGE_ALIGN(size);      if (!shared) {          m->id = 0; @@ -136,25 +146,25 @@ int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) {              goto fail;          } -        m->size = size + PA_ALIGN(sizeof(struct shm_marker)); +        m->size = size + SHM_MARKER_SIZE;          if (ftruncate(fd, (off_t) m->size) < 0) {              pa_log("ftruncate() failed: %s", pa_cstrerror(errno));              goto fail;          } -        if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { +        if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {              pa_log("mmap() failed: %s", pa_cstrerror(errno));              goto fail;          }          /* We store our PID at the end of the shm block, so that we           * can check for dead shm segments later */ -        marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker))); +        marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE);          pa_atomic_store(&marker->pid, (int) getpid());          pa_atomic_store(&marker->marker, SHM_MARKER); -        pa_assert_se(close(fd) == 0); +        pa_assert_se(pa_close(fd) == 0);          m->do_unlink = TRUE;  #else          return -1; @@ -197,7 +207,7 @@ void pa_shm_free(pa_shm *m) {  #endif      } else {  #ifdef HAVE_SHM_OPEN -        if (munmap(m->ptr, m->size) < 0) +        if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)              pa_log("munmap() failed: %s", pa_cstrerror(errno));          if (m->do_unlink) { @@ -214,12 +224,12 @@ void pa_shm_free(pa_shm *m) {  #endif      } -    memset(m, 0, sizeof(*m)); +    pa_zero(*m);  }  void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {      void *ptr; -    size_t o, ps; +    size_t o;      pa_assert(m);      pa_assert(m->ptr); @@ -233,16 +243,19 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {      /* You're welcome to implement this as NOOP on systems that don't       * support it */ -    /* Align this to multiples of the page size */ +    /* Align the pointer up to multiples of the page size */      ptr = (uint8_t*) m->ptr + offset;      o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));      if (o > 0) { -        ps = PA_PAGE_SIZE; -        ptr = (uint8_t*) ptr + (ps - o); -        size -= ps - o; +        size_t delta = PA_PAGE_SIZE - o; +        ptr = (uint8_t*) ptr + delta; +        size -= delta;      } +    /* Align the size down to multiples of page size */ +    size = (size / PA_PAGE_SIZE) * PA_PAGE_SIZE; +  #ifdef MADV_REMOVE      if (madvise(ptr, size, MADV_REMOVE) >= 0)          return; @@ -254,9 +267,9 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {  #endif  #ifdef MADV_DONTNEED -    pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0); +    madvise(ptr, size, MADV_DONTNEED);  #elif defined(POSIX_MADV_DONTNEED) -    pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0); +    posix_madvise(ptr, size, POSIX_MADV_DONTNEED);  #endif  } @@ -283,7 +296,7 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) {      }      if (st.st_size <= 0 || -        st.st_size > (off_t) (MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker))) || +        st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) ||          PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {          pa_log("Invalid shared memory segment size");          goto fail; @@ -291,13 +304,13 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) {      m->size = (size_t) st.st_size; -    if ((m->ptr = mmap(NULL, m->size, PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { +    if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {          pa_log("mmap() failed: %s", pa_cstrerror(errno));          goto fail;      } -    m->do_unlink = 0; -    m->shared = 1; +    m->do_unlink = FALSE; +    m->shared = TRUE;      pa_assert_se(pa_close(fd) == 0); @@ -346,12 +359,12 @@ int pa_shm_cleanup(void) {          if (pa_shm_attach_ro(&seg, id) < 0)              continue; -        if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) { +        if (seg.size < SHM_MARKER_SIZE) {              pa_shm_free(&seg);              continue;          } -        m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker))); +        m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE);          if (pa_atomic_load(&m->marker) != SHM_MARKER) {              pa_shm_free(&seg); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 53e727bb..a5f96351 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -117,7 +117,8 @@ static void reset_callbacks(pa_sink_input *i) {      i->attach = NULL;      i->detach = NULL;      i->suspend = NULL; -    i->moved = NULL; +    i->suspend_within_thread = NULL; +    i->moving = NULL;      i->kill = NULL;      i->get_latency = NULL;      i->state_change = NULL; @@ -175,16 +176,8 @@ int pa_sink_input_new(      pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID);      if (!data->volume_is_set) { - -        if (data->sink->flags & PA_SINK_FLAT_VOLUME) { -            data->volume = *pa_sink_get_volume(data->sink, FALSE); -            pa_cvolume_remap(&data->volume, &data->sink->channel_map, &data->channel_map); -            data->volume_is_absolute = TRUE; -        } else { -            pa_cvolume_reset(&data->volume, data->sample_spec.channels); -            data->volume_is_absolute = FALSE; -        } - +        pa_cvolume_reset(&data->volume, data->sample_spec.channels); +        data->volume_is_absolute = FALSE;          data->save_volume = FALSE;      } @@ -278,15 +271,15 @@ int pa_sink_input_new(          /* 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 t = *pa_sink_get_volume(data->sink, FALSE); -        pa_cvolume_remap(&t, &data->sink->channel_map, &data->channel_map); - -        pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &t); +        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);      } else          i->virtual_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->save_volume = data->save_volume;      i->save_sink = data->save_sink;      i->save_muted = data->save_muted; @@ -333,8 +326,8 @@ int pa_sink_input_new(              0,              &i->sink->silence); -    pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0); -    pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0); +    pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0); +    pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0);      if (i->client)          pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0); @@ -449,7 +442,7 @@ void pa_sink_input_unlink(pa_sink_input *i) {          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); +            pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);          }          if (i->sink->asyncmsgq) @@ -527,9 +520,9 @@ void pa_sink_input_put(pa_sink_input *i) {      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); +        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);      } else -        pa_sw_cvolume_multiply(&i->soft_volume, &i->virtual_volume, &i->volume_factor); +        pa_sink_input_set_relative_volume(i, &i->virtual_volume);      i->thread_info.soft_volume = i->soft_volume;      i->thread_info.muted = i->muted; @@ -631,7 +624,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p               * data, so let's just hand out silence */              pa_atomic_store(&i->thread_info.drained, 1); -            pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE); +            pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);              i->thread_info.playing_for = 0;              if (i->thread_info.underrun_for != (uint64_t) -1)                  i->thread_info.underrun_for += ilength; @@ -776,7 +769,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam              if (amount > 0)                  /* Ok, now update the write pointer */ -                pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE); +                pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, TRUE);              if (i->thread_info.rewrite_flush)                  pa_memblockq_silence(i->thread_info.render_memblockq); @@ -819,26 +812,15 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes  /* in the  }  /* Called from thread context */ -static pa_usec_t fixup_latency(pa_sink *s, pa_usec_t usec) { -    pa_sink_assert_ref(s); - -    if (usec == (pa_usec_t) -1) -        return usec; - -    if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency) -        usec = s->thread_info.max_latency; - -    if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency) -        usec = s->thread_info.min_latency; - -    return usec; -} - -/* 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); -    usec = fixup_latency(i->sink, usec); +    if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) +        usec = i->sink->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); @@ -849,41 +831,62 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa  pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {      pa_sink_input_assert_ref(i); -    if (PA_SINK_INPUT_IS_LINKED(i->state)) +    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); -    else -        /* If this sink input is not realized yet, we have to touch -         * the thread info data directly */ +        return usec; +    } -        i->thread_info.requested_sink_latency = usec; +    /* If this sink input is not realized yet or we are being moved, +     * we have to touch the thread info data directly */ + +    if (i->sink) { +        if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) +            usec = i->sink->fixed_latency; + +        if (usec != (pa_usec_t) -1) { +            pa_usec_t min_latency, max_latency; +            pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); +            usec =  PA_CLAMP(usec, min_latency, max_latency); +        } +    } + +    i->thread_info.requested_sink_latency = usec;      return usec;  }  /* Called from main context */  pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { -    pa_usec_t usec = 0; -      pa_sink_input_assert_ref(i); -    if (PA_SINK_INPUT_IS_LINKED(i->state)) +    if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { +        pa_usec_t usec = 0;          pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); -    else -        /* If this sink input is not realized yet, we have to touch -         * the thread info data directly */ -        usec = i->thread_info.requested_sink_latency; +        return usec; +    } -    return usec; +    /* If this sink input is not realized yet or we are being moved, +     * we have to touch the thread info data directly */ + +    return i->thread_info.requested_sink_latency;  }  /* Called from main context */ -void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save) { +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) { +    pa_cvolume v; +      pa_sink_input_assert_ref(i);      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)); +    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_equal(volume, &i->virtual_volume))          return; @@ -897,17 +900,18 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_boo           * 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); +        pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE, FALSE, FALSE);      } else {          /* OK, we are in normal volume mode. The volume only affects           * ourselves */ -        pa_sw_cvolume_multiply(&i->soft_volume, volume, &i->volume_factor); +        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); +        /* 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);      } @@ -916,11 +920,67 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_boo  }  /* Called from main context */ -const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) { +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(PA_SINK_INPUT_IS_LINKED(i->state)); -    return &i->virtual_volume; +    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; + +    return volume; +} + +/* 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; +} + +/* 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_sink_input_assert_ref(i); +    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])); +    } + +    /* We don't copy the data to the thread_info data. That's left for someone else to do */  }  /* Called from main context */ @@ -1090,14 +1150,16 @@ int pa_sink_input_start_move(pa_sink_input *i) {      if (i->sink->flags & PA_SINK_FLAT_VOLUME) {          pa_cvolume new_volume; -        /* Make the absolute volume relative */ -        i->virtual_volume = i->soft_volume; -        i->soft_volume = i->volume_factor; +        /* 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);          /* 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); +        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);      }      pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); @@ -1105,6 +1167,8 @@ int pa_sink_input_start_move(pa_sink_input *i) {      pa_sink_update_status(i->sink);      i->sink = NULL; +    pa_sink_input_unref(i); +      return 0;  } @@ -1147,9 +1211,12 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {      } else          new_resampler = NULL; +    if (i->moving) +        i->moving(i, dest); +      i->sink = dest;      i->save_sink = save; -    pa_idxset_put(dest->inputs, i, NULL); +    pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL);      if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)          i->sink->n_corked++; @@ -1173,20 +1240,19 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {                  0,                  &i->sink->silence);      } -      pa_sink_update_status(dest);      if (i->sink->flags & PA_SINK_FLAT_VOLUME) {          pa_cvolume new_volume;          /* Make relative volume absolute again */ -        pa_cvolume t = dest->virtual_volume; +        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);          /* 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); +        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);      }      pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); @@ -1194,9 +1260,6 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {      pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name);      /* Notify everyone */ -    if (i->moved) -        i->moved(i); -      pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i);      pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); @@ -1218,11 +1281,19 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {      if (!pa_sink_input_may_move_to(i, dest))          return -PA_ERR_NOTSUPPORTED; -    if ((r = pa_sink_input_start_move(i)) < 0) +    pa_sink_input_ref(i); + +    if ((r = pa_sink_input_start_move(i)) < 0) { +        pa_sink_input_unref(i);          return r; +    } -    if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) +    if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) { +        pa_sink_input_unref(i);          return r; +    } + +    pa_sink_input_unref(i);      return 0;  } @@ -1291,12 +1362,9 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t          case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {              pa_usec_t *r = userdata; -            pa_usec_t sink_usec = 0;              r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); - -            if (i->sink->parent.process_msg(PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_usec, 0, NULL) >= 0) -                r[1] += sink_usec; +            r[1] += pa_sink_get_latency_within_thread(i->sink);              return 0;          } diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index e3801687..98144d41 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -91,7 +91,11 @@ struct pa_sink_input {      pa_sink_input *sync_prev, *sync_next; -    pa_cvolume virtual_volume, soft_volume, volume_factor; +    /* 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_bool_t muted:1;      /* if TRUE then the source we are connected to and/or the volume @@ -121,7 +125,7 @@ struct pa_sink_input {       * changes. Called from IO context. */      void (*update_max_rewind) (pa_sink_input *i, size_t nbytes); /* may be NULL */ -    /* Called whenever the maxiumum request size of the sink +    /* Called whenever the maximum request size of the sink       * changes. Called from IO context. */      void (*update_max_request) (pa_sink_input *i, size_t nbytes); /* may be NULL */ @@ -144,13 +148,19 @@ struct pa_sink_input {       * disconnected from its sink. Called from IO thread context */      void (*detach) (pa_sink_input *i);           /* may be NULL */ -    /* If non-NULL called whenever the the sink this input is attached +    /* If non-NULL called whenever the sink this input is attached       * to suspends or resumes. Called from main context */      void (*suspend) (pa_sink_input *i, pa_bool_t b);   /* may be NULL */ -    /* If non-NULL called whenever the the sink this input is attached -     * to changes. Called from main context */ -    void (*moved) (pa_sink_input *i);   /* may be NULL */ +    /* If non-NULL called whenever the sink this input is attached +     * to suspends or resumes. Called from IO context */ +    void (*suspend_within_thread) (pa_sink_input *i, pa_bool_t b);   /* may be NULL */ + +    /* 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. */ +    void (*moving) (pa_sink_input *i, pa_sink *dest);   /* may be NULL */      /* Supposed to unlink and destroy this stream. Called from main       * context. */ @@ -300,10 +310,14 @@ void pa_sink_input_kill(pa_sink_input*i);  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); -const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i); +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); +  void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p);  pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i); @@ -342,4 +356,7 @@ 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); +  #endif diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index c725595f..d8f3c7d1 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -48,7 +48,9 @@  #define MAX_MIX_CHANNELS 32  #define MIX_BUFFER_LENGTH (PA_PAGE_SIZE) -#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) +#define ABSOLUTE_MIN_LATENCY (500) +#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); @@ -98,11 +100,51 @@ void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute) {      data->muted = !!mute;  } +void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port) { +    pa_assert(data); + +    pa_xfree(data->active_port); +    data->active_port = pa_xstrdup(port); +} +  void pa_sink_new_data_done(pa_sink_new_data *data) {      pa_assert(data); -    pa_xfree(data->name);      pa_proplist_free(data->proplist); + +    if (data->ports) { +        pa_device_port *p; + +        while ((p = pa_hashmap_steal_first(data->ports))) +            pa_device_port_free(p); + +        pa_hashmap_free(data->ports, NULL, NULL); +    } + +    pa_xfree(data->name); +    pa_xfree(data->active_port); +} + +pa_device_port *pa_device_port_new(const char *name, const char *description, size_t extra) { +    pa_device_port *p; + +    pa_assert(name); + +    p = pa_xmalloc(PA_ALIGN(sizeof(pa_device_port)) + extra); +    p->name = pa_xstrdup(name); +    p->description = pa_xstrdup(description); + +    p->priority = 0; + +    return p; +} + +void pa_device_port_free(pa_device_port *p) { +    pa_assert(p); + +    pa_xfree(p->name); +    pa_xfree(p->description); +    pa_xfree(p);  }  /* Called from main context */ @@ -116,6 +158,7 @@ static void reset_callbacks(pa_sink *s) {      s->set_mute = NULL;      s->request_rewind = NULL;      s->update_requested_latency = NULL; +    s->set_port = NULL;  }  /* Called from main context */ @@ -138,6 +181,7 @@ pa_sink* pa_sink_new(      s = pa_msgobject_new(pa_sink);      if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SINK, s, data->namereg_fail))) { +        pa_log_debug("Failed to register name %s.", data->name);          pa_xfree(s);          return NULL;      } @@ -150,6 +194,8 @@ pa_sink* pa_sink_new(          return NULL;      } +    /* FIXME, need to free s here on failure */ +      pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));      pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); @@ -175,6 +221,7 @@ pa_sink* pa_sink_new(      pa_device_init_description(data->proplist);      pa_device_init_icon(data->proplist, TRUE); +    pa_device_init_intended_roles(data->proplist);      if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) {          pa_xfree(s); @@ -188,6 +235,7 @@ pa_sink* pa_sink_new(      s->core = core;      s->state = PA_SINK_INIT;      s->flags = flags; +    s->suspend_cause = 0;      s->name = pa_xstrdup(name);      s->proplist = pa_proplist_copy(data->proplist);      s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); @@ -200,19 +248,45 @@ pa_sink* pa_sink_new(      s->inputs = pa_idxset_new(NULL, NULL);      s->n_corked = 0; -    s->virtual_volume = data->volume; +    s->reference_volume = s->virtual_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 */ +    s->ports = data->ports; +    data->ports = NULL; + +    s->active_port = NULL; +    s->save_port = FALSE; + +    if (data->active_port && s->ports) +        if ((s->active_port = pa_hashmap_get(s->ports, data->active_port))) +            s->save_port = data->save_port; + +    if (!s->active_port && s->ports) { +        void *state; +        pa_device_port *p; + +        PA_HASHMAP_FOREACH(p, s->ports, state) +            if (!s->active_port || p->priority > s->active_port->priority) +                s->active_port = p; +    } + +    s->save_volume = data->save_volume; +    s->save_muted = data->save_muted; +      pa_silence_memchunk_get(              &core->silence_cache,              core->mempool, @@ -230,8 +304,8 @@ pa_sink* pa_sink_new(      s->thread_info.max_request = 0;      s->thread_info.requested_latency_valid = FALSE;      s->thread_info.requested_latency = 0; -    s->thread_info.min_latency = DEFAULT_MIN_LATENCY; -    s->thread_info.max_latency = 0; +    s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; +    s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;      pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); @@ -259,7 +333,9 @@ pa_sink* pa_sink_new(      pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Monitor of %s", dn ? dn : s->name);      pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "monitor"); -    s->monitor_source = pa_source_new(core, &source_data, PA_SOURCE_LATENCY); +    s->monitor_source = pa_source_new(core, &source_data, +                                      ((flags & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | +                                      ((flags & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0));      pa_source_new_data_done(&source_data); @@ -343,22 +419,30 @@ void pa_sink_put(pa_sink* s) {      /* 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 || -              s->thread_info.min_latency <= s->thread_info.max_latency); +    pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); + +    /* Generally, flags should be initialized via pa_sink_new(). As a +     * special exception we allow volume related flags to be set +     * between _new() and _put(). */ -    if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) { +    if (!(s->flags & PA_SINK_HW_VOLUME_CTRL))          s->flags |= PA_SINK_DECIBEL_VOLUME; -        s->thread_info.soft_volume = s->soft_volume; -        s->thread_info.soft_muted = s->muted; -    } +    if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes) +        s->flags |= PA_SINK_FLAT_VOLUME; + +    s->thread_info.soft_volume = s->soft_volume; +    s->thread_info.soft_muted = s->muted; -    if (s->flags & PA_SINK_DECIBEL_VOLUME) -        s->n_volume_steps = PA_VOLUME_NORM+1; +    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_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY)); +    pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY)); -    if (s->core->flat_volumes) -        if (s->flags & PA_SINK_DECIBEL_VOLUME) -            s->flags |= PA_SINK_FLAT_VOLUME; +    pa_assert(s->monitor_source->fixed_latency == s->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);      pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0); @@ -451,6 +535,15 @@ static void sink_free(pa_object *o) {      if (s->proplist)          pa_proplist_free(s->proplist); +    if (s->ports) { +        pa_device_port *p; + +        while ((p = pa_hashmap_steal_first(s->ports))) +            pa_device_port_free(p); + +        pa_hashmap_free(s->ports, NULL, NULL); +    } +      pa_xfree(s);  } @@ -469,6 +562,7 @@ void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) {      pa_sink_assert_ref(s);      s->rtpoll = p; +      if (s->monitor_source)          pa_source_set_rtpoll(s->monitor_source, p);  } @@ -485,32 +579,50 @@ int pa_sink_update_status(pa_sink*s) {  }  /* Called from main context */ -int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause) {      pa_sink_assert_ref(s);      pa_assert(PA_SINK_IS_LINKED(s->state)); +    pa_assert(cause != 0); + +    if (suspend) { +        s->suspend_cause |= cause; +        s->monitor_source->suspend_cause |= cause; +    } else { +        s->suspend_cause &= ~cause; +        s->monitor_source->suspend_cause &= ~cause; +    } -    if (suspend) +    if ((pa_sink_get_state(s) == PA_SINK_SUSPENDED) == !!s->suspend_cause) +        return 0; + +    pa_log_debug("Suspend cause of sink %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming"); + +    if (s->suspend_cause)          return sink_set_state(s, PA_SINK_SUSPENDED);      else          return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE);  }  /* Called from main context */ -pa_queue *pa_sink_move_all_start(pa_sink *s) { -    pa_queue *q; +pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q) {      pa_sink_input *i, *n;      uint32_t idx;      pa_sink_assert_ref(s);      pa_assert(PA_SINK_IS_LINKED(s->state)); -    q = pa_queue_new(); +    if (!q) +        q = pa_queue_new();      for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = n) {          n = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx)); +        pa_sink_input_ref(i); +          if (pa_sink_input_start_move(i) >= 0) -            pa_queue_push(q, pa_sink_input_ref(i)); +            pa_queue_push(q, i); +        else +            pa_sink_input_unref(i);      }      return q; @@ -769,11 +881,17 @@ 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)) { -            pa_memchunk_make_writable(result, 0); -            if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) -                pa_silence_memchunk(result, &s->sample_spec); -            else +            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); +            }          }      } else {          void *ptr; @@ -914,22 +1032,95 @@ 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_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); -    /*** This needs optimization ***/ +    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); -    result->index = 0; -    result->length = length; -    result->memblock = pa_memblock_new(s->core->mempool, length); +        if (result->length > length) +            result->length = length; -    pa_sink_render_into_full(s, 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, 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_memblock_release(result->memblock); +    } + +    inputs_drop(s, info, n, 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; + +            pa_sink_render_into(s, &chunk); + +            d += chunk.length; +            l -= chunk.length; +        } +        result->length = length; +    } + +    pa_sink_unref(s);  }  /* Called from main thread */ @@ -952,6 +1143,72 @@ pa_usec_t pa_sink_get_latency(pa_sink *s) {      return usec;  } +/* Called from IO thread */ +pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) { +    pa_usec_t usec = 0; +    pa_msgobject *o; + +    pa_sink_assert_ref(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! */ + +    if (s->thread_info.state == PA_SINK_SUSPENDED) +        return 0; + +    if (!(s->flags & PA_SINK_LATENCY)) +        return 0; + +    o = PA_MSGOBJECT(s); + +    /* 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; + +    return usec; +} + +static void compute_new_soft_volume(pa_sink_input *i, const pa_cvolume *new_volume) { +    unsigned c; + +    pa_sink_input_assert_ref(i); +    pa_assert(new_volume->channels == i->sample_spec.channels); + +    /* +     * This basically calculates: +     * +     * i->relative_volume := i->virtual_volume / new_volume +     * i->soft_volume := i->relative_volume * i->volume_factor +     */ + +    /* The new sink volume passed in here must already be remapped to +     * the sink input's channel map! */ + +    i->soft_volume.channels = i->sample_spec.channels; + +    for (c = 0; c < i->sample_spec.channels; c++) + +        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]); + +            i->soft_volume.values[c] = pa_sw_volume_from_linear( +                    i->relative_volume[c] * +                    pa_sw_volume_to_linear(i->volume_factor.values[c])); +        } + +    /* 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); + +    /* 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) {      pa_sink_input *i; @@ -962,16 +1219,16 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {      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 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_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().*/      if (pa_idxset_isempty(s->inputs)) {          /* In the special case that we have no sink input we leave the           * volume unmodified. */ -        *new_volume = s->virtual_volume; +        *new_volume = s->reference_volume;          return;      } @@ -998,26 +1255,22 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {          remapped_new_volume = *new_volume;          pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); -        pa_sw_cvolume_divide(&i->soft_volume, &i->virtual_volume, &remapped_new_volume); -        pa_sw_cvolume_multiply(&i->soft_volume, &i->soft_volume, &i->volume_factor); +        compute_new_soft_volume(i, &remapped_new_volume); -        /* Hooks have the ability to play games with i->soft_volume */ -        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); - -        /* We don't issue PA_SINK_INPUT_MESSAGE_SET_VOLUME because -         * we want the update to have atomically with the sink -         * volume update, hence we do it within the -         * pa_sink_set_flat_volume() call below*/ +        /* 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 */      }  }  /* Called from main thread */ -void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) { +void pa_sink_propagate_flat_volume(pa_sink *s) {      pa_sink_input *i;      uint32_t idx;      pa_sink_assert_ref(s); -    pa_assert(old_volume);      pa_assert(PA_SINK_IS_LINKED(s->state));      pa_assert(s->flags & PA_SINK_FLAT_VOLUME); @@ -1026,39 +1279,43 @@ void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) {       * 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 remapped_old_volume, remapped_new_volume, fixed_volume; +        pa_cvolume sink_volume, new_virtual_volume;          unsigned c; -        remapped_new_volume = s->virtual_volume; -        pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); +        /* This basically calculates i->virtual_volume := i->relative_volume * s->virtual_volume  */ -        remapped_old_volume = *old_volume; -        pa_cvolume_remap(&remapped_old_volume, &s->channel_map, &i->channel_map); +        sink_volume = s->virtual_volume; +        pa_cvolume_remap(&sink_volume, &s->channel_map, &i->channel_map);          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])); -            if (remapped_old_volume.values[c] == PA_VOLUME_MUTED) -                fixed_volume.values[c] = PA_VOLUME_MUTED; -            else -                fixed_volume.values[c] = (pa_volume_t) -                    ((uint64_t) i->virtual_volume.values[c] * -                     (uint64_t) remapped_new_volume.values[c] / -                     (uint64_t) remapped_old_volume.values[c]); +        new_virtual_volume.channels = i->sample_spec.channels; -        fixed_volume.channels = i->virtual_volume.channels; +        if (!pa_cvolume_equal(&new_virtual_volume, &i->virtual_volume)) { +            i->virtual_volume = new_virtual_volume; -        if (!pa_cvolume_equal(&fixed_volume, &i->virtual_volume)) { -            i->virtual_volume = fixed_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);              /* 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_cvolume old_virtual_volume; +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;      pa_sink_assert_ref(s); @@ -1067,19 +1324,22 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat      pa_assert(pa_cvolume_valid(volume));      pa_assert(pa_cvolume_compatible(volume, &s->sample_spec)); -    old_virtual_volume = s->virtual_volume; +    virtual_volume_changed = !pa_cvolume_equal(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; + +    if (become_reference) +        s->reference_volume = s->virtual_volume;      /* 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_virtual_volume); +            pa_sink_propagate_flat_volume(s);      if (s->set_volume) {          /* If we have a function set_volume(), then we do not apply a -         * soft volume by default. However, set_volume() is apply one -         * to s->soft_volume */ +         * soft volume by default. However, set_volume() is free to +         * apply one to s->soft_volume */          pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);          s->set_volume(s); @@ -1111,7 +1371,7 @@ void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {  }  /* Called from main thread */ -const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) { +const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh, pa_bool_t reference) {      pa_sink_assert_ref(s);      if (s->refresh_volume || force_refresh) { @@ -1124,18 +1384,39 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {          if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) { +            s->reference_volume = s->virtual_volume; +              if (s->flags & PA_SINK_FLAT_VOLUME) -                pa_sink_propagate_flat_volume(s, &old_virtual_volume); +                pa_sink_propagate_flat_volume(s);              pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);          }      } -    return &s->virtual_volume; +    return reference ? &s->reference_volume : &s->virtual_volume;  }  /* Called from main thread */ -void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) { +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume, pa_bool_t save) { +    pa_sink_assert_ref(s); + +    /* 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; +    } + +    s->reference_volume = s->virtual_volume = *new_volume; +    s->save_volume = save; + +    if (s->flags & PA_SINK_FLAT_VOLUME) +        pa_sink_propagate_flat_volume(s); + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main thread */ +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); @@ -1143,6 +1424,7 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) {      old_muted = s->muted;      s->muted = mute; +    s->save_muted = (old_muted == s->muted && s->save_muted) || save;      if (s->set_mute)          s->set_mute(s); @@ -1166,20 +1448,40 @@ 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) +        if (old_muted != s->muted) {              pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + +            /* Make sure the soft mute status stays in sync */ +            pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); +        }      }      return s->muted;  }  /* Called from main thread */ -pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) { +void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted, pa_bool_t save) { +    pa_sink_assert_ref(s); + +    /* 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; +        return; +    } + +    s->muted = new_muted; +    s->save_muted = save; + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} +/* 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(p); -    pa_proplist_update(s->proplist, mode, p); +    if (p) +        pa_proplist_update(s->proplist, mode, p);      if (PA_SINK_IS_LINKED(s->state)) {          pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s); @@ -1268,7 +1570,7 @@ unsigned pa_sink_check_suspend(pa_sink *s) {      ret = 0; -    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) {          pa_sink_input_state_t st;          st = pa_sink_input_get_state(i); @@ -1508,9 +1810,13 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse                  pa_sink_request_rewind(s, (size_t) -1);              } -            if (s->flags & PA_SINK_FLAT_VOLUME) -                sync_input_volumes_within_thread(s); +            if (!(s->flags & PA_SINK_FLAT_VOLUME)) +                return 0; +            /* Fall through ... */ + +        case PA_SINK_MESSAGE_SYNC_VOLUMES: +            sync_input_volumes_within_thread(s);              return 0;          case PA_SINK_MESSAGE_GET_VOLUME: @@ -1528,7 +1834,11 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse          case PA_SINK_MESSAGE_GET_MUTE:              return 0; -        case PA_SINK_MESSAGE_SET_STATE: +        case PA_SINK_MESSAGE_SET_STATE: { + +            pa_bool_t suspend_change = +                (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(PA_PTR_TO_UINT(userdata))) || +                (PA_SINK_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SINK_SUSPENDED);              s->thread_info.state = PA_PTR_TO_UINT(userdata); @@ -1537,7 +1847,17 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse                  s->thread_info.rewind_requested = FALSE;              } +            if (suspend_change) { +                pa_sink_input *i; +                void *state = NULL; + +                while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) +                    if (i->suspend_within_thread) +                        i->suspend_within_thread(i, s->thread_info.state == PA_SINK_SUSPENDED); +            } +              return 0; +        }          case PA_SINK_MESSAGE_DETACH: @@ -1565,7 +1885,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse          case PA_SINK_MESSAGE_SET_LATENCY_RANGE: {              pa_usec_t *r = userdata; -            pa_sink_update_latency_range(s, r[0], r[1]); +            pa_sink_set_latency_range_within_thread(s, r[0], r[1]);              return 0;          } @@ -1589,6 +1909,16 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse              *((size_t*) userdata) = s->thread_info.max_request;              return 0; +        case PA_SINK_MESSAGE_SET_MAX_REWIND: + +            pa_sink_set_max_rewind_within_thread(s, (size_t) offset); +            return 0; + +        case PA_SINK_MESSAGE_SET_MAX_REQUEST: + +            pa_sink_set_max_request_within_thread(s, (size_t) offset); +            return 0; +          case PA_SINK_MESSAGE_GET_LATENCY:          case PA_SINK_MESSAGE_MAX:              ; @@ -1598,17 +1928,18 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse  }  /* Called from main thread */ -int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) { +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) {      pa_sink *sink;      uint32_t idx;      int ret = 0;      pa_core_assert_ref(c); +    pa_assert(cause != 0);      for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx))) {          int r; -        if ((r = pa_sink_suspend(sink, suspend)) < 0) +        if ((r = pa_sink_suspend(sink, suspend, cause)) < 0)              ret = r;      } @@ -1696,6 +2027,9 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) {      pa_sink_assert_ref(s); +    if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) +        return PA_CLAMP(s->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; @@ -1711,17 +2045,15 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) {          (result == (pa_usec_t) -1 || result > monitor_latency))          result = monitor_latency; -    if (result != (pa_usec_t) -1) { -        if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) -            result = s->thread_info.max_latency; +    if (result != (pa_usec_t) -1) +        result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); -        if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) -            result = s->thread_info.min_latency; +    if (PA_SINK_IS_LINKED(s->thread_info.state)) { +        /* Only cache if properly initialized */ +        s->thread_info.requested_latency = result; +        s->thread_info.requested_latency_valid = TRUE;      } -    s->thread_info.requested_latency = result; -    s->thread_info.requested_latency_valid = TRUE; -      return result;  } @@ -1740,7 +2072,7 @@ pa_usec_t pa_sink_get_requested_latency(pa_sink *s) {  }  /* Called from IO as well as the main thread -- the latter only before the IO thread started up */ -void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { +void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) {      pa_sink_input *i;      void *state = NULL; @@ -1757,11 +2089,21 @@ void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) {      }      if (s->monitor_source) -        pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); +        pa_source_set_max_rewind_within_thread(s->monitor_source, s->thread_info.max_rewind); +} + +/* Called from main thread */ +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { +    pa_sink_assert_ref(s); + +    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); +    else +        pa_sink_set_max_rewind_within_thread(s, max_rewind);  }  /* Called from IO as well as the main thread -- the latter only before the IO thread started up */ -void pa_sink_set_max_request(pa_sink *s, size_t max_request) { +void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) {      void *state = NULL;      pa_sink_assert_ref(s); @@ -1779,6 +2121,16 @@ void pa_sink_set_max_request(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); + +    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); +    else +        pa_sink_set_max_request_within_thread(s, max_request); +} +  /* Called from IO thread */  void pa_sink_invalidate_requested_latency(pa_sink *s) {      pa_sink_input *i; @@ -1786,14 +2138,20 @@ void pa_sink_invalidate_requested_latency(pa_sink *s) {      pa_sink_assert_ref(s); +    if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) +        return; +      s->thread_info.requested_latency_valid = FALSE; -    if (s->update_requested_latency) -        s->update_requested_latency(s); +    if (PA_SINK_IS_LINKED(s->thread_info.state)) { -    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) -        if (i->update_sink_requested_latency) -            i->update_sink_requested_latency(i); +        if (s->update_requested_latency) +            s->update_requested_latency(s); + +        while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) +            if (i->update_sink_requested_latency) +                i->update_sink_requested_latency(i); +    }  }  /* Called from main thread */ @@ -1801,19 +2159,23 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_      pa_sink_assert_ref(s);      /* min_latency == 0:           no limit -     * min_latency == (size_t) -1: default limit       * min_latency anything else:  specified limit       *       * Similar for max_latency */ -    if (min_latency == (pa_usec_t) -1) -        min_latency = DEFAULT_MIN_LATENCY; +    if (min_latency < ABSOLUTE_MIN_LATENCY) +        min_latency = ABSOLUTE_MIN_LATENCY; -    if (max_latency == (pa_usec_t) -1) -        max_latency = min_latency; +    if (max_latency <= 0 || +        max_latency > ABSOLUTE_MAX_LATENCY) +        max_latency = ABSOLUTE_MAX_LATENCY; -    pa_assert(!min_latency || !max_latency || -              min_latency <= max_latency); +    pa_assert(min_latency <= max_latency); + +    /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */ +    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && +               max_latency == ABSOLUTE_MAX_LATENCY) || +              (s->flags & PA_SINK_DYNAMIC_LATENCY));      if (PA_SINK_IS_LINKED(s->state)) {          pa_usec_t r[2]; @@ -1822,15 +2184,8 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_          r[1] = max_latency;          pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); -    } else { -        s->thread_info.min_latency = min_latency; -        s->thread_info.max_latency = max_latency; - -        s->monitor_source->thread_info.min_latency = min_latency; -        s->monitor_source->thread_info.max_latency = max_latency; - -        s->thread_info.requested_latency_valid = s->monitor_source->thread_info.requested_latency_valid = FALSE; -    } +    } else +        pa_sink_set_latency_range_within_thread(s, min_latency, max_latency);  }  /* Called from main thread */ @@ -1853,25 +2208,50 @@ 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_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { -    pa_sink_input *i; +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_assert(!min_latency || !max_latency || -              min_latency <= max_latency); +    pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); +    pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); +    pa_assert(min_latency <= max_latency); + +    /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */ +    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && +               max_latency == ABSOLUTE_MAX_LATENCY) || +              (s->flags & PA_SINK_DYNAMIC_LATENCY));      s->thread_info.min_latency = min_latency;      s->thread_info.max_latency = max_latency; -    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) -        if (i->update_sink_latency_range) -            i->update_sink_latency_range(i); +    if (PA_SINK_IS_LINKED(s->thread_info.state)) { +        pa_sink_input *i; + +        while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) +            if (i->update_sink_latency_range) +                i->update_sink_latency_range(i); +    }      pa_sink_invalidate_requested_latency(s); -    pa_source_update_latency_range(s->monitor_source, min_latency, max_latency); +    pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency); +} + +/* Called from main thread, before the sink is put */ +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) { +    pa_sink_assert_ref(s); + +    pa_assert(pa_sink_get_state(s) == PA_SINK_INIT); + +    if (latency < ABSOLUTE_MIN_LATENCY) +        latency = ABSOLUTE_MIN_LATENCY; + +    if (latency > ABSOLUTE_MAX_LATENCY) +        latency = ABSOLUTE_MAX_LATENCY; + +    s->fixed_latency = latency; +    pa_source_set_fixed_latency(s->monitor_source, latency);  }  /* Called from main context */ @@ -1901,6 +2281,41 @@ size_t pa_sink_get_max_request(pa_sink *s) {  }  /* Called from main context */ +int pa_sink_set_port(pa_sink *s, const char *name, pa_bool_t save) { +    pa_device_port *port; + +    pa_assert(s); + +    if (!s->set_port) { +        pa_log_debug("set_port() operation not implemented for sink %u \"%s\"", s->index, s->name); +        return -PA_ERR_NOTIMPLEMENTED; +    } + +    if (!s->ports) +        return -PA_ERR_NOENTITY; + +    if (!(port = pa_hashmap_get(s->ports, name))) +        return -PA_ERR_NOENTITY; + +    if (s->active_port == port) { +        s->save_port = s->save_port || save; +        return 0; +    } + +    if ((s->set_port(s, port)) < 0) +        return -PA_ERR_NOENTITY; + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + +    pa_log_info("Changed port of sink %u \"%s\" to %s", s->index, s->name, port->name); + +    s->active_port = port; +    s->save_port = 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; @@ -1923,6 +2338,21 @@ pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) {              t = "multimedia-player";          else if (pa_streq(ff, "tv"))              t = "video-display"; + +        /* +         * The following icons are not part of the icon naming spec, +         * because Rodney Dawes sucks as the maintainer of that spec. +         * +         * http://lists.freedesktop.org/archives/xdg/2009-May/010397.html +         */ +        else if (pa_streq(ff, "headset")) +            t = "audio-headset"; +        else if (pa_streq(ff, "headphone")) +            t = "audio-headphones"; +        else if (pa_streq(ff, "speaker")) +            t = "audio-speakers"; +        else if (pa_streq(ff, "hands-free")) +            t = "audio-handsfree";      }      if (!t) @@ -1954,28 +2384,49 @@ pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) {  }  pa_bool_t pa_device_init_description(pa_proplist *p) { -    const char *s; +    const char *s, *d = NULL, *k;      pa_assert(p);      if (pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))          return TRUE;      if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) -        if (pa_streq(s, "internal")) { -            pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, _("Internal Audio")); -            return TRUE; -        } +        if (pa_streq(s, "internal")) +            d = _("Internal Audio"); -    if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) -        if (pa_streq(s, "modem")) { -            pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, _("Modem")); -            return TRUE; -        } +    if (!d) +        if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) +            if (pa_streq(s, "modem")) +                d = _("Modem"); + +    if (!d) +        d = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME); + +    if (!d) +        return FALSE; + +    k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION); + +    if (d && k) +        pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, _("%s %s"), d, k); +    else if (d) +        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d); + +    return TRUE; +} -    if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME))) { -        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, s); +pa_bool_t pa_device_init_intended_roles(pa_proplist *p) { +    const char *s; +    pa_assert(p); + +    if (pa_proplist_contains(p, PA_PROP_DEVICE_INTENDED_ROLES))          return TRUE; -    } + +    if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) +        if (pa_streq(s, "handset") || pa_streq(s, "hands-free")) { +            pa_proplist_sets(p, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); +            return TRUE; +        }      return FALSE;  } diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 0d33679f..d16fcc01 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -24,6 +24,7 @@  ***/  typedef struct pa_sink pa_sink; +typedef struct pa_device_port pa_device_port;  #include <inttypes.h> @@ -49,13 +50,26 @@ static inline pa_bool_t PA_SINK_IS_LINKED(pa_sink_state_t x) {      return x == PA_SINK_RUNNING || x == PA_SINK_IDLE || x == PA_SINK_SUSPENDED;  } +struct pa_device_port { +    char *name; +    char *description; + +    unsigned priority; + +    /* .. followed by some implementation specific data */ +}; + +#define PA_DEVICE_PORT_DATA(d) ((void*) ((uint8_t*) d + PA_ALIGN(sizeof(pa_device_port)))) +  struct pa_sink {      pa_msgobject parent;      uint32_t index;      pa_core *core; +      pa_sink_state_t state;      pa_sink_flags_t flags; +    pa_suspend_cause_t suspend_cause;      char *name;      char *driver;                           /* may be NULL */ @@ -74,17 +88,28 @@ struct pa_sink {      pa_volume_t base_volume; /* shall be constant */      unsigned n_volume_steps; /* shall be constant */ -    pa_cvolume virtual_volume, soft_volume; +    /* 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 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;      pa_bool_t refresh_muted:1; +    pa_bool_t save_port:1; +    pa_bool_t save_volume:1; +    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; +      /* Called when the main loop requests a state change. Called from       * main loop context. If returns -1 the state change will be       * inhibited */ @@ -120,6 +145,10 @@ struct pa_sink {       * thread context. */      void (*update_requested_latency)(pa_sink *s); /* dito */ +    /* Called whenever the port shall be changed. Called from main +     * thread. */ +    int (*set_port)(pa_sink *s, pa_device_port *port); /* dito */ +      /* Contains copies of the above data so that the real-time worker       * thread can work without access locking */      struct { @@ -159,6 +188,7 @@ typedef enum pa_sink_message {      PA_SINK_MESSAGE_REMOVE_INPUT,      PA_SINK_MESSAGE_GET_VOLUME,      PA_SINK_MESSAGE_SET_VOLUME, +    PA_SINK_MESSAGE_SYNC_VOLUMES,      PA_SINK_MESSAGE_GET_MUTE,      PA_SINK_MESSAGE_SET_MUTE,      PA_SINK_MESSAGE_GET_LATENCY, @@ -172,6 +202,8 @@ typedef enum pa_sink_message {      PA_SINK_MESSAGE_GET_LATENCY_RANGE,      PA_SINK_MESSAGE_GET_MAX_REWIND,      PA_SINK_MESSAGE_GET_MAX_REQUEST, +    PA_SINK_MESSAGE_SET_MAX_REWIND, +    PA_SINK_MESSAGE_SET_MAX_REQUEST,      PA_SINK_MESSAGE_MAX  } pa_sink_message_t; @@ -183,6 +215,9 @@ typedef struct pa_sink_new_data {      pa_module *module;      pa_card *card; +    pa_hashmap *ports; +    char *active_port; +      pa_sample_spec sample_spec;      pa_channel_map channel_map;      pa_cvolume volume; @@ -194,6 +229,10 @@ typedef struct pa_sink_new_data {      pa_bool_t muted_is_set:1;      pa_bool_t namereg_fail:1; + +    pa_bool_t save_port:1; +    pa_bool_t save_volume:1; +    pa_bool_t save_muted:1;  } pa_sink_new_data;  pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data); @@ -202,6 +241,7 @@ void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_sp  void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map);  void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume);  void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute); +void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port);  void pa_sink_new_data_done(pa_sink_new_data *data);  /*** To be called exclusively by the sink driver, from main context */ @@ -218,15 +258,21 @@ void pa_sink_set_description(pa_sink *s, const char *description);  void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q);  void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p); +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); +void pa_sink_set_max_request(pa_sink *s, size_t max_request);  void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency);  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);  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);  /**** May be called by everyone, from main context */ @@ -239,26 +285,29 @@ size_t pa_sink_get_max_rewind(pa_sink *s);  size_t pa_sink_get_max_request(pa_sink *s);  int pa_sink_update_status(pa_sink*s); -int pa_sink_suspend(pa_sink *s, pa_bool_t suspend); -int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend); +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, const pa_cvolume *old_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 propagate, pa_bool_t sendmsg); -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); +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);  pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p); +int pa_sink_set_port(pa_sink *s, const char *name, pa_bool_t save); +  unsigned pa_sink_linked_by(pa_sink *s); /* Number of connected streams */  unsigned pa_sink_used_by(pa_sink *s); /* Number of connected streams which are not corked */  unsigned pa_sink_check_suspend(pa_sink *s); /* Returns how many streams are active that don't allow suspensions */  #define pa_sink_get_state(s) ((s)->state)  /* Moves all inputs away, and stores them in pa_queue */ -pa_queue *pa_sink_move_all_start(pa_sink *s); +pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q);  void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, pa_bool_t save);  void pa_sink_move_all_fail(pa_queue *q); @@ -278,10 +327,10 @@ void pa_sink_detach_within_thread(pa_sink *s);  pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s); -void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); -void pa_sink_set_max_request(pa_sink *s, size_t max_request); +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_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency);  /*** To be called exclusively by sink input drivers, from IO context */ @@ -289,4 +338,9 @@ void pa_sink_request_rewind(pa_sink*s, size_t nbytes);  void pa_sink_invalidate_requested_latency(pa_sink *s); +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); +  #endif diff --git a/src/pulsecore/sndfile-util.c b/src/pulsecore/sndfile-util.c new file mode 100644 index 00000000..4f7f8bdb --- /dev/null +++ b/src/pulsecore/sndfile-util.c @@ -0,0 +1,462 @@ +/*** +  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 + +/* Shared between pacat/parec/paplay and the server */ + +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "sndfile-util.h" + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) { +    SF_INFO sfi; + +    pa_assert(sf); +    pa_assert(ss); + +    pa_zero(sfi); +    pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + +    switch (sfi.format & SF_FORMAT_SUBMASK) { + +        case SF_FORMAT_PCM_16: +        case SF_FORMAT_PCM_U8: +        case SF_FORMAT_PCM_S8: +            ss->format = PA_SAMPLE_S16NE; +            break; + +        case SF_FORMAT_PCM_24: +        case SF_FORMAT_PCM_32: +            ss->format = PA_SAMPLE_S32NE; +            break; + +        case SF_FORMAT_ULAW: +            ss->format = PA_SAMPLE_ULAW; +            break; + +        case SF_FORMAT_ALAW: +            ss->format = PA_SAMPLE_ALAW; +            break; + +        case SF_FORMAT_FLOAT: +        case SF_FORMAT_DOUBLE: +        default: +            ss->format = PA_SAMPLE_FLOAT32NE; +            break; +    } + +    ss->rate = (uint32_t) sfi.samplerate; +    ss->channels = (uint8_t) sfi.channels; + +    if (!pa_sample_spec_valid(ss)) +        return -1; + +    return 0; +} + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss) { +    pa_assert(sfi); +    pa_assert(ss); + +    sfi->samplerate = (int) ss->rate; +    sfi->channels = (int) ss->channels; + +    if (pa_sample_format_is_le(ss->format) > 0) +        sfi->format = SF_ENDIAN_LITTLE; +    else if (pa_sample_format_is_be(ss->format) > 0) +        sfi->format = SF_ENDIAN_BIG; + +    switch (ss->format) { + +        case PA_SAMPLE_U8: +            ss->format = PA_SAMPLE_S16NE; +            sfi->format = SF_FORMAT_PCM_U8; +            break; + +        case PA_SAMPLE_S16LE: +        case PA_SAMPLE_S16BE: +            ss->format = PA_SAMPLE_S16NE; +            sfi->format |= SF_FORMAT_PCM_16; +            break; + +        case PA_SAMPLE_S24LE: +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_S24_32LE: +        case PA_SAMPLE_S24_32BE: +            ss->format = PA_SAMPLE_S32NE; +            sfi->format |= SF_FORMAT_PCM_24; +            break; + +        case PA_SAMPLE_S32LE: +        case PA_SAMPLE_S32BE: +            ss->format = PA_SAMPLE_S32NE; +            sfi->format |= SF_FORMAT_PCM_32; +            break; + +        case PA_SAMPLE_ULAW: +            sfi->format = SF_FORMAT_ULAW; +            break; + +        case PA_SAMPLE_ALAW: +            sfi->format = SF_FORMAT_ALAW; +            break; + +        case PA_SAMPLE_FLOAT32LE: +        case PA_SAMPLE_FLOAT32BE: +        default: +            ss->format = PA_SAMPLE_FLOAT32NE; +            sfi->format |= SF_FORMAT_FLOAT; +            break; +    } + + +    if (!pa_sample_spec_valid(ss)) +        return -1; + +    return 0; +} + +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm) { + +    static const pa_channel_position_t table[] = { +        [SF_CHANNEL_MAP_MONO] =                  PA_CHANNEL_POSITION_MONO, +        [SF_CHANNEL_MAP_LEFT] =                  PA_CHANNEL_POSITION_FRONT_LEFT, /* libsndfile distuingishes left und front-left, which we don't */ +        [SF_CHANNEL_MAP_RIGHT] =                 PA_CHANNEL_POSITION_FRONT_RIGHT, +        [SF_CHANNEL_MAP_CENTER] =                PA_CHANNEL_POSITION_FRONT_CENTER, +        [SF_CHANNEL_MAP_FRONT_LEFT] =            PA_CHANNEL_POSITION_FRONT_LEFT, +        [SF_CHANNEL_MAP_FRONT_RIGHT] =           PA_CHANNEL_POSITION_FRONT_RIGHT, +        [SF_CHANNEL_MAP_FRONT_CENTER] =          PA_CHANNEL_POSITION_FRONT_CENTER, +        [SF_CHANNEL_MAP_REAR_CENTER] =           PA_CHANNEL_POSITION_REAR_CENTER, +        [SF_CHANNEL_MAP_REAR_LEFT] =             PA_CHANNEL_POSITION_REAR_LEFT, +        [SF_CHANNEL_MAP_REAR_RIGHT] =            PA_CHANNEL_POSITION_REAR_RIGHT, +        [SF_CHANNEL_MAP_LFE] =                   PA_CHANNEL_POSITION_LFE, +        [SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER] =  PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, +        [SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, +        [SF_CHANNEL_MAP_SIDE_LEFT] =             PA_CHANNEL_POSITION_SIDE_LEFT, +        [SF_CHANNEL_MAP_SIDE_RIGHT] =            PA_CHANNEL_POSITION_SIDE_RIGHT, +        [SF_CHANNEL_MAP_TOP_CENTER] =            PA_CHANNEL_POSITION_TOP_CENTER, +        [SF_CHANNEL_MAP_TOP_FRONT_LEFT] =        PA_CHANNEL_POSITION_TOP_FRONT_LEFT, +        [SF_CHANNEL_MAP_TOP_FRONT_RIGHT] =       PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, +        [SF_CHANNEL_MAP_TOP_FRONT_CENTER] =      PA_CHANNEL_POSITION_TOP_FRONT_CENTER, +        [SF_CHANNEL_MAP_TOP_REAR_LEFT] =         PA_CHANNEL_POSITION_TOP_REAR_LEFT, +        [SF_CHANNEL_MAP_TOP_REAR_RIGHT] =        PA_CHANNEL_POSITION_TOP_REAR_RIGHT, +        [SF_CHANNEL_MAP_TOP_REAR_CENTER] =       PA_CHANNEL_POSITION_TOP_REAR_CENTER +    }; + +    SF_INFO sfi; +    int *channels; +    unsigned c; + +    pa_assert(sf); +    pa_assert(cm); + +    pa_zero(sfi); +    pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + +    channels = pa_xnew(int, sfi.channels); +    if (!sf_command(sf, SFC_GET_CHANNEL_MAP_INFO, +                    channels, sizeof(channels[0]) * sfi.channels)) { + +        pa_xfree(channels); +        return -1; +    } + +    cm->channels = (uint8_t) sfi.channels; +    for (c = 0; c < cm->channels; c++) { +        if (channels[c] <= SF_CHANNEL_MAP_INVALID || +            (unsigned) channels[c] >= PA_ELEMENTSOF(table)) { +            pa_xfree(channels); +            return -1; +        } + +        cm->map[c] = table[channels[c]]; +    } + +    pa_xfree(channels); + +    if (!pa_channel_map_valid(cm)) +        return -1; + +    return 0; +} + +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm) { +    static const int table[PA_CHANNEL_POSITION_MAX] = { +        [PA_CHANNEL_POSITION_MONO] = SF_CHANNEL_MAP_MONO, + +        [PA_CHANNEL_POSITION_FRONT_LEFT] = SF_CHANNEL_MAP_FRONT_LEFT, +        [PA_CHANNEL_POSITION_FRONT_RIGHT] = SF_CHANNEL_MAP_FRONT_RIGHT, +        [PA_CHANNEL_POSITION_FRONT_CENTER] = SF_CHANNEL_MAP_FRONT_CENTER, + +        [PA_CHANNEL_POSITION_REAR_CENTER] = SF_CHANNEL_MAP_REAR_CENTER, +        [PA_CHANNEL_POSITION_REAR_LEFT] = SF_CHANNEL_MAP_REAR_LEFT, +        [PA_CHANNEL_POSITION_REAR_RIGHT] = SF_CHANNEL_MAP_REAR_RIGHT, + +        [PA_CHANNEL_POSITION_LFE] = SF_CHANNEL_MAP_LFE, + +        [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER, +        [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER, + +        [PA_CHANNEL_POSITION_SIDE_LEFT] = SF_CHANNEL_MAP_SIDE_LEFT, +        [PA_CHANNEL_POSITION_SIDE_RIGHT] = SF_CHANNEL_MAP_SIDE_RIGHT, + +        [PA_CHANNEL_POSITION_AUX0] = -1, +        [PA_CHANNEL_POSITION_AUX1] = -1, +        [PA_CHANNEL_POSITION_AUX2] = -1, +        [PA_CHANNEL_POSITION_AUX3] = -1, +        [PA_CHANNEL_POSITION_AUX4] = -1, +        [PA_CHANNEL_POSITION_AUX5] = -1, +        [PA_CHANNEL_POSITION_AUX6] = -1, +        [PA_CHANNEL_POSITION_AUX7] = -1, +        [PA_CHANNEL_POSITION_AUX8] = -1, +        [PA_CHANNEL_POSITION_AUX9] = -1, +        [PA_CHANNEL_POSITION_AUX10] = -1, +        [PA_CHANNEL_POSITION_AUX11] = -1, +        [PA_CHANNEL_POSITION_AUX12] = -1, +        [PA_CHANNEL_POSITION_AUX13] = -1, +        [PA_CHANNEL_POSITION_AUX14] = -1, +        [PA_CHANNEL_POSITION_AUX15] = -1, +        [PA_CHANNEL_POSITION_AUX16] = -1, +        [PA_CHANNEL_POSITION_AUX17] = -1, +        [PA_CHANNEL_POSITION_AUX18] = -1, +        [PA_CHANNEL_POSITION_AUX19] = -1, +        [PA_CHANNEL_POSITION_AUX20] = -1, +        [PA_CHANNEL_POSITION_AUX21] = -1, +        [PA_CHANNEL_POSITION_AUX22] = -1, +        [PA_CHANNEL_POSITION_AUX23] = -1, +        [PA_CHANNEL_POSITION_AUX24] = -1, +        [PA_CHANNEL_POSITION_AUX25] = -1, +        [PA_CHANNEL_POSITION_AUX26] = -1, +        [PA_CHANNEL_POSITION_AUX27] = -1, +        [PA_CHANNEL_POSITION_AUX28] = -1, +        [PA_CHANNEL_POSITION_AUX29] = -1, +        [PA_CHANNEL_POSITION_AUX30] = -1, +        [PA_CHANNEL_POSITION_AUX31] = -1, + +        [PA_CHANNEL_POSITION_TOP_CENTER] = SF_CHANNEL_MAP_TOP_CENTER, + +        [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SF_CHANNEL_MAP_TOP_FRONT_LEFT, +        [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SF_CHANNEL_MAP_TOP_FRONT_RIGHT, +        [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SF_CHANNEL_MAP_TOP_FRONT_CENTER , + +        [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SF_CHANNEL_MAP_TOP_REAR_LEFT, +        [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SF_CHANNEL_MAP_TOP_REAR_RIGHT, +        [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SF_CHANNEL_MAP_TOP_REAR_CENTER, +    }; + +    int *channels; +    unsigned c; + +    pa_assert(sf); +    pa_assert(cm); + +    /* Suppress channel mapping for the obvious cases */ +    if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_MONO) +        return 0; + +    if (cm->channels == 2 && +        cm->map[0] == PA_CHANNEL_POSITION_FRONT_LEFT && +        cm->map[1] == PA_CHANNEL_POSITION_FRONT_RIGHT) +        return 0; + +    channels = pa_xnew(int, cm->channels); +    for (c = 0; c < cm->channels; c++) { + +        if (cm->map[c] < 0 || +            cm->map[c] >= PA_CHANNEL_POSITION_MAX || +            table[cm->map[c]] < 0) { +            pa_xfree(channels); +            return -1; +        } + +        channels[c] = table[cm->map[c]]; +    } + +    if (!sf_command(sf, SFC_SET_CHANNEL_MAP_INFO, +                    channels, sizeof(channels[0]) * cm->channels)) { +        pa_xfree(channels); +        return -1; +    } + +    pa_xfree(channels); +    return 0; +} + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p) { + +    static const char* table[] = { +        [SF_STR_TITLE] = PA_PROP_MEDIA_TITLE, +        [SF_STR_COPYRIGHT] = PA_PROP_MEDIA_COPYRIGHT, +        [SF_STR_SOFTWARE] = PA_PROP_MEDIA_SOFTWARE, +        [SF_STR_ARTIST] = PA_PROP_MEDIA_ARTIST, +        [SF_STR_COMMENT] = "media.comment", +        [SF_STR_DATE] = "media.date" +    }; + +    SF_INFO sfi; +    SF_FORMAT_INFO fi; +    unsigned c; + +    pa_assert(sf); +    pa_assert(p); + +    for (c = 0; c < PA_ELEMENTSOF(table); c++) { +        const char *s; +        char *t; + +        if (!table[c]) +            continue; + +        if (!(s = sf_get_string(sf, c))) +            continue; + +        t = pa_utf8_filter(s); +        pa_proplist_sets(p, table[c], t); +        pa_xfree(t); +    } + +    pa_zero(sfi); +    pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + +    pa_zero(fi); +    fi.format = sfi.format; +    if (sf_command(sf, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name) { +        char *t; + +        t = pa_utf8_filter(fi.name); +        pa_proplist_sets(p, "media.format", t); +        pa_xfree(t); +    } +} + +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss) { +    pa_assert(ss); + +    switch (ss->format) { +        case PA_SAMPLE_S16NE: +            return (pa_sndfile_readf_t) sf_readf_short; + +        case PA_SAMPLE_S32NE: +            return (pa_sndfile_readf_t) sf_readf_int; + +        case PA_SAMPLE_FLOAT32NE: +            return (pa_sndfile_readf_t) sf_readf_float; + +        case PA_SAMPLE_ULAW: +        case PA_SAMPLE_ALAW: +            return NULL; + +        default: +            pa_assert_not_reached(); +    } +} + +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss) { +    pa_assert(ss); + +    switch (ss->format) { +        case PA_SAMPLE_S16NE: +            return (pa_sndfile_writef_t) sf_writef_short; + +        case PA_SAMPLE_S32NE: +            return (pa_sndfile_writef_t) sf_writef_int; + +        case PA_SAMPLE_FLOAT32NE: +            return (pa_sndfile_writef_t) sf_writef_float; + +        case PA_SAMPLE_ULAW: +        case PA_SAMPLE_ALAW: +            return NULL; + +        default: +            pa_assert_not_reached(); +    } +} + +int pa_sndfile_format_from_string(const char *name) { +    int i, count = 0; + + +    if (!name[0]) +        return -1; + +    pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + +    /* First try to match via full type string */ +    for (i = 0; i < count; i++) { +        SF_FORMAT_INFO fi; +        pa_zero(fi); +        fi.format = i; + +        pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + +        if (strcasecmp(name, fi.name) == 0) +            return i; +    } + +    /* Then, try to match via the full extension */ +    for (i = 0; i < count; i++) { +        SF_FORMAT_INFO fi; +        pa_zero(fi); +        fi.format = i; + +        pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + +        if (strcasecmp(name, fi.extension) == 0) +            return i; +    } + +    /* Then, try to match via the start of the type string */ +    for (i = 0; i < count; i++) { +        SF_FORMAT_INFO fi; +        pa_zero(fi); +        fi.format = i; + +        pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + +        if (strncasecmp(name, fi.extension, strlen(name)) == 0) +            return i; +    } + +    return -1; +} + +void pa_sndfile_dump_formats(void) { +    int i, count = 0; + +    pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + +    for (i = 0; i < count; i++) { +        SF_FORMAT_INFO fi; +        pa_zero(fi); +        fi.format = i; + +        pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); +        printf("%s\t%s\n", fi.extension, fi.name); +    } +} diff --git a/src/pulsecore/sndfile-util.h b/src/pulsecore/sndfile-util.h new file mode 100644 index 00000000..74021da1 --- /dev/null +++ b/src/pulsecore/sndfile-util.h @@ -0,0 +1,52 @@ +#ifndef foopulsecoresndfileutilhfoo +#define foopulsecoresndfileutilhfoo + +/*** +  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. +***/ + +#include <sndfile.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss); +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm); + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss); +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm); + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p); + +typedef sf_count_t (*pa_sndfile_readf_t)(SNDFILE *sndfile, void *ptr, sf_count_t frames); +typedef sf_count_t (*pa_sndfile_writef_t)(SNDFILE *sndfile, const void *ptr, sf_count_t frames); + +/* Returns NULL if sf_read_raw() shall be used */ +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss); + +/* Returns NULL if sf_write_raw() shall be used */ +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss); + +int pa_sndfile_format_from_string(const char *extension); + +void pa_sndfile_dump_formats(void); + +#endif diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c index dc23bff6..24535157 100644 --- a/src/pulsecore/socket-client.c +++ b/src/pulsecore/socket-client.c @@ -52,12 +52,14 @@  #include <asyncns.h>  #endif +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/xmalloc.h>  #include <pulsecore/winsock.h>  #include <pulsecore/core-error.h>  #include <pulsecore/socket-util.h> +#include <pulsecore/core-rtclock.h>  #include <pulsecore/core-util.h>  #include <pulsecore/socket-util.h>  #include <pulsecore/log.h> @@ -420,12 +422,11 @@ fail:  #endif -static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      pa_socket_client *c = userdata;      pa_assert(m);      pa_assert(e); -    pa_assert(tv);      pa_assert(c);      if (c->fd >= 0) { @@ -437,17 +438,16 @@ static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeva      do_call(c);  } -static void start_timeout(pa_socket_client *c) { +static void start_timeout(pa_socket_client *c, pa_bool_t use_rtclock) {      struct timeval tv; +      pa_assert(c);      pa_assert(!c->timeout_event); -    pa_gettimeofday(&tv); -    pa_timeval_add(&tv, CONNECT_TIMEOUT * PA_USEC_PER_SEC); -    c->timeout_event = c->mainloop->time_new(c->mainloop, &tv, timeout_cb, c); +    c->timeout_event = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + CONNECT_TIMEOUT * PA_USEC_PER_SEC, use_rtclock), timeout_cb, c);  } -pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*name, uint16_t default_port) { +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, pa_bool_t use_rtclock, const char*name, uint16_t default_port) {      pa_socket_client *c = NULL;      pa_parsed_address a; @@ -463,7 +463,7 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam      switch (a.type) {          case PA_PARSED_ADDRESS_UNIX:              if ((c = pa_socket_client_new_unix(m, a.path_or_host))) -                start_timeout(c); +                start_timeout(c, use_rtclock);              break;          case PA_PARSED_ADDRESS_TCP4:  /* Fallthrough */ @@ -499,7 +499,7 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam                  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); -                start_timeout(c); +                start_timeout(c, use_rtclock);              }  #elif defined(HAVE_GETADDRINFO)              { @@ -513,7 +513,7 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam                  if (res->ai_addr) {                      if ((c = pa_socket_client_new_sockaddr(m, res->ai_addr, res->ai_addrlen))) -                        start_timeout(c); +                        start_timeout(c, use_rtclock);                  }                  freeaddrinfo(res); @@ -546,7 +546,7 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam                  s.sin_port = htons(a.port);                  if ((c = pa_socket_client_new_sockaddr(m, (struct sockaddr*)&s, sizeof(s)))) -                    start_timeout(c); +                    start_timeout(c, use_rtclock);              }  #endif /* HAVE_LIBASYNCNS */          } diff --git a/src/pulsecore/socket-client.h b/src/pulsecore/socket-client.h index ed36400c..b896afa9 100644 --- a/src/pulsecore/socket-client.h +++ b/src/pulsecore/socket-client.h @@ -40,7 +40,7 @@ pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[  #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, const char *a, uint16_t default_port); +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, pa_bool_t use_rtclock, const char *a, uint16_t default_port);  pa_socket_client* pa_socket_client_ref(pa_socket_client *c);  void pa_socket_client_unref(pa_socket_client *c); diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c index 6a4405e3..e660700c 100644 --- a/src/pulsecore/socket-server.c +++ b/src/pulsecore/socket-server.c @@ -467,11 +467,13 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) {                  pa_snprintf(c, l, "tcp6:%s:%u", fqdn, (unsigned) ntohs(sa.sin6_port));              } else if (memcmp(&in6addr_loopback, &sa.sin6_addr, sizeof(in6addr_loopback)) == 0) { -                char hn[256]; -                if (!pa_get_host_name(hn, sizeof(hn))) +                char *id; + +                if (!(id = pa_machine_id()))                      return NULL; -                pa_snprintf(c, l, "{%s}tcp6:localhost:%u", hn, (unsigned) ntohs(sa.sin6_port)); +                pa_snprintf(c, l, "{%s}tcp6:localhost:%u", id, (unsigned) ntohs(sa.sin6_port)); +                pa_xfree(id);              } else {                  char ip[INET6_ADDRSTRLEN]; @@ -503,11 +505,13 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) {                  pa_snprintf(c, l, "tcp:%s:%u", fqdn, (unsigned) ntohs(sa.sin_port));              } else if (sa.sin_addr.s_addr == INADDR_LOOPBACK) { -                char hn[256]; -                if (!pa_get_host_name(hn, sizeof(hn))) +                char *id; + +                if (!(id = pa_machine_id()))                      return NULL; -                pa_snprintf(c, l, "{%s}tcp:localhost:%u", hn, (unsigned) ntohs(sa.sin_port)); +                pa_snprintf(c, l, "{%s}tcp:localhost:%u", id, (unsigned) ntohs(sa.sin_port)); +                pa_xfree(id);              } else {                  char ip[INET_ADDRSTRLEN]; @@ -523,15 +527,16 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) {          }          case SOCKET_SERVER_UNIX: { -            char hn[256]; +            char *id;              if (!s->filename)                  return NULL; -            if (!pa_get_host_name(hn, sizeof(hn))) +            if (!(id = pa_machine_id()))                  return NULL; -            pa_snprintf(c, l, "{%s}unix:%s", hn, s->filename); +            pa_snprintf(c, l, "{%s}unix:%s", id, s->filename); +            pa_xfree(id);              return c;          } diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index 3453637f..502e5c69 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -41,6 +41,7 @@  #include <pulsecore/thread-mq.h>  #include <pulsecore/core-util.h>  #include <pulsecore/sample-util.h> +#include <pulsecore/sndfile-util.h>  #include "sound-file-stream.h" @@ -234,10 +235,11 @@ int pa_play_file(          const pa_cvolume *volume) {      file_stream *u = NULL; -    SF_INFO sfinfo;      pa_sample_spec ss; +    pa_channel_map cm;      pa_sink_input_new_data data;      int fd; +    SF_INFO sfi;      pa_assert(sink);      pa_assert(fname); @@ -251,8 +253,6 @@ int pa_play_file(      u->readf_function = NULL;      u->memblockq = NULL; -    memset(&sfinfo, 0, sizeof(sfinfo)); -      if ((fd = open(fname, O_RDONLY  #ifdef O_NOCTTY                     |O_NOCTTY @@ -281,50 +281,36 @@ int pa_play_file(          pa_log_debug("POSIX_FADV_WILLNEED succeeded.");  #endif -    if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { +    pa_zero(sfi); +    if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) {          pa_log("Failed to open file %s", fname); -        pa_close(fd);          goto fail;      } -    switch (sfinfo.format & 0xFF) { -        case SF_FORMAT_PCM_16: -        case SF_FORMAT_PCM_U8: -        case SF_FORMAT_PCM_S8: -            ss.format = PA_SAMPLE_S16NE; -            u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short; -            break; - -        case SF_FORMAT_ULAW: -            ss.format = PA_SAMPLE_ULAW; -            break; - -        case SF_FORMAT_ALAW: -            ss.format = PA_SAMPLE_ALAW; -            break; +    fd = -1; -        case SF_FORMAT_FLOAT: -        default: -            ss.format = PA_SAMPLE_FLOAT32NE; -            u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float; -            break; +    if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) { +        pa_log("Failed to determine file sample format."); +        goto fail;      } -    ss.rate = (uint32_t) sfinfo.samplerate; -    ss.channels = (uint8_t) sfinfo.channels; - -    if (!pa_sample_spec_valid(&ss)) { -        pa_log("Unsupported sample format in file %s", fname); -        goto fail; +    if (pa_sndfile_read_channel_map(u->sndfile, &cm) < 0) { +        if (ss.channels > 2) +            pa_log_info("Failed to determine file channel map, synthesizing one."); +        pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT);      } +    u->readf_function = pa_sndfile_readf_function(&ss); +      pa_sink_input_new_data_init(&data);      data.sink = sink;      data.driver = __FILE__;      pa_sink_input_new_data_set_sample_spec(&data, &ss); +    pa_sink_input_new_data_set_channel_map(&data, &cm);      pa_sink_input_new_data_set_volume(&data, volume);      pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname));      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_data_done(&data); @@ -352,5 +338,8 @@ fail:      if (u)          file_stream_unref(u); +    if (fd >= 0) +        pa_close(fd); +      return -1;  } diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c index db75ae08..2d9b76ad 100644 --- a/src/pulsecore/sound-file.c +++ b/src/pulsecore/sound-file.c @@ -35,19 +35,21 @@  #include <pulsecore/macro.h>  #include <pulsecore/core-error.h>  #include <pulsecore/core-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sndfile-util.h>  #include "sound-file.h" -#include "core-scache.h"  int pa_sound_file_load(          pa_mempool *pool,          const char *fname,          pa_sample_spec *ss,          pa_channel_map *map, -        pa_memchunk *chunk) { +        pa_memchunk *chunk, +        pa_proplist *p) {      SNDFILE *sf = NULL; -    SF_INFO sfinfo; +    SF_INFO sfi;      int ret = -1;      size_t l;      sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL; @@ -59,7 +61,6 @@ int pa_sound_file_load(      pa_assert(chunk);      pa_memchunk_reset(chunk); -    memset(&sfinfo, 0, sizeof(sfinfo));      if ((fd = open(fname, O_RDONLY  #ifdef O_NOCTTY @@ -78,48 +79,29 @@ int pa_sound_file_load(          pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded.");  #endif -    if (!(sf = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { +    pa_zero(sfi); +    if (!(sf = sf_open_fd(fd, SFM_READ, &sfi, 1))) {          pa_log("Failed to open file %s", fname); -        pa_close(fd);          goto finish;      } -    switch (sfinfo.format & SF_FORMAT_SUBMASK) { -        case SF_FORMAT_PCM_16: -        case SF_FORMAT_PCM_U8: -        case SF_FORMAT_PCM_S8: -            ss->format = PA_SAMPLE_S16NE; -            readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_short; -            break; - -        case SF_FORMAT_ULAW: -            ss->format = PA_SAMPLE_ULAW; -            break; - -        case SF_FORMAT_ALAW: -            ss->format = PA_SAMPLE_ALAW; -            break; - -        case SF_FORMAT_FLOAT: -        case SF_FORMAT_DOUBLE: -        default: -            ss->format = PA_SAMPLE_FLOAT32NE; -            readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_float; -            break; -    } - -    ss->rate = (uint32_t) sfinfo.samplerate; -    ss->channels = (uint8_t) sfinfo.channels; +    fd = -1; -    if (!pa_sample_spec_valid(ss)) { -        pa_log("Unsupported sample format in file %s", fname); +    if (pa_sndfile_read_sample_spec(sf, ss) < 0) { +        pa_log("Failed to determine file sample format.");          goto finish;      } -    if (map) +    if ((map && pa_sndfile_read_channel_map(sf, map) < 0)) { +        if (ss->channels > 2) +            pa_log("Failed to determine file channel map, synthesizing one.");          pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_DEFAULT); +    } -    if ((l = pa_frame_size(ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { +    if (p) +        pa_sndfile_init_proplist(sf, p); + +    if ((l = pa_frame_size(ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) {          pa_log("File too large");          goto finish;      } @@ -128,9 +110,11 @@ int pa_sound_file_load(      chunk->index = 0;      chunk->length = l; +    readf_function = pa_sndfile_readf_function(ss); +      ptr = pa_memblock_acquire(chunk->memblock); -    if ((readf_function && readf_function(sf, ptr, sfinfo.frames) != sfinfo.frames) || +    if ((readf_function && readf_function(sf, ptr, sfi.frames) != sfi.frames) ||          (!readf_function && sf_read_raw(sf, ptr, (sf_count_t) l) != (sf_count_t) l)) {          pa_log("Premature file end");          goto finish; @@ -149,55 +133,35 @@ finish:      if (ret != 0 && chunk->memblock)          pa_memblock_unref(chunk->memblock); +    if (fd >= 0) +        pa_close(fd); +      return ret;  }  int pa_sound_file_too_big_to_cache(const char *fname) {      SNDFILE*sf = NULL; -    SF_INFO sfinfo; +    SF_INFO sfi;      pa_sample_spec ss;      pa_assert(fname); -    if (!(sf = sf_open(fname, SFM_READ, &sfinfo))) { +    pa_zero(sfi); +    if (!(sf = sf_open(fname, SFM_READ, &sfi))) {          pa_log("Failed to open file %s", fname);          return -1;      } -    sf_close(sf); - -    switch (sfinfo.format & SF_FORMAT_SUBMASK) { -        case SF_FORMAT_PCM_16: -        case SF_FORMAT_PCM_U8: -        case SF_FORMAT_PCM_S8: -            ss.format = PA_SAMPLE_S16NE; -            break; - -        case SF_FORMAT_ULAW: -            ss.format = PA_SAMPLE_ULAW; -            break; - -        case SF_FORMAT_ALAW: -            ss.format = PA_SAMPLE_ALAW; -            break; - -        case SF_FORMAT_DOUBLE: -        case SF_FORMAT_FLOAT: -        default: -            ss.format = PA_SAMPLE_FLOAT32NE; -            break; -    } - -    ss.rate = (uint32_t) sfinfo.samplerate; -    ss.channels = (uint8_t) sfinfo.channels; - -    if (!pa_sample_spec_valid(&ss)) { -        pa_log("Unsupported sample format in file %s", fname); +    if (pa_sndfile_read_sample_spec(sf, &ss) < 0) { +        pa_log("Failed to determine file sample format."); +        sf_close(sf);          return -1;      } -    if ((pa_frame_size(&ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { +    sf_close(sf); + +    if ((pa_frame_size(&ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) {          pa_log("File too large: %s", fname);          return 1;      } diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h index 34e02616..4dc0c7e1 100644 --- a/src/pulsecore/sound-file.h +++ b/src/pulsecore/sound-file.h @@ -26,7 +26,7 @@  #include <pulse/channelmap.h>  #include <pulsecore/memchunk.h> -int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk); +int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk, pa_proplist *p);  int pa_sound_file_too_big_to_cache(const char *fname); diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 373d5637..4ba25ae4 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -87,7 +87,8 @@ static void reset_callbacks(pa_source_output *o) {      o->attach = NULL;      o->detach = NULL;      o->suspend = NULL; -    o->moved = NULL; +    o->suspend_within_thread = NULL; +    o->moving = NULL;      o->kill = NULL;      o->get_latency = NULL;      o->state_change = NULL; @@ -434,11 +435,30 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {      if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) {          pa_log_debug("Delay queue overflow!"); -        pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE); +        pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, TRUE);      }      limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind; +    if (limit > 0 && o->source->monitor_of) { +        pa_usec_t latency; +        size_t n; + +        /* Hmm, check the latency for knowing how much of the buffered +         * data is actually still unplayed and might hence still +         * change. This is suboptimal. Ideally we'd have a call like +         * pa_sink_get_changeable_size() or so that tells us how much +         * of the queued data is actually still changeable. Hence +         * FIXME! */ + +        latency = pa_sink_get_latency_within_thread(o->source->monitor_of); + +        n = pa_usec_to_bytes(latency, &o->source->sample_spec); + +        if (n < limit) +            limit = n; +    } +      /* Implement the delay queue */      while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) {          pa_memchunk qchunk; @@ -516,26 +536,15 @@ void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes  /* i  }  /* Called from thread context */ -static pa_usec_t fixup_latency(pa_source *s, pa_usec_t usec) { -    pa_source_assert_ref(s); - -    if (usec == (pa_usec_t) -1) -        return usec; - -    if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency) -        usec = s->thread_info.max_latency; - -    if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency) -        usec = s->thread_info.min_latency; - -    return usec; -} - -/* 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); -    usec = fixup_latency(o->source, usec); +    if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) +        usec = o->source->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); @@ -546,31 +555,44 @@ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output  pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) {      pa_source_output_assert_ref(o); -    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) +    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); -    else -        /* If this source output is not realized yet, we have to touch -         * the thread info data directly */ +        return usec; +    } -        o->thread_info.requested_source_latency = usec; +    /* If this source output is not realized yet or is being moved, we +     * have to touch the thread info data directly */ + +    if (o->source) { +        if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) +            usec = o->source->fixed_latency; + +        if (usec != (pa_usec_t) -1) { +            pa_usec_t min_latency, max_latency; +            pa_source_get_latency_range(o->source, &min_latency, &max_latency); +            usec = PA_CLAMP(usec, min_latency, max_latency); +        } +    } + +    o->thread_info.requested_source_latency = usec;      return usec;  }  /* Called from main context */  pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) { -    pa_usec_t usec = 0; -      pa_source_output_assert_ref(o); -    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) +    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { +        pa_usec_t usec = 0;          pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); -    else -        /* If this source output is not realized yet, we have to touch -         * the thread info data directly */ -        usec = o->thread_info.requested_source_latency; +        return usec; +    } -    return usec; +    /* If this source output is not realized yet or is being moved, we +     * have to touch the thread info data directly */ + +    return o->thread_info.requested_source_latency;  }  /* Called from main context */ @@ -707,6 +729,8 @@ int pa_source_output_start_move(pa_source_output *o) {      pa_source_update_status(o->source);      o->source = NULL; +    pa_source_output_unref(o); +      return 0;  } @@ -749,9 +773,12 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t      } else          new_resampler = NULL; +    if (o->moving) +        o->moving(o, dest); +      o->source = dest;      o->save_source = save; -    pa_idxset_put(o->source->outputs, o, NULL); +    pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL);      if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)          o->source->n_corked++; @@ -776,14 +803,12 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t      }      pa_source_update_status(dest); +      pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);      pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name);      /* Notify everyone */ -    if (o->moved) -        o->moved(o); -      pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], o);      pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); @@ -805,11 +830,19 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t sav      if (!pa_source_output_may_move_to(o, dest))          return -PA_ERR_NOTSUPPORTED; -    if ((r = pa_source_output_start_move(o)) < 0) +    pa_source_output_ref(o); + +    if ((r = pa_source_output_start_move(o)) < 0) { +        pa_source_output_unref(o);          return r; +    } -    if ((r = pa_source_output_finish_move(o, dest, save)) < 0) +    if ((r = pa_source_output_finish_move(o, dest, save)) < 0) { +        pa_source_output_unref(o);          return r; +    } + +    pa_source_output_unref(o);      return 0;  } @@ -836,12 +869,9 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int          case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {              pa_usec_t *r = userdata; -            pa_usec_t source_usec = 0;              r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec); - -            if (o->source->parent.process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_usec, 0, NULL) >= 0) -                r[1] += source_usec; +            r[1] += pa_source_get_latency_within_thread(o->source);              return 0;          } diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index 018ec886..9824e160 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -116,13 +116,19 @@ struct pa_source_output {       * disconnected from its source. Called from IO thread context */      void (*detach) (pa_source_output *o);           /* may be NULL */ -    /* If non-NULL called whenever the the source this output is attached +    /* If non-NULL called whenever the source this output is attached       * to suspends or resumes. Called from main context */      void (*suspend) (pa_source_output *o, pa_bool_t b);   /* may be NULL */ -    /* If non-NULL called whenever the the source this output is attached -     * to changes. Called from main context */ -    void (*moved) (pa_source_output *o);   /* may be NULL */ +    /* If non-NULL called whenever the source this output is attached +     * to suspends or resumes. Called from IO context */ +    void (*suspend_within_thread) (pa_source_output *o, pa_bool_t b);   /* may be NULL */ + +    /* 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. */ +    void (*moving) (pa_source_output *o, pa_source *dest);   /* may be NULL */      /* Supposed to unlink and destroy this stream. Called from main       * context. */ diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index cc6dfc40..74f38bc5 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -41,7 +41,9 @@  #include "source.h" -#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) +#define ABSOLUTE_MIN_LATENCY (500) +#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); @@ -91,11 +93,29 @@ void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute) {      data->muted = !!mute;  } +void pa_source_new_data_set_port(pa_source_new_data *data, const char *port) { +    pa_assert(data); + +    pa_xfree(data->active_port); +    data->active_port = pa_xstrdup(port); +} +  void pa_source_new_data_done(pa_source_new_data *data) {      pa_assert(data); -    pa_xfree(data->name);      pa_proplist_free(data->proplist); + +    if (data->ports) { +        pa_device_port *p; + +        while ((p = pa_hashmap_steal_first(data->ports))) +            pa_device_port_free(p); + +        pa_hashmap_free(data->ports, NULL, NULL); +    } + +    pa_xfree(data->name); +    pa_xfree(data->active_port);  }  /* Called from main context */ @@ -108,6 +128,7 @@ static void reset_callbacks(pa_source *s) {      s->get_mute = NULL;      s->set_mute = NULL;      s->update_requested_latency = NULL; +    s->set_port = NULL;  }  /* Called from main context */ @@ -128,6 +149,7 @@ pa_source* pa_source_new(      s = pa_msgobject_new(pa_source);      if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) { +        pa_log_debug("Failed to register name %s.", data->name);          pa_xfree(s);          return NULL;      } @@ -140,6 +162,8 @@ pa_source* pa_source_new(          return NULL;      } +    /* FIXME, need to free s here on failure */ +      pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));      pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); @@ -165,6 +189,7 @@ pa_source* pa_source_new(      pa_device_init_description(data->proplist);      pa_device_init_icon(data->proplist, FALSE); +    pa_device_init_intended_roles(data->proplist);      if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) {          pa_xfree(s); @@ -178,6 +203,7 @@ pa_source* pa_source_new(      s->core = core;      s->state = PA_SOURCE_INIT;      s->flags = flags; +    s->suspend_cause = 0;      s->name = pa_xstrdup(name);      s->proplist = pa_proplist_copy(data->proplist);      s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); @@ -198,12 +224,38 @@ pa_source* pa_source_new(      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 */ +    s->ports = data->ports; +    data->ports = NULL; + +    s->active_port = NULL; +    s->save_port = FALSE; + +    if (data->active_port && s->ports) +        if ((s->active_port = pa_hashmap_get(s->ports, data->active_port))) +            s->save_port = data->save_port; + +    if (!s->active_port && s->ports) { +        void *state; +        pa_device_port *p; + +        PA_HASHMAP_FOREACH(p, s->ports, state) +            if (!s->active_port || p->priority > s->active_port->priority) +                s->active_port = p; +    } + +    s->save_volume = data->save_volume; +    s->save_muted = data->save_muted; +      pa_silence_memchunk_get(              &core->silence_cache,              core->mempool, @@ -218,8 +270,8 @@ pa_source* pa_source_new(      s->thread_info.max_rewind = 0;      s->thread_info.requested_latency_valid = FALSE;      s->thread_info.requested_latency = 0; -    s->thread_info.min_latency = DEFAULT_MIN_LATENCY; -    s->thread_info.max_latency = 0; +    s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; +    s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;      pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0); @@ -302,18 +354,21 @@ void pa_source_put(pa_source *s) {      /* 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 || -              s->thread_info.min_latency <= s->thread_info.max_latency); +    pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); + +    /* Generally, flags should be initialized via pa_source_new(). As +     * a special exception we allow volume related flags to be set +     * between _new() and _put(). */ -    if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) { +    if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL))          s->flags |= PA_SOURCE_DECIBEL_VOLUME; -        s->thread_info.soft_volume = s->soft_volume; -        s->thread_info.soft_muted = s->muted; -    } +    s->thread_info.soft_volume = s->soft_volume; +    s->thread_info.soft_muted = s->muted; -    if (s->flags & PA_SOURCE_DECIBEL_VOLUME) -        s->n_volume_steps = PA_VOLUME_NORM+1; +    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_se(source_set_state(s, PA_SOURCE_IDLE) == 0); @@ -391,6 +446,15 @@ static void source_free(pa_object *o) {      if (s->proplist)          pa_proplist_free(s->proplist); +    if (s->ports) { +        pa_device_port *p; + +        while ((p = pa_hashmap_steal_first(s->ports))) +            pa_device_port_free(p); + +        pa_hashmap_free(s->ports, NULL, NULL); +    } +      pa_xfree(s);  } @@ -420,14 +484,25 @@ int pa_source_update_status(pa_source*s) {  }  /* Called from main context */ -int pa_source_suspend(pa_source *s, pa_bool_t suspend) { +int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause) {      pa_source_assert_ref(s);      pa_assert(PA_SOURCE_IS_LINKED(s->state)); +    pa_assert(cause != 0);      if (s->monitor_of)          return -PA_ERR_NOTSUPPORTED;      if (suspend) +        s->suspend_cause |= cause; +    else +        s->suspend_cause &= ~cause; + +    if ((pa_source_get_state(s) == PA_SOURCE_SUSPENDED) == !!s->suspend_cause) +        return 0; + +    pa_log_debug("Suspend cause of source %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming"); + +    if (suspend)          return source_set_state(s, PA_SOURCE_SUSPENDED);      else          return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE); @@ -452,21 +527,25 @@ int pa_source_sync_suspend(pa_source *s) {  }  /* Called from main context */ -pa_queue *pa_source_move_all_start(pa_source *s) { -    pa_queue *q; +pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q) {      pa_source_output *o, *n;      uint32_t idx;      pa_source_assert_ref(s);      pa_assert(PA_SOURCE_IS_LINKED(s->state)); -    q = pa_queue_new(); +    if (!q) +        q = pa_queue_new();      for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) {          n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx)); +        pa_source_output_ref(o); +          if (pa_source_output_start_move(o) >= 0) -            pa_queue_push(q, pa_source_output_ref(o)); +            pa_queue_push(q, o); +        else +            pa_source_output_unref(o);      }      return q; @@ -616,8 +695,34 @@ pa_usec_t pa_source_get_latency(pa_source *s) {      return usec;  } +/* Called from IO thread */ +pa_usec_t pa_source_get_latency_within_thread(pa_source *s) { +    pa_usec_t usec = 0; +    pa_msgobject *o; + +    pa_source_assert_ref(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! */ + +    if (s->thread_info.state == PA_SOURCE_SUSPENDED) +        return 0; + +    if (!(s->flags & PA_SOURCE_LATENCY)) +        return 0; + +    o = PA_MSGOBJECT(s); + +    /* We probably should make this a proper vtable callback instead of going through process_msg() */ + +    if (o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) +        return -1; + +    return usec; +} +  /* Called from main thread */ -void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) { +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; @@ -630,6 +735,7 @@ void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) {      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;      if (s->set_volume) {          pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); @@ -675,7 +781,24 @@ const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) {  }  /* Called from main thread */ -void pa_source_set_mute(pa_source *s, pa_bool_t mute) { +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume, pa_bool_t save) { +    pa_source_assert_ref(s); + +    /* 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; +        return; +    } + +    s->virtual_volume = *new_volume; +    s->save_volume = save; + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main thread */ +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); @@ -683,6 +806,7 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute) {      old_muted = s->muted;      s->muted = mute; +    s->save_muted = (old_muted == s->muted && s->save_muted) || save;      if (s->set_mute)          s->set_mute(s); @@ -695,7 +819,6 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute) {  /* 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(PA_SOURCE_IS_LINKED(s->state)); @@ -707,19 +830,40 @@ 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) +        if (old_muted != s->muted) {              pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + +            /* Make sure the soft mute status stays in sync */ +            pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); +        }      }      return s->muted;  }  /* Called from main thread */ +void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted, pa_bool_t save) { +    pa_source_assert_ref(s); + +    /* 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; +        return; +    } + +    s->muted = new_muted; +    s->save_muted = save; + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* 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(p); -    pa_proplist_update(s->proplist, mode, p); +    if (p) +        pa_proplist_update(s->proplist, mode, p);      if (PA_SOURCE_IS_LINKED(s->state)) {          pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s); @@ -787,7 +931,7 @@ unsigned pa_source_check_suspend(pa_source *s) {      ret = 0; -    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) {          pa_source_output_state_t st;          st = pa_source_output_get_state(o); @@ -881,9 +1025,26 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_          case PA_SOURCE_MESSAGE_GET_MUTE:              return 0; -        case PA_SOURCE_MESSAGE_SET_STATE: +        case PA_SOURCE_MESSAGE_SET_STATE: { + +            pa_bool_t suspend_change = +                (s->thread_info.state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(PA_PTR_TO_UINT(userdata))) || +                (PA_SOURCE_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SOURCE_SUSPENDED); +              s->thread_info.state = PA_PTR_TO_UINT(userdata); + +            if (suspend_change) { +                pa_source_output *o; +                void *state = NULL; + +                while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) +                    if (o->suspend_within_thread) +                        o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED); +            } + +              return 0; +        }          case PA_SOURCE_MESSAGE_DETACH: @@ -911,7 +1072,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_          case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: {              pa_usec_t *r = userdata; -            pa_source_update_latency_range(s, r[0], r[1]); +            pa_source_set_latency_range_within_thread(s, r[0], r[1]);              return 0;          } @@ -930,6 +1091,11 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_              *((size_t*) userdata) = s->thread_info.max_rewind;              return 0; +        case PA_SOURCE_MESSAGE_SET_MAX_REWIND: + +            pa_source_set_max_rewind_within_thread(s, (size_t) offset); +            return 0; +          case PA_SOURCE_MESSAGE_GET_LATENCY:              if (s->monitor_of) { @@ -948,12 +1114,13 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_  }  /* Called from main thread */ -int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) { +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) {      uint32_t idx;      pa_source *source;      int ret = 0;      pa_core_assert_ref(c); +    pa_assert(cause != 0);      for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) {          int r; @@ -961,7 +1128,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) {          if (source->monitor_of)              continue; -        if ((r = pa_source_suspend(source, suspend)) < 0) +        if ((r = pa_source_suspend(source, suspend, cause)) < 0)              ret = r;      } @@ -1018,6 +1185,9 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {      pa_source_assert_ref(s); +    if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) +        return PA_CLAMP(s->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; @@ -1027,23 +1197,21 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {              (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))              result = o->thread_info.requested_source_latency; -    if (result != (pa_usec_t) -1) { -        if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) -            result = s->thread_info.max_latency; +    if (result != (pa_usec_t) -1) +        result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); -        if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) -            result = s->thread_info.min_latency; +    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { +        /* Only cache this if we are fully set up */ +        s->thread_info.requested_latency = result; +        s->thread_info.requested_latency_valid = TRUE;      } -    s->thread_info.requested_latency = result; -    s->thread_info.requested_latency_valid = TRUE; -      return result;  }  /* Called from main thread */  pa_usec_t pa_source_get_requested_latency(pa_source *s) { -    pa_usec_t usec; +    pa_usec_t usec = 0;      pa_source_assert_ref(s);      pa_assert(PA_SOURCE_IS_LINKED(s->state)); @@ -1057,7 +1225,7 @@ pa_usec_t pa_source_get_requested_latency(pa_source *s) {  }  /* Called from IO thread */ -void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { +void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind) {      pa_source_output *o;      void *state = NULL; @@ -1074,42 +1242,64 @@ void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) {      }  } +/* Called from main thread */ +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { +    pa_source_assert_ref(s); + +    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); +    else +        pa_source_set_max_rewind_within_thread(s, max_rewind); +} + +/* Called from IO thread */  void pa_source_invalidate_requested_latency(pa_source *s) {      pa_source_output *o;      void *state = NULL;      pa_source_assert_ref(s); +    if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) +        return; +      s->thread_info.requested_latency_valid = FALSE; -    if (s->update_requested_latency) -        s->update_requested_latency(s); +    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { -    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) -        if (o->update_source_requested_latency) -            o->update_source_requested_latency(o); +        if (s->update_requested_latency) +            s->update_requested_latency(s); + +        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) +            if (o->update_source_requested_latency) +                o->update_source_requested_latency(o); +    }      if (s->monitor_of)          pa_sink_invalidate_requested_latency(s->monitor_of);  } +/* 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);      /* min_latency == 0:           no limit -     * min_latency == (size_t) -1: default limit       * min_latency anything else:  specified limit       *       * Similar for max_latency */ -    if (min_latency == (pa_usec_t) -1) -        min_latency = DEFAULT_MIN_LATENCY; +    if (min_latency < ABSOLUTE_MIN_LATENCY) +        min_latency = ABSOLUTE_MIN_LATENCY; + +    if (max_latency <= 0 || +        max_latency > ABSOLUTE_MAX_LATENCY) +        max_latency = ABSOLUTE_MAX_LATENCY; -    if (max_latency == (pa_usec_t) -1) -        max_latency = min_latency; +    pa_assert(min_latency <= max_latency); -    pa_assert(!min_latency || !max_latency || -              min_latency <= max_latency); +    /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */ +    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && +               max_latency == ABSOLUTE_MAX_LATENCY) || +              (s->flags & PA_SOURCE_DYNAMIC_LATENCY));      if (PA_SOURCE_IS_LINKED(s->state)) {          pa_usec_t r[2]; @@ -1118,14 +1308,11 @@ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t          r[1] = max_latency;          pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); -    } else { -        s->thread_info.min_latency = min_latency; -        s->thread_info.max_latency = max_latency; - -        s->thread_info.requested_latency_valid = FALSE; -    } +    } else +        pa_source_set_latency_range_within_thread(s, min_latency, max_latency);  } +/* 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(min_latency); @@ -1144,26 +1331,52 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t     }  } -/* Called from IO thread */ -void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { -    pa_source_output *o; +/* 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_assert(!min_latency || !max_latency || -              min_latency <= max_latency); +    pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); +    pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); +    pa_assert(min_latency <= max_latency); + +    /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */ +    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && +               max_latency == ABSOLUTE_MAX_LATENCY) || +              (s->flags & PA_SOURCE_DYNAMIC_LATENCY) || +              s->monitor_of);      s->thread_info.min_latency = min_latency;      s->thread_info.max_latency = max_latency; -    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) -        if (o->update_source_latency_range) -            o->update_source_latency_range(o); +    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { +        pa_source_output *o; + +        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) +            if (o->update_source_latency_range) +                o->update_source_latency_range(o); +    }      pa_source_invalidate_requested_latency(s);  } +/* 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(pa_source_get_state(s) == PA_SOURCE_INIT); + +    if (latency < ABSOLUTE_MIN_LATENCY) +        latency = ABSOLUTE_MIN_LATENCY; + +    if (latency > ABSOLUTE_MAX_LATENCY) +        latency = ABSOLUTE_MAX_LATENCY; + +    s->fixed_latency = latency; +} + +/* Called from main thread */  size_t pa_source_get_max_rewind(pa_source *s) {      size_t r;      pa_source_assert_ref(s); @@ -1175,3 +1388,38 @@ size_t pa_source_get_max_rewind(pa_source *s) {      return r;  } + +/* Called from main context */ +int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) { +    pa_device_port *port; + +    pa_assert(s); + +    if (!s->set_port) { +        pa_log_debug("set_port() operation not implemented for sink %u \"%s\"", s->index, s->name); +        return -PA_ERR_NOTIMPLEMENTED; +    } + +    if (!s->ports) +        return -PA_ERR_NOENTITY; + +    if (!(port = pa_hashmap_get(s->ports, name))) +        return -PA_ERR_NOENTITY; + +    if (s->active_port == port) { +        s->save_port = s->save_port || save; +        return 0; +    } + +    if ((s->set_port(s, port)) < 0) +        return -PA_ERR_NOENTITY; + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + +    pa_log_info("Changed port of source %u \"%s\" to %s", s->index, s->name, port->name); + +    s->active_port = port; +    s->save_port = save; + +    return 0; +} diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 26471de0..7e9fd8b7 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -56,8 +56,10 @@ struct pa_source {      uint32_t index;      pa_core *core; +      pa_source_state_t state;      pa_source_flags_t flags; +    pa_suspend_cause_t suspend_cause;      char *name;      char *driver;                             /* may be NULL */ @@ -82,11 +84,20 @@ struct pa_source {      pa_bool_t refresh_volume:1;      pa_bool_t refresh_muted:1; +    pa_bool_t save_port:1; +    pa_bool_t save_volume:1; +    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; +      /* Called when the main loop requests a state change. Called from       * main loop context. If returns -1 the state change will be       * inhibited */ @@ -118,6 +129,10 @@ struct pa_source {       * thread context. */      void (*update_requested_latency)(pa_source *s); /* dito */ +    /* Called whenever the port shall be changed. Called from main +     * thread. */ +    int (*set_port)(pa_source *s, pa_device_port *port); /*dito */ +      /* Contains copies of the above data so that the real-time worker       * thread can work without access locking */      struct { @@ -159,6 +174,7 @@ typedef enum pa_source_message {      PA_SOURCE_MESSAGE_SET_LATENCY_RANGE,      PA_SOURCE_MESSAGE_GET_LATENCY_RANGE,      PA_SOURCE_MESSAGE_GET_MAX_REWIND, +    PA_SOURCE_MESSAGE_SET_MAX_REWIND,      PA_SOURCE_MESSAGE_MAX  } pa_source_message_t; @@ -170,6 +186,9 @@ typedef struct pa_source_new_data {      pa_module *module;      pa_card *card; +    pa_hashmap *ports; +    char *active_port; +      pa_sample_spec sample_spec;      pa_channel_map channel_map;      pa_cvolume volume; @@ -181,6 +200,10 @@ typedef struct pa_source_new_data {      pa_bool_t channel_map_is_set:1;      pa_bool_t namereg_fail:1; + +    pa_bool_t save_port:1; +    pa_bool_t save_volume:1; +    pa_bool_t save_muted:1;  } pa_source_new_data;  pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data); @@ -189,6 +212,7 @@ void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sampl  void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map);  void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume);  void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute); +void pa_source_new_data_set_port(pa_source_new_data *data, const char *port);  void pa_source_new_data_done(pa_source_new_data *data);  /*** To be called exclusively by the source driver, from main context */ @@ -205,12 +229,16 @@ void pa_source_set_description(pa_source *s, const char *description);  void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q);  void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p); +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind);  void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency);  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);  int pa_source_sync_suspend(pa_source *s); @@ -224,23 +252,25 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t  size_t pa_source_get_max_rewind(pa_source *s);  int pa_source_update_status(pa_source*s); -int pa_source_suspend(pa_source *s, pa_bool_t suspend); -int pa_source_suspend_all(pa_core *c, pa_bool_t suspend); +int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause); +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); -void pa_source_set_volume(pa_source *source, const pa_cvolume *volume); +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); +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);  pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p); +int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save); +  unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */  unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */  unsigned pa_source_check_suspend(pa_source *s); /* Returns how many streams are active that don't allow suspensions */  #define pa_source_get_state(s) ((pa_source_state_t) (s)->state)  /* Moves all inputs away, and stores them in pa_queue */ -pa_queue *pa_source_move_all_start(pa_source *s); +pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q);  void pa_source_move_all_finish(pa_source *s, pa_queue *q, pa_bool_t save);  void pa_source_move_all_fail(pa_queue *q); @@ -257,11 +287,12 @@ 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(pa_source *s, size_t max_rewind); -void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +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);  /*** To be called exclusively by source output drivers, from IO context */  void pa_source_invalidate_requested_latency(pa_source *s); +pa_usec_t pa_source_get_latency_within_thread(pa_source *s);  #endif diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c index 9f5a84b4..4fc82ded 100644 --- a/src/pulsecore/strbuf.c +++ b/src/pulsecore/strbuf.c @@ -113,6 +113,13 @@ void pa_strbuf_puts(pa_strbuf *sb, const char *t) {      pa_strbuf_putsn(sb, t, strlen(t));  } +/* Append a character to the string buffer */ +void pa_strbuf_putc(pa_strbuf *sb, char c) { +    pa_assert(sb); + +    pa_strbuf_putsn(sb, &c, 1); +} +  /* Append a new chunk to the linked list */  static void append(pa_strbuf *sb, struct chunk *c) {      pa_assert(sb); diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h index 05e69e03..d71ecb98 100644 --- a/src/pulsecore/strbuf.h +++ b/src/pulsecore/strbuf.h @@ -35,6 +35,7 @@ char *pa_strbuf_tostring_free(pa_strbuf *sb);  size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...)  PA_GCC_PRINTF_ATTR(2,3);  void pa_strbuf_puts(pa_strbuf *sb, const char *t);  void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m); +void pa_strbuf_putc(pa_strbuf *sb, char c);  pa_bool_t pa_strbuf_isempty(pa_strbuf *sb); diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c index cbafbba6..0f4ca867 100644 --- a/src/pulsecore/strlist.c +++ b/src/pulsecore/strlist.c @@ -159,3 +159,15 @@ pa_strlist *pa_strlist_reverse(pa_strlist *l) {      return r;  } + +pa_strlist *pa_strlist_next(pa_strlist *s) { +    pa_assert(s); + +    return s->next; +} + +const char *pa_strlist_data(pa_strlist *s) { +    pa_assert(s); + +    return ITEM_TO_TEXT(s); +} diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h index 2584e86c..e57203c5 100644 --- a/src/pulsecore/strlist.h +++ b/src/pulsecore/strlist.h @@ -47,4 +47,10 @@ pa_strlist* pa_strlist_parse(const char *s);  /* Reverse string list */  pa_strlist *pa_strlist_reverse(pa_strlist *l); +/* Return the next item in the list */ +pa_strlist *pa_strlist_next(pa_strlist *s); + +/* Return the string associated to the current item */ +const char *pa_strlist_data(pa_strlist *s); +  #endif diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c index 65621948..9d5a0705 100644 --- a/src/pulsecore/time-smoother.c +++ b/src/pulsecore/time-smoother.c @@ -78,17 +78,26 @@ struct pa_smoother {      /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */      double a, b, c; -    pa_bool_t abc_valid; +    pa_bool_t abc_valid:1;      pa_bool_t monotonic:1;      pa_bool_t paused:1; +    pa_bool_t smoothing:1; /* If FALSE we skip the polonyomial interpolation step */      pa_usec_t pause_time;      unsigned min_history;  }; -pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic, unsigned min_history) { +pa_smoother* pa_smoother_new( +        pa_usec_t adjust_time, +        pa_usec_t history_time, +        pa_bool_t monotonic, +        pa_bool_t smoothing, +        unsigned min_history, +        pa_usec_t time_offset, +        pa_bool_t paused) { +      pa_smoother *s;      pa_assert(adjust_time > 0); @@ -116,9 +125,13 @@ pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_b      s->abc_valid = FALSE;      s->paused = FALSE; +    s->smoothing = smoothing;      s->min_history = min_history; +    s->paused = paused; +    s->time_offset = s->pause_time = time_offset; +      return s;  } @@ -279,6 +292,7 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {      pa_assert(y);      if (x >= s->px) { +        /* Linear interpolation right from px */          int64_t t;          /* The requested point is right of the point where we wanted @@ -294,7 +308,22 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {          if (deriv)              *deriv = s->dp; +    } else if (x <= s->ex) { +        /* Linear interpolation left from ex */ +        int64_t t; + +        t = (int64_t) s->ey - (int64_t) llrint(s->de * (double) (s->ex - x)); + +        if (t < 0) +            t = 0; + +        *y = (pa_usec_t) t; + +        if (deriv) +            *deriv = s->de; +      } else { +        /* Spline interpolation between ex and px */          double tx, ty;          /* Ok, we're not yet on track, thus let's interpolate, and @@ -348,7 +377,6 @@ void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) {           * we can adjust our position smoothly from this one */          estimate(s, x, &ney, &nde);          s->ex = x; s->ey = ney; s->de = nde; -          s->ry = y;      } @@ -359,12 +387,19 @@ void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) {      s->dp = avg_gradient(s, x);      /* And calculate when we want to be on track again */ -    s->px = s->ex + s->adjust_time; -    s->py = s->ry + (pa_usec_t) llrint(s->dp * (double) s->adjust_time); +    if (s->smoothing) { +        s->px = s->ex + s->adjust_time; +        s->py = s->ry + (pa_usec_t) llrint(s->dp * (double) s->adjust_time); +    } else { +        s->px = s->ex; +        s->py = s->ry; +    }      s->abc_valid = FALSE; -/*     pa_log_debug("put(%llu | %llu) = %llu", (unsigned long long)  (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +#ifdef DEBUG_DATA +    pa_log_debug("%p, put(%llu | %llu) = %llu", s, (unsigned long long)  (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif  }  pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { @@ -395,7 +430,9 @@ pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) {              s->last_y = y;      } -/*     pa_log_debug("get(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +#ifdef DEBUG_DATA +    pa_log_debug("%p, get(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif      return y;  } @@ -405,7 +442,9 @@ void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) {      s->time_offset = offset; -/*     pa_log_debug("offset(%llu)", (unsigned long long) offset); */ +#ifdef DEBUG_DATA +    pa_log_debug("offset(%llu)", (unsigned long long) offset); +#endif  }  void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { @@ -414,13 +453,15 @@ void pa_smoother_pause(pa_smoother *s, pa_usec_t x) {      if (s->paused)          return; -/*     pa_log_debug("pause(%llu)", (unsigned long long)  x); */ +#ifdef DEBUG_DATA +    pa_log_debug("pause(%llu)", (unsigned long long)  x); +#endif      s->paused = TRUE;      s->pause_time = x;  } -void pa_smoother_resume(pa_smoother *s, pa_usec_t x) { +void pa_smoother_resume(pa_smoother *s, pa_usec_t x, pa_bool_t fix_now) {      pa_assert(s);      if (!s->paused) @@ -429,10 +470,22 @@ void pa_smoother_resume(pa_smoother *s, pa_usec_t x) {      if (x < s->pause_time)          x = s->pause_time; -/*     pa_log_debug("resume(%llu)", (unsigned long long) x); */ +#ifdef DEBUG_DATA +    pa_log_debug("resume(%llu)", (unsigned long long) x); +#endif      s->paused = FALSE;      s->time_offset += x - s->pause_time; + +    if (fix_now) +        pa_smoother_fix_now(s); +} + +void pa_smoother_fix_now(pa_smoother *s) { +    pa_assert(s); + +    s->px = s->ex; +    s->py = s->ry;  }  pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) { @@ -454,7 +507,9 @@ pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay)      if (s->dp > nde)          nde = s->dp; -/*     pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); */ +#ifdef DEBUG_DATA +    pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); +#endif      return (pa_usec_t) llrint((double) y_delay / nde);  } diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h index 2051e640..5244a7e7 100644 --- a/src/pulsecore/time-smoother.h +++ b/src/pulsecore/time-smoother.h @@ -27,7 +27,15 @@  typedef struct pa_smoother pa_smoother; -pa_smoother* pa_smoother_new(pa_usec_t x_adjust_time, pa_usec_t x_history_time, pa_bool_t monotonic, unsigned min_history); +pa_smoother* pa_smoother_new( +        pa_usec_t x_adjust_time, +        pa_usec_t x_history_time, +        pa_bool_t monotonic, +        pa_bool_t smoothing, +        unsigned min_history, +        pa_usec_t x_offset, +        pa_bool_t paused); +  void pa_smoother_free(pa_smoother* s);  /* Adds a new value to our dataset. x = local/system time, y = remote time */ @@ -42,8 +50,10 @@ pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay);  void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t x_offset);  void pa_smoother_pause(pa_smoother *s, pa_usec_t x); -void pa_smoother_resume(pa_smoother *s, pa_usec_t x); +void pa_smoother_resume(pa_smoother *s, pa_usec_t x, pa_bool_t abrupt);  void pa_smoother_reset(pa_smoother *s); +void pa_smoother_fix_now(pa_smoother *s); +  #endif diff --git a/src/t.csv b/src/t.csv new file mode 100644 index 00000000..92c99387 --- /dev/null +++ b/src/t.csv @@ -0,0 +1,2551 @@ +4	4967	0	4967	0	4967	1	0 +5	5972	0	1005	0	5972	0	0 +6	7448	0	1476	0	7448	0	0 +7	8452	0	1004	0	8452	0	0 +8	10226	0	1774	0	10226	0	0 +9	11230	0	1004	0	11230	0	0 +10	12234	0	1004	0	12234	0	0 +11	13237	0	1003	0	13237	0	0 +12	14243	0	1006	0	14243	1	0 +13	15247	0	1004	0	15247	0	0 +14	16251	0	1004	0	16251	0	0 +15	17256	0	1005	0	17256	0	0 +16	18261	0	1005	0	18261	0	0 +17	19265	0	1004	0	19265	0	0 +18	20269	0	1004	0	20269	0	0 +19	21272	0	1003	0	21272	0	0 +20	22277	0	1005	0	22277	0	0 +21	23280	0	1003	0	23280	0	0 +22	24284	0	1004	0	24284	0	0 +23	28848	0	4564	0	28848	0	0 +24	29853	0	1005	0	29853	0	0 +25	30860	0	1007	0	30860	0	0 +26	31876	0	1016	0	31876	0	0 +27	32880	0	1004	0	32880	0	0 +28	33885	0	1005	0	33885	0	0 +29	34890	0	1005	0	34890	1	0 +30	35894	0	1004	0	35894	0	0 +31	36899	0	1005	0	36899	0	0 +32	37904	0	1005	0	37904	0	0 +33	38908	0	1004	0	38908	0	0 +34	39913	0	1005	0	39913	0	0 +35	40917	0	1004	0	40917	0	0 +36	41920	0	1003	0	41920	0	0 +37	42924	0	1004	0	42924	0	0 +38	43928	0	1004	0	43928	0	0 +39	44932	0	1004	0	44932	0	0 +40	45937	0	1005	0	45937	0	0 +41	46941	0	1004	0	46941	0	0 +42	47945	0	1004	0	47945	0	0 +43	48949	0	1004	0	48949	0	0 +44	49953	0	1004	0	49953	0	0 +45	50956	0	1003	0	50956	0	0 +46	51960	0	1004	0	51960	0	0 +47	52963	0	1003	0	52963	0	0 +48	53968	0	1005	0	53968	0	0 +49	54972	0	1004	0	54972	0	0 +50	55977	0	1005	0	55977	0	0 +51	56981	0	1004	0	56981	0	0 +52	57986	0	1005	0	57986	0	0 +53	58990	0	1004	0	58990	0	0 +54	59994	0	1004	0	59994	0	0 +55	60999	0	1005	0	60999	0	0 +56	62003	0	1004	0	62003	0	0 +57	63008	0	1005	0	63008	0	0 +58	64012	0	1004	0	64012	0	0 +59	65017	0	1005	0	65017	0	0 +60	66022	0	1005	0	66022	0	0 +61	67027	0	1005	0	67027	0	0 +62	68032	0	1005	0	68032	0	0 +63	69037	0	1005	0	69037	0	0 +64	70042	0	1005	0	70042	0	0 +65	71046	0	1004	0	71046	0	0 +66	72050	0	1004	0	72050	0	0 +67	73054	0	1004	0	73054	0	0 +68	74059	0	1005	0	74059	0	0 +69	75064	0	1005	0	75064	0	0 +70	76068	0	1004	0	76068	0	0 +71	77071	0	1003	0	77071	0	0 +72	78075	0	1004	0	78075	0	0 +73	79079	0	1004	0	79079	0	0 +74	80083	0	1004	0	80083	0	0 +75	81087	0	1004	0	81087	0	0 +76	82092	0	1005	0	82092	0	0 +77	83095	0	1003	0	83095	0	0 +78	84100	0	1005	0	84100	0	0 +79	85105	0	1005	0	85105	0	0 +80	86109	0	1004	0	86109	0	0 +81	87113	0	1004	0	87113	0	0 +82	88119	0	1006	0	88119	0	0 +83	89124	0	1005	0	89124	0	0 +84	90129	0	1005	0	90129	0	0 +85	91132	0	1003	0	91132	0	0 +86	92138	0	1006	0	92138	0	0 +87	93143	0	1005	0	93143	0	0 +88	94148	0	1005	0	94148	0	0 +89	95155	0	1007	0	95155	0	0 +90	96160	0	1005	0	96160	0	0 +91	97165	0	1005	0	97165	0	0 +92	98168	0	1003	0	98168	0	0 +93	99173	0	1005	0	99173	0	0 +94	100177	0	1004	0	100177	0	0 +95	101181	0	1004	0	101181	0	0 +96	102185	0	1004	0	102185	0	0 +97	103189	0	1004	0	103189	0	0 +98	104193	0	1004	0	104193	0	0 +99	105197	0	1004	0	105197	0	0 +100	106202	0	1005	0	106202	0	0 +101	107206	0	1004	0	107206	0	0 +102	108210	0	1004	0	108210	0	0 +103	109213	0	1003	0	109213	0	0 +104	110217	0	1004	0	110217	0	0 +105	111220	0	1003	0	111220	0	0 +106	112223	0	1003	0	112223	0	0 +107	113227	0	1004	0	113227	0	0 +108	114232	0	1005	0	114232	0	0 +109	115237	0	1005	0	115237	0	0 +110	116241	0	1004	0	116241	0	0 +111	117246	0	1005	0	117246	0	0 +112	118250	0	1004	0	118250	0	0 +113	119254	0	1004	0	119254	0	0 +114	120259	0	1005	0	120259	0	0 +115	121264	0	1005	0	121264	0	0 +116	122270	0	1006	0	122270	0	0 +117	123273	0	1003	0	123273	0	0 +118	124279	0	1006	0	124279	0	0 +119	125284	0	1005	0	125284	0	0 +120	126289	0	1005	0	126289	0	0 +121	127294	0	1005	0	127294	0	0 +122	128297	0	1003	0	128297	0	0 +123	129301	0	1004	0	129301	0	0 +124	130305	0	1004	0	130305	0	0 +125	131310	0	1005	0	131310	0	0 +126	132314	0	1004	0	132314	0	0 +127	133318	0	1004	0	133318	0	0 +128	134322	0	1004	0	134322	0	0 +129	135326	0	1004	0	135326	0	0 +130	136331	0	1005	0	136331	0	0 +131	137337	0	1006	0	137337	0	0 +132	138341	0	1004	0	138341	0	0 +133	139347	0	1006	0	139347	0	0 +134	140350	0	1003	0	140350	0	0 +135	141353	0	1003	0	141353	0	0 +136	142357	0	1004	0	142357	0	0 +137	143362	0	1005	0	143362	0	0 +138	144366	0	1004	0	144366	0	0 +139	145369	0	1003	0	145369	0	0 +140	146372	0	1003	0	146372	0	0 +141	147376	0	1004	0	147376	0	0 +142	148380	0	1004	0	148380	0	0 +143	149384	0	1004	0	149384	0	0 +144	150389	0	1005	0	150389	0	0 +145	151393	0	1004	0	151393	0	0 +146	152398	0	1005	0	152398	0	0 +147	153403	0	1005	0	153403	0	0 +148	154407	0	1004	0	154407	0	0 +149	155412	0	1005	0	155412	0	0 +150	156417	0	1005	0	156417	0	0 +151	157421	0	1004	0	157421	0	0 +152	158425	0	1004	0	158425	0	0 +153	159429	0	1004	0	159429	0	0 +154	160432	0	1003	0	160432	0	0 +155	161436	0	1004	0	161436	0	0 +156	162440	0	1004	0	162440	0	0 +157	163444	0	1004	0	163444	0	0 +158	164448	0	1004	0	164448	0	0 +159	165452	0	1004	0	165452	0	0 +160	166457	0	1005	0	166457	0	0 +161	167461	0	1004	0	167461	0	0 +162	168465	0	1004	0	168465	0	0 +163	169470	0	1005	0	169470	0	0 +164	170475	0	1005	0	170475	0	0 +165	171479	0	1004	0	171479	0	0 +166	172482	0	1003	0	172482	0	0 +167	173486	0	1004	0	173486	0	0 +168	174490	0	1004	0	174490	0	0 +169	175495	0	1005	0	175495	0	0 +170	176499	0	1004	0	176499	0	0 +171	177504	0	1005	0	177504	0	0 +172	178524	0	1020	0	178524	0	0 +173	179528	0	1004	0	179528	0	0 +174	180533	0	1005	0	180533	0	0 +175	181538	0	1005	0	181538	0	0 +176	182542	0	1004	0	182542	0	0 +177	183546	0	1004	0	183546	0	0 +178	184551	0	1005	0	184551	0	0 +179	185556	0	1005	0	185556	0	0 +180	186561	0	1005	0	186561	0	0 +181	187566	0	1005	0	187566	0	0 +182	188571	0	1005	0	188571	0	0 +183	189574	0	1003	0	189574	0	0 +184	190578	0	1004	0	190578	0	0 +185	191583	0	1005	0	191583	0	0 +186	192588	0	1005	0	192588	0	0 +187	193592	0	1004	0	193592	0	0 +188	194596	0	1004	0	194596	0	0 +189	195601	0	1005	0	195601	0	0 +190	197363	0	1762	0	197363	0	0 +191	198368	0	1005	0	198368	0	0 +192	199372	0	1004	0	199372	0	0 +193	200377	0	1005	0	200377	0	0 +194	201382	0	1005	0	201382	0	0 +195	202386	0	1004	0	202386	0	0 +196	203391	0	1005	0	203391	0	0 +197	204395	0	1004	0	204395	0	0 +198	205400	0	1005	0	205400	0	0 +199	206405	0	1005	0	206405	0	0 +200	207409	0	1004	0	207409	0	0 +201	208414	0	1005	0	208414	0	0 +202	209418	0	1004	0	209418	0	0 +203	210422	0	1004	0	210422	0	0 +204	211426	0	1004	0	211426	0	0 +205	212431	0	1005	0	212431	0	0 +206	213434	0	1003	0	213434	0	0 +207	214439	0	1005	0	214439	0	0 +208	215444	0	1005	0	215444	0	0 +209	216449	0	1005	0	216449	0	0 +210	217454	0	1005	0	217454	0	0 +211	218458	0	1004	0	218458	0	0 +212	219462	0	1004	0	219462	0	0 +213	220467	0	1005	0	220467	0	0 +214	221471	0	1004	0	221471	0	0 +215	222475	0	1004	0	222475	0	0 +216	223479	0	1004	0	223479	0	0 +217	224485	0	1006	0	224485	0	0 +218	225490	0	1005	0	225490	0	0 +219	226495	0	1005	0	226495	0	0 +220	227499	0	1004	0	227499	0	0 +221	228503	0	1004	0	228503	0	0 +222	229507	0	1004	0	229507	0	0 +223	230511	0	1004	0	230511	0	0 +224	231516	0	1005	0	231516	0	0 +225	232521	0	1005	0	232521	0	0 +226	233525	0	1004	0	233525	0	0 +227	234530	0	1005	0	234530	0	0 +228	235534	0	1004	0	235534	0	0 +229	236538	0	1004	0	236538	0	0 +230	237542	0	1004	0	237542	0	0 +231	238545	0	1003	0	238545	0	0 +232	239549	0	1004	0	239549	0	0 +233	240553	0	1004	0	240553	0	0 +234	241557	0	1004	0	241557	0	0 +235	242562	0	1005	0	242562	0	0 +236	243567	0	1005	0	243567	0	0 +237	244572	0	1005	0	244572	0	0 +238	245576	0	1004	0	245576	0	0 +239	246581	0	1005	0	246581	0	0 +240	247585	0	1004	0	247585	0	0 +241	248590	0	1005	0	248590	0	0 +242	249595	0	1005	0	249595	0	0 +243	250599	0	1004	0	250599	0	0 +244	251604	0	1005	0	251604	0	0 +245	252608	0	1004	0	252608	0	0 +246	253613	0	1005	0	253613	0	0 +247	254618	0	1005	0	254618	0	0 +248	255622	0	1004	0	255622	0	0 +249	256627	0	1005	0	256627	0	0 +250	257632	0	1005	0	257632	0	0 +251	258636	0	1004	0	258636	0	0 +252	259640	0	1004	0	259640	0	0 +253	260643	0	1003	0	260643	0	0 +254	261648	0	1005	0	261648	0	0 +255	262653	0	1005	0	262653	0	0 +256	263657	0	1004	0	263657	0	0 +257	264662	0	1005	0	264662	0	0 +258	265666	0	1004	0	265666	0	0 +259	266670	0	1004	0	266670	0	0 +260	267673	0	1003	0	267673	0	0 +261	268678	0	1005	0	268678	0	0 +262	269682	0	1004	0	269682	0	0 +263	270685	0	1003	0	270685	0	0 +264	271689	0	1004	0	271689	0	0 +265	272692	0	1003	0	272692	0	0 +266	273696	0	1004	0	273696	0	0 +267	274700	0	1004	0	274700	0	0 +268	275705	0	1005	0	275705	0	0 +269	276710	0	1005	0	276710	0	0 +270	277713	0	1003	0	277713	0	0 +271	278717	0	1004	0	278717	0	0 +272	279721	0	1004	0	279721	0	0 +273	280726	0	1005	0	280726	0	0 +274	281730	0	1004	0	281730	0	0 +275	282734	0	1004	0	282734	0	0 +276	283738	0	1004	0	283738	0	0 +277	284742	0	1004	0	284742	0	0 +278	285746	0	1004	0	285746	0	0 +279	286749	0	1003	0	286749	0	0 +280	287752	0	1003	0	287752	0	0 +281	288756	0	1004	0	288756	0	0 +282	289761	0	1005	0	289761	0	0 +283	290766	0	1005	0	290766	0	0 +284	291771	0	1005	0	291771	0	0 +285	292776	0	1005	0	292776	0	0 +286	293780	0	1004	0	293780	0	0 +287	294784	0	1004	0	294784	0	0 +288	295787	0	1003	0	295787	0	0 +289	296791	0	1004	0	296791	0	0 +290	297797	0	1006	0	297797	0	0 +291	298801	0	1004	0	298801	0	0 +292	299806	0	1005	0	299806	0	0 +293	300810	0	1004	0	300810	0	0 +294	301815	0	1005	0	301815	0	0 +295	302820	0	1005	0	302820	0	0 +296	303824	0	1004	0	303824	0	0 +297	304828	0	1004	0	304828	0	0 +298	305833	0	1005	0	305833	0	0 +299	306838	0	1005	0	306838	0	0 +300	307842	0	1004	0	307842	0	0 +301	308847	0	1005	0	308847	0	0 +302	309852	0	1005	0	309852	0	0 +303	310857	0	1005	0	310857	0	0 +304	311871	0	1014	0	311871	0	0 +305	312875	0	1004	0	312875	0	0 +306	313880	0	1005	0	313880	0	0 +307	314885	0	1005	0	314885	0	0 +308	315889	0	1004	0	315889	0	0 +309	316894	0	1005	0	316894	0	0 +310	317899	0	1005	0	317899	0	0 +311	318903	0	1004	0	318903	0	0 +312	319906	0	1003	0	319906	0	0 +313	320910	0	1004	0	320910	0	0 +314	321914	0	1004	0	321914	0	0 +315	322919	0	1005	0	322919	0	0 +316	323923	0	1004	0	323923	0	0 +317	324927	0	1004	0	324927	0	0 +318	325931	0	1004	0	325931	0	0 +319	326936	0	1005	0	326936	0	0 +320	327941	0	1005	0	327941	0	0 +321	328945	0	1004	0	328945	0	0 +322	329950	0	1005	0	329950	0	0 +323	330954	0	1004	0	330954	0	0 +324	331959	0	1005	0	331959	0	0 +325	332963	0	1004	0	332963	0	0 +326	333968	0	1005	0	333968	0	0 +327	334973	0	1005	0	334973	0	0 +328	335978	0	1005	0	335978	0	0 +329	336983	0	1005	0	336983	0	0 +330	337987	0	1004	0	337987	0	0 +331	338992	0	1005	0	338992	0	0 +332	339996	0	1004	0	339996	0	0 +333	341001	0	1005	0	341001	0	0 +334	342006	0	1005	0	342006	0	0 +335	343010	0	1004	0	343010	0	0 +336	344015	0	1005	0	344015	0	0 +337	345019	0	1004	0	345019	0	0 +338	346024	0	1005	0	346024	0	0 +339	347028	0	1004	0	347028	0	0 +340	348032	0	1004	0	348032	0	0 +341	349036	0	1004	0	349036	0	0 +342	350041	0	1005	0	350041	0	0 +343	351046	0	1005	0	351046	0	0 +344	352050	0	1004	0	352050	0	0 +345	353054	0	1004	0	353054	0	0 +346	354059	0	1005	0	354059	0	0 +347	355064	0	1005	0	355064	0	0 +348	356069	0	1005	0	356069	0	0 +349	357075	0	1006	0	357075	0	0 +350	358080	0	1005	0	358080	0	0 +351	359085	0	1005	0	359085	0	0 +352	360088	0	1003	0	360088	0	0 +353	361093	0	1005	0	361093	0	0 +354	362097	0	1004	0	362097	0	0 +355	363101	0	1004	0	363101	0	0 +356	364105	0	1004	0	364105	0	0 +357	365109	0	1004	0	365109	0	0 +358	366117	0	1008	0	366117	0	0 +359	367122	0	1005	0	367122	0	0 +360	368127	0	1005	0	368127	0	0 +361	369132	0	1005	0	369132	0	0 +362	370135	0	1003	0	370135	0	0 +363	371141	0	1006	0	371141	0	0 +364	372147	0	1006	0	372147	0	0 +365	373152	0	1005	0	373152	0	0 +366	374156	0	1004	0	374156	0	0 +367	375160	0	1004	0	375160	0	0 +368	376163	0	1003	0	376163	0	0 +369	377168	0	1005	0	377168	0	0 +370	378171	0	1003	0	378171	0	0 +371	379175	0	1004	0	379175	0	0 +372	380178	0	1003	0	380178	0	0 +373	381183	0	1005	0	381183	0	0 +374	382187	0	1004	0	382187	0	0 +375	383192	0	1005	0	383192	0	0 +376	384197	0	1005	0	384197	0	0 +377	385202	0	1005	0	385202	0	0 +378	386207	0	1005	0	386207	0	0 +379	387212	0	1005	0	387212	0	0 +380	388216	0	1004	0	388216	0	0 +381	389220	0	1004	0	389220	0	0 +382	390225	0	1005	0	390225	0	0 +383	391229	0	1004	0	391229	0	0 +384	392234	0	1005	0	392234	0	0 +385	393239	0	1005	0	393239	0	0 +386	394242	0	1003	0	394242	0	0 +387	395246	0	1004	0	395246	0	0 +388	396251	0	1005	0	396251	0	0 +389	397255	0	1004	0	397255	0	0 +390	398259	0	1004	0	398259	0	0 +391	399263	0	1004	0	399263	0	0 +392	400268	0	1005	0	400268	0	0 +393	401273	0	1005	0	401273	0	0 +394	402278	0	1005	0	402278	0	0 +395	403282	0	1004	0	403282	0	0 +396	404286	0	1004	0	404286	0	0 +397	405290	0	1004	0	405290	0	0 +398	406294	0	1004	0	406294	0	0 +399	407299	0	1005	0	407299	0	0 +400	408304	0	1005	0	408304	0	0 +401	409309	0	1005	0	409309	0	0 +402	410313	0	1004	0	410313	0	0 +403	411318	0	1005	0	411318	0	0 +404	412322	0	1004	0	412322	0	0 +405	413327	0	1005	0	413327	0	0 +406	414332	0	1005	0	414332	0	0 +407	415337	0	1005	0	415337	0	0 +408	416340	0	1003	0	416340	0	0 +409	417344	0	1004	0	417344	0	0 +410	418349	0	1005	0	418349	0	0 +411	419354	0	1005	0	419354	0	0 +412	420359	0	1005	0	420359	0	0 +413	421363	0	1004	0	421363	0	0 +414	422368	0	1005	0	422368	0	0 +415	423372	0	1004	0	423372	0	0 +416	424377	0	1005	0	424377	0	0 +417	425381	0	1004	0	425381	0	0 +418	426384	0	1003	0	426384	0	0 +419	427389	0	1005	0	427389	0	0 +420	428393	0	1004	0	428393	0	0 +421	429398	0	1005	0	429398	0	0 +422	430403	0	1005	0	430403	0	0 +423	431408	0	1005	0	431408	0	0 +424	432412	0	1004	0	432412	0	0 +425	433417	0	1005	0	433417	0	0 +426	434421	0	1004	0	434421	0	0 +427	435426	0	1005	0	435426	0	0 +428	436430	0	1004	0	436430	0	0 +429	437434	0	1004	0	437434	0	0 +430	438437	0	1003	0	438437	0	0 +431	439442	0	1005	0	439442	0	0 +432	440447	0	1005	0	440447	0	0 +433	441451	0	1004	0	441451	0	0 +434	442456	0	1005	0	442456	0	0 +435	443461	0	1005	0	443461	0	0 +436	444464	0	1003	0	444464	0	0 +437	445468	0	1004	0	445468	0	0 +438	446471	0	1003	0	446471	0	0 +439	447476	0	1005	0	447476	0	0 +440	448481	0	1005	0	448481	0	0 +441	449485	0	1004	0	449485	0	0 +442	450490	0	1005	0	450490	0	0 +443	451495	0	1005	0	451495	0	0 +444	452500	0	1005	0	452500	0	0 +445	453504	0	1004	0	453504	0	0 +446	454509	0	1005	0	454509	0	0 +447	455513	0	1004	0	455513	0	0 +448	456518	0	1005	0	456518	0	0 +449	457523	0	1005	0	457523	0	0 +450	458528	0	1005	0	458528	0	0 +451	459533	0	1005	0	459533	0	0 +452	460538	0	1005	0	460538	0	0 +453	461543	0	1005	0	461543	0	0 +454	462546	0	1003	0	462546	0	0 +455	463551	0	1005	0	463551	0	0 +456	464556	0	1005	0	464556	0	0 +457	465559	0	1003	0	465559	0	0 +458	466562	0	1003	0	466562	0	0 +459	467565	0	1003	0	467565	0	0 +460	468570	0	1005	0	468570	0	0 +461	469575	0	1005	0	469575	0	0 +462	470578	0	1003	0	470578	0	0 +463	471581	0	1003	0	471581	0	0 +464	472586	0	1005	0	472586	0	0 +465	473591	0	1005	0	473591	0	0 +466	474596	0	1005	0	474596	0	0 +467	475600	0	1004	0	475600	0	0 +468	476605	0	1005	0	476605	0	0 +469	477609	0	1004	0	477609	0	0 +470	478614	0	1005	0	478614	0	0 +471	479618	0	1004	0	479618	0	0 +472	480622	0	1004	0	480622	0	0 +473	481627	0	1005	0	481627	0	0 +474	482630	0	1003	0	482630	0	0 +475	483634	0	1004	0	483634	0	0 +476	484639	0	1005	0	484639	0	0 +477	485643	0	1004	0	485643	0	0 +478	486647	0	1004	0	486647	0	0 +479	487651	0	1004	0	487651	0	0 +480	488655	0	1004	0	488655	0	0 +481	489659	0	1004	0	489659	0	0 +482	490663	0	1004	0	490663	0	0 +483	491668	0	1005	0	491668	0	0 +484	492673	0	1005	0	492673	0	0 +485	493678	0	1005	0	493678	0	0 +486	494683	0	1005	0	494683	0	0 +487	495687	0	1004	0	495687	0	0 +488	496692	0	1005	0	496692	0	0 +489	497697	0	1005	0	497697	0	0 +490	498702	0	1005	0	498702	0	0 +491	499707	0	1005	0	499707	0	0 +492	500712	0	1005	0	500712	0	0 +493	501717	0	1005	0	501717	0	0 +494	502721	0	1004	0	502721	0	0 +495	503725	0	1004	0	503725	0	0 +496	504730	0	1005	0	504730	0	0 +497	505734	0	1004	0	505734	0	0 +498	506738	0	1004	0	506738	0	0 +499	508045	0	1307	0	508045	0	0 +500	509050	0	1005	0	509050	0	0 +501	510054	0	1004	0	510054	0	0 +502	511059	0	1005	0	511059	0	0 +503	512062	0	1003	0	512062	0	0 +504	513066	0	1004	0	513066	0	0 +505	514069	0	1003	0	514069	0	0 +506	515074	0	1005	0	515074	0	0 +507	516079	0	1005	0	516079	0	0 +508	517084	0	1005	0	517084	0	0 +509	518088	0	1004	0	518088	0	0 +510	519094	0	1006	0	519094	0	0 +511	520098	0	1004	0	520098	0	0 +512	521103	0	1005	0	521103	0	0 +513	522107	0	1004	0	522107	0	0 +514	523112	0	1005	0	523112	0	0 +515	524121	0	1009	0	524121	0	0 +516	525127	0	1006	0	525127	0	0 +517	526132	0	1005	0	526132	0	0 +518	527137	0	1005	0	527137	0	0 +519	528143	0	1006	0	528143	0	0 +520	529147	0	1004	0	529147	0	0 +521	530151	0	1004	0	530151	0	0 +522	531156	0	1005	0	531156	0	0 +523	532160	0	1004	0	532160	0	0 +524	533165	0	1005	0	533165	0	0 +525	534169	0	1004	0	534169	0	0 +526	535172	0	1003	0	535172	0	0 +527	536175	0	1003	0	536175	0	0 +528	537180	0	1005	0	537180	0	0 +529	538185	0	1005	0	538185	0	0 +530	539191	0	1006	0	539191	0	0 +531	540195	0	1004	0	540195	0	0 +532	541200	0	1005	0	541200	0	0 +533	542204	0	1004	0	542204	0	0 +534	543209	0	1005	0	543209	0	0 +535	544213	0	1004	0	544213	0	0 +536	545218	0	1005	0	545218	0	0 +537	546222	0	1004	0	546222	0	0 +538	547226	0	1004	0	547226	0	0 +539	548229	0	1003	0	548229	0	0 +540	549233	0	1004	0	549233	0	0 +541	550238	0	1005	0	550238	0	0 +542	551242	0	1004	0	551242	0	0 +543	552247	0	1005	0	552247	0	0 +544	553252	0	1005	0	553252	0	0 +545	554257	0	1005	0	554257	0	0 +546	555262	0	1005	0	555262	0	0 +547	556266	0	1004	0	556266	0	0 +548	557271	0	1005	0	557271	0	0 +549	558275	0	1004	0	558275	0	0 +550	559280	0	1005	0	559280	0	0 +551	560285	0	1005	0	560285	0	0 +552	561289	0	1004	0	561289	0	0 +553	562294	0	1005	0	562294	0	0 +554	563299	0	1005	0	563299	0	0 +555	564303	0	1004	0	564303	0	0 +556	565308	0	1005	0	565308	0	0 +557	566313	0	1005	0	566313	0	0 +558	567317	0	1004	0	567317	0	0 +559	568322	0	1005	0	568322	0	0 +560	569326	0	1004	0	569326	0	0 +561	570331	0	1005	0	570331	0	0 +562	571334	0	1003	0	571334	0	0 +563	572338	0	1004	0	572338	0	0 +564	573343	0	1005	0	573343	0	0 +565	574347	0	1004	0	574347	0	0 +566	575351	0	1004	0	575351	0	0 +567	576357	0	1006	0	576357	0	0 +568	577360	0	1003	0	577360	0	0 +569	578364	0	1004	0	578364	0	0 +570	579368	0	1004	0	579368	0	0 +571	580372	0	1004	0	580372	0	0 +572	581377	0	1005	0	581377	0	0 +573	582381	0	1004	0	582381	0	0 +574	583384	0	1003	0	583384	0	0 +575	584387	0	1003	0	584387	0	0 +576	585391	0	1004	0	585391	0	0 +577	586395	0	1004	0	586395	0	0 +578	587400	0	1005	0	587400	0	0 +579	588405	0	1005	0	588405	0	0 +580	589409	0	1004	0	589409	0	0 +581	590414	0	1005	0	590414	0	0 +582	591419	0	1005	0	591419	0	0 +583	592423	0	1004	0	592423	0	0 +584	593428	0	1005	0	593428	0	0 +585	594433	0	1005	0	594433	0	0 +586	595437	0	1004	0	595437	0	0 +587	596442	0	1005	0	596442	0	0 +588	597445	0	1003	0	597445	0	0 +589	598448	0	1003	0	598448	0	0 +590	599453	0	1005	0	599453	0	0 +591	600458	0	1005	0	600458	0	0 +592	601463	0	1005	0	601463	0	0 +593	602467	0	1004	0	602467	0	0 +594	603472	0	1005	0	603472	0	0 +595	604477	0	1005	0	604477	0	0 +596	605481	0	1004	0	605481	0	0 +597	606487	0	1006	0	606487	0	0 +598	607491	0	1004	0	607491	0	0 +599	608495	0	1004	0	608495	0	0 +600	609500	0	1005	0	609500	0	0 +601	610505	0	1005	0	610505	0	0 +602	611509	0	1004	0	611509	0	0 +603	612513	0	1004	0	612513	0	0 +604	613516	0	1003	0	613516	0	0 +605	614521	0	1005	0	614521	0	0 +606	615525	0	1004	0	615525	0	0 +607	616528	0	1003	0	616528	0	0 +608	617532	0	1004	0	617532	0	0 +609	618537	0	1005	0	618537	0	0 +610	619542	0	1005	0	619542	0	0 +611	620546	0	1004	0	620546	0	0 +612	621551	0	1005	0	621551	0	0 +613	622555	0	1004	0	622555	0	0 +614	623560	0	1005	0	623560	0	0 +615	624565	0	1005	0	624565	0	0 +616	625569	0	1004	0	625569	0	0 +617	626574	0	1005	0	626574	0	0 +618	627578	0	1004	0	627578	0	0 +619	628583	0	1005	0	628583	0	0 +620	629588	0	1005	0	629588	0	0 +621	630592	0	1004	0	630592	0	0 +622	631597	0	1005	0	631597	0	0 +623	632601	0	1004	0	632601	0	0 +624	633604	0	1003	0	633604	0	0 +625	634609	0	1005	0	634609	0	0 +626	635614	0	1005	0	635614	0	0 +627	636618	0	1004	0	636618	0	0 +628	637623	0	1005	0	637623	0	0 +629	638628	0	1005	0	638628	0	0 +630	639634	0	1006	0	639634	0	0 +631	640639	0	1005	0	640639	0	0 +632	641644	0	1005	0	641644	0	0 +633	642648	0	1004	0	642648	0	0 +634	643653	0	1005	0	643653	0	0 +635	644658	0	1005	0	644658	0	0 +636	645662	0	1004	0	645662	0	0 +637	646667	0	1005	0	646667	0	0 +638	647672	0	1005	0	647672	0	0 +639	648676	0	1004	0	648676	0	0 +640	649681	0	1005	0	649681	0	0 +641	650685	0	1004	0	650685	0	0 +642	651690	0	1005	0	651690	0	0 +643	652694	0	1004	0	652694	0	0 +644	653699	0	1005	0	653699	0	0 +645	654703	0	1004	0	654703	0	0 +646	655708	0	1005	0	655708	0	0 +647	656713	0	1005	0	656713	0	0 +648	657717	0	1004	0	657717	0	0 +649	658722	0	1005	0	658722	0	0 +650	659727	0	1005	0	659727	0	0 +651	660731	0	1004	0	660731	0	0 +652	661736	0	1005	0	661736	0	0 +653	662741	0	1005	0	662741	0	0 +654	663745	0	1004	0	663745	0	0 +655	664748	0	1003	0	664748	0	0 +656	665751	0	1003	0	665751	0	0 +657	666756	0	1005	0	666756	0	0 +658	667761	0	1005	0	667761	0	0 +659	668765	0	1004	0	668765	0	0 +660	669770	0	1005	0	669770	0	0 +661	670774	0	1004	0	670774	0	0 +662	671779	0	1005	0	671779	0	0 +663	672784	0	1005	0	672784	0	0 +664	673789	0	1005	0	673789	0	0 +665	674793	0	1004	0	674793	0	0 +666	675798	0	1005	0	675798	0	0 +667	676801	0	1003	0	676801	0	0 +668	677805	0	1004	0	677805	0	0 +669	678808	0	1003	0	678808	0	0 +670	679813	0	1005	0	679813	0	0 +671	680818	0	1005	0	680818	0	0 +672	681822	0	1004	0	681822	0	0 +673	682827	0	1005	0	682827	0	0 +674	683831	0	1004	0	683831	0	0 +675	684836	0	1005	0	684836	0	0 +676	685840	0	1004	0	685840	0	0 +677	686843	0	1003	0	686843	0	0 +678	687848	0	1005	0	687848	0	0 +679	688853	0	1005	0	688853	0	0 +680	689857	0	1004	0	689857	0	0 +681	690871	0	1014	0	690871	0	0 +682	691875	0	1004	0	691875	0	0 +683	692880	0	1005	0	692880	0	0 +684	693885	0	1005	0	693885	0	0 +685	694890	0	1005	0	694890	0	0 +686	695894	0	1004	0	695894	0	0 +687	696899	0	1005	0	696899	0	0 +688	697903	0	1004	0	697903	0	0 +689	698908	0	1005	0	698908	0	0 +690	699913	0	1005	0	699913	0	0 +691	700918	0	1005	0	700918	0	0 +692	701922	0	1004	0	701922	0	0 +693	702927	0	1005	0	702927	0	0 +694	703932	0	1005	0	703932	0	0 +695	704936	0	1004	0	704936	0	0 +696	705940	0	1004	0	705940	0	0 +697	706944	0	1004	0	706944	0	0 +698	707949	0	1005	0	707949	0	0 +699	708953	0	1004	0	708953	0	0 +700	709957	0	1004	0	709957	0	0 +701	710960	0	1003	0	710960	0	0 +702	711965	0	1005	0	711965	0	0 +703	712969	0	1004	0	712969	0	0 +704	713974	0	1005	0	713974	0	0 +705	714979	0	1005	0	714979	0	0 +706	715984	0	1005	0	715984	0	0 +707	716988	0	1004	0	716988	0	0 +708	717992	0	1004	0	717992	0	0 +709	718997	0	1005	0	718997	0	0 +710	720002	0	1005	0	720002	0	0 +711	721006	0	1004	0	721006	0	0 +712	722010	0	1004	0	722010	0	0 +713	723015	0	1005	0	723015	0	0 +714	724020	0	1005	0	724020	0	0 +715	725024	0	1004	0	725024	0	0 +716	726028	0	1004	0	726028	0	0 +717	727031	0	1003	0	727031	0	0 +718	728035	0	1004	0	728035	0	0 +719	729038	0	1003	0	729038	0	0 +720	730043	0	1005	0	730043	0	0 +721	731048	0	1005	0	731048	0	0 +722	732052	0	1004	0	732052	0	0 +723	733057	0	1005	0	733057	0	0 +724	734061	0	1004	0	734061	0	0 +725	735064	0	1003	0	735064	0	0 +726	736068	0	1004	0	736068	0	0 +727	737073	0	1005	0	737073	0	0 +728	738078	0	1005	0	738078	0	0 +729	739083	0	1005	0	739083	0	0 +730	740087	0	1004	0	740087	0	0 +731	741093	0	1006	0	741093	0	0 +732	742096	0	1003	0	742096	0	0 +733	743100	0	1004	0	743100	0	0 +734	744104	0	1004	0	744104	0	0 +735	745109	0	1005	0	745109	0	0 +736	746113	0	1004	0	746113	0	0 +737	747121	0	1008	0	747121	0	0 +738	748127	0	1006	0	748127	0	0 +739	749132	0	1005	0	749132	0	0 +740	750137	0	1005	0	750137	0	0 +741	751142	0	1005	0	751142	0	0 +742	752146	0	1004	0	752146	0	0 +743	753151	0	1005	0	753151	0	0 +744	754155	0	1004	0	754155	0	0 +745	755158	0	1003	0	755158	0	0 +746	756161	0	1003	0	756161	0	0 +747	757166	0	1005	0	757166	0	0 +748	758171	0	1005	0	758171	0	0 +749	759176	0	1005	0	759176	0	0 +750	760180	0	1004	0	760180	0	0 +751	761185	0	1005	0	761185	0	0 +752	762189	0	1004	0	762189	0	0 +753	763193	0	1004	0	763193	0	0 +754	764197	0	1004	0	764197	0	0 +755	765200	0	1003	0	765200	0	0 +756	766205	0	1005	0	766205	0	0 +757	767209	0	1004	0	767209	0	0 +758	768214	0	1005	0	768214	0	0 +759	769218	0	1004	0	769218	0	0 +760	770223	0	1005	0	770223	0	0 +761	771228	0	1005	0	771228	0	0 +762	772233	0	1005	0	772233	0	0 +763	773237	0	1004	0	773237	0	0 +764	774242	0	1005	0	774242	0	0 +765	775247	0	1005	0	775247	0	0 +766	776250	0	1003	0	776250	0	0 +767	777254	0	1004	0	777254	0	0 +768	778259	0	1005	0	778259	0	0 +769	779263	0	1004	0	779263	0	0 +770	780266	0	1003	0	780266	0	0 +771	781270	0	1004	0	781270	0	0 +772	782275	0	1005	0	782275	0	0 +773	783279	0	1004	0	783279	0	0 +774	784283	0	1004	0	784283	0	0 +775	785287	0	1004	0	785287	0	0 +776	786292	0	1005	0	786292	0	0 +777	787296	0	1004	0	787296	0	0 +778	788301	0	1005	0	788301	0	0 +779	789305	0	1004	0	789305	0	0 +780	790309	0	1004	0	790309	0	0 +781	791312	0	1003	0	791312	0	0 +782	792317	0	1005	0	792317	0	0 +783	793322	0	1005	0	793322	0	0 +784	794326	0	1004	0	794326	0	0 +785	795330	0	1004	0	795330	0	0 +786	796334	0	1004	0	796334	0	0 +787	797338	0	1004	0	797338	0	0 +788	798343	0	1005	0	798343	0	0 +789	799349	0	1006	0	799349	0	0 +790	800353	0	1004	0	800353	0	0 +791	801358	0	1005	0	801358	0	0 +792	802361	0	1003	0	802361	0	0 +793	803365	0	1004	0	803365	0	0 +794	804371	0	1006	0	804371	0	0 +795	805374	0	1003	0	805374	0	0 +796	806379	0	1005	0	806379	0	0 +797	807384	0	1005	0	807384	0	0 +798	808388	0	1004	0	808388	0	0 +799	809391	0	1003	0	809391	0	0 +800	810396	0	1005	0	810396	0	0 +801	811401	0	1005	0	811401	0	0 +802	812405	0	1004	0	812405	0	0 +803	813410	0	1005	0	813410	0	0 +804	814414	0	1004	0	814414	0	0 +805	815418	0	1004	0	815418	0	0 +806	816423	0	1005	0	816423	0	0 +807	817429	0	1006	0	817429	0	0 +808	818433	0	1004	0	818433	0	0 +809	819438	0	1005	0	819438	0	0 +810	820442	0	1004	0	820442	0	0 +811	821447	0	1005	0	821447	0	0 +812	822452	0	1005	0	822452	0	0 +813	823456	0	1004	0	823456	0	0 +814	824459	0	1003	0	824459	0	0 +815	825462	0	1003	0	825462	0	0 +816	826467	0	1005	0	826467	0	0 +817	827472	0	1005	0	827472	0	0 +818	828477	0	1005	0	828477	0	0 +819	829481	0	1004	0	829481	0	0 +820	830486	0	1005	0	830486	0	0 +821	831490	0	1004	0	831490	0	0 +822	832494	0	1004	0	832494	0	0 +823	833497	0	1003	0	833497	0	0 +824	834501	0	1004	0	834501	0	0 +825	835505	0	1004	0	835505	0	0 +826	836510	0	1005	0	836510	0	0 +827	837514	0	1004	0	837514	0	0 +828	838518	0	1004	0	838518	0	0 +829	839523	0	1005	0	839523	0	0 +830	840528	0	1005	0	840528	0	0 +831	841532	0	1004	0	841532	0	0 +832	842536	0	1004	0	842536	0	0 +833	843540	0	1004	0	843540	0	0 +834	844543	0	1003	0	844543	0	0 +835	845548	0	1005	0	845548	0	0 +836	846552	0	1004	0	846552	0	0 +837	847557	0	1005	0	847557	0	0 +838	848562	0	1005	0	848562	0	0 +839	849567	0	1005	0	849567	0	0 +840	850571	0	1004	0	850571	0	0 +841	851575	0	1004	0	851575	0	0 +842	852578	0	1003	0	852578	0	0 +843	853582	0	1004	0	853582	0	0 +844	854587	0	1005	0	854587	0	0 +845	855592	0	1005	0	855592	0	0 +846	856595	0	1003	0	856595	0	0 +847	857600	0	1005	0	857600	0	0 +848	858605	0	1005	0	858605	0	0 +849	859609	0	1004	0	859609	0	0 +850	860614	0	1005	0	860614	0	0 +851	861617	0	1003	0	861617	0	0 +852	862622	0	1005	0	862622	0	0 +853	863626	0	1004	0	863626	0	0 +854	864631	0	1005	0	864631	0	0 +855	865636	0	1005	0	865636	0	0 +856	866641	0	1005	0	866641	0	0 +857	867644	0	1003	0	867644	0	0 +858	868649	0	1005	0	868649	0	0 +859	869654	0	1005	0	869654	0	0 +860	870658	0	1004	0	870658	0	0 +861	871663	0	1005	0	871663	0	0 +862	872667	0	1004	0	872667	0	0 +863	873671	0	1004	0	873671	0	0 +864	874676	0	1005	0	874676	0	0 +865	875681	0	1005	0	875681	0	0 +866	876686	0	1005	0	876686	0	0 +867	877691	0	1005	0	877691	0	0 +868	878695	0	1004	0	878695	0	0 +869	879699	0	1004	0	879699	0	0 +870	880702	0	1003	0	880702	0	0 +871	881706	0	1004	0	881706	0	0 +872	882709	0	1003	0	882709	0	0 +873	883714	0	1005	0	883714	0	0 +874	884718	0	1004	0	884718	0	0 +875	885723	0	1005	0	885723	0	0 +876	886727	0	1004	0	886727	0	0 +877	887730	0	1003	0	887730	0	0 +878	888735	0	1005	0	888735	0	0 +879	889739	0	1004	0	889739	0	0 +880	890744	0	1005	0	890744	0	0 +881	891749	0	1005	0	891749	0	0 +882	892753	0	1004	0	892753	0	0 +883	893757	0	1004	0	893757	0	0 +884	894760	0	1003	0	894760	0	0 +885	895765	0	1005	0	895765	0	0 +886	896770	0	1005	0	896770	0	0 +887	897774	0	1004	0	897774	0	0 +888	898779	0	1005	0	898779	0	0 +889	899784	0	1005	0	899784	0	0 +890	900788	0	1004	0	900788	0	0 +891	901793	0	1005	0	901793	0	0 +892	902798	0	1005	0	902798	0	0 +893	903802	0	1004	0	903802	0	0 +894	904807	0	1005	0	904807	0	0 +895	905811	0	1004	0	905811	0	0 +896	906816	0	1005	0	906816	0	0 +897	907821	0	1005	0	907821	0	0 +898	908825	0	1004	0	908825	0	0 +899	909830	0	1005	0	909830	0	0 +900	910835	0	1005	0	910835	0	0 +901	911839	0	1004	0	911839	0	0 +902	912843	0	1004	0	912843	0	0 +903	913846	0	1003	0	913846	0	0 +904	914851	0	1005	0	914851	0	0 +905	915855	0	1004	0	915855	0	0 +906	916860	0	1005	0	916860	0	0 +907	917875	0	1015	0	917875	0	0 +908	918891	0	1016	0	918891	0	0 +909	919896	0	1005	0	919896	0	0 +910	920901	0	1005	0	920901	0	0 +911	921905	0	1004	0	921905	0	0 +912	922908	0	1003	0	922908	0	0 +913	923912	0	1004	0	923912	0	0 +914	924916	0	1004	0	924916	0	0 +915	925921	0	1005	0	925921	0	0 +916	926924	0	1003	0	926924	0	0 +917	927928	0	1004	0	927928	0	0 +918	928933	0	1005	0	928933	0	0 +919	929937	0	1004	0	929937	0	0 +920	930942	0	1005	0	930942	0	0 +921	931945	0	1003	0	931945	0	0 +922	932949	0	1004	0	932949	0	0 +923	933954	0	1005	0	933954	0	0 +924	934959	0	1005	0	934959	0	0 +925	935962	0	1003	0	935962	0	0 +926	936967	0	1005	0	936967	0	0 +927	937972	0	1005	0	937972	0	0 +928	938977	0	1005	0	938977	0	0 +929	939982	0	1005	0	939982	0	0 +930	940986	0	1004	0	940986	0	0 +931	941991	0	1005	0	941991	0	0 +932	942996	0	1005	0	942996	0	0 +933	944000	0	1004	0	944000	0	0 +934	945005	0	1005	0	945005	0	0 +935	946009	0	1004	0	946009	0	0 +936	947013	0	1004	0	947013	0	0 +937	948018	0	1005	0	948018	0	0 +938	949023	0	1005	0	949023	0	0 +939	950028	0	1005	0	950028	0	0 +940	951033	0	1005	0	951033	0	0 +941	952037	0	1004	0	952037	0	0 +942	953042	0	1005	0	953042	0	0 +943	954046	0	1004	0	954046	0	0 +944	955050	0	1004	0	955050	0	0 +945	956053	0	1003	0	956053	0	0 +946	957058	0	1005	0	957058	0	0 +947	958061	0	1003	0	958061	0	0 +948	959065	0	1004	0	959065	0	0 +949	960070	0	1005	0	960070	0	0 +950	961074	0	1004	0	961074	0	0 +951	962078	0	1004	0	962078	0	0 +952	963083	0	1005	0	963083	0	0 +953	964088	0	1005	0	964088	0	0 +954	965093	0	1005	0	965093	0	0 +955	966097	0	1004	0	966097	0	0 +956	967102	0	1005	0	967102	0	0 +957	968106	0	1004	0	968106	0	0 +958	969111	0	1005	0	969111	0	0 +959	970119	0	1008	0	970119	0	0 +960	971124	0	1005	0	971124	0	0 +961	972130	0	1006	0	972130	0	0 +962	973135	0	1005	0	973135	0	0 +963	974139	0	1004	0	974139	0	0 +964	975143	0	1004	0	975143	0	0 +965	976148	0	1005	0	976148	0	0 +966	977152	0	1004	0	977152	0	0 +967	978158	0	1006	0	978158	0	0 +968	979162	0	1004	0	979162	0	0 +969	980167	0	1005	0	980167	0	0 +970	981171	0	1004	0	981171	0	0 +971	982175	0	1004	0	982175	0	0 +972	983179	0	1004	0	983179	0	0 +973	984184	0	1005	0	984184	0	0 +974	985188	0	1004	0	985188	0	0 +975	986192	0	1004	0	986192	0	0 +976	987197	0	1005	0	987197	0	0 +977	988201	0	1004	0	988201	0	0 +978	989205	0	1004	0	989205	0	0 +979	990208	0	1003	0	990208	0	0 +980	991212	0	1004	0	991212	0	0 +981	992217	0	1005	0	992217	0	0 +982	993222	0	1005	0	993222	0	0 +983	994227	0	1005	0	994227	0	0 +984	995232	0	1005	0	995232	0	0 +985	996236	0	1004	0	996236	0	0 +986	997239	0	1003	0	997239	0	0 +987	998243	0	1004	0	998243	0	0 +988	999247	0	1004	0	999247	0	0 +989	1000250	0	1003	0	1000250	0	0 +990	1001254	0	1004	0	1001254	0	0 +991	1002259	0	1005	0	1002259	0	0 +992	1003263	0	1004	0	1003263	0	0 +993	1004268	0	1005	0	1004268	0	0 +994	1005273	0	1005	0	1005273	0	0 +995	1006277	0	1004	0	1006277	0	0 +996	1007284	0	1007	0	1007284	0	0 +997	1008288	0	1004	0	1008288	0	0 +998	1009291	0	1003	0	1009291	0	0 +999	1010295	0	1004	0	1010295	0	0 +1000	1011299	0	1004	0	1011299	0	0 +1001	1012304	0	1005	0	1012304	0	0 +1002	1013309	0	1005	0	1013309	0	0 +1003	1014314	0	1005	0	1014314	0	0 +1004	1015319	0	1005	0	1015319	0	0 +1005	1016322	0	1003	0	1016322	0	0 +1006	1017327	0	1005	0	1017327	0	0 +1007	1018331	0	1004	0	1018331	0	0 +1008	1019336	0	1005	0	1019336	0	0 +1009	1020341	0	1005	0	1020341	0	0 +1010	1021345	0	1004	0	1021345	0	0 +1011	1022349	0	1004	0	1022349	0	0 +1012	1023354	0	1005	0	1023354	0	0 +1013	1024357	0	1003	0	1024357	0	0 +1014	1025363	0	1006	0	1025363	0	0 +1015	1026369	0	1006	0	1026369	0	0 +1016	1027373	0	1004	0	1027373	0	0 +1017	1028378	0	1005	0	1028378	0	0 +1018	1029382	0	1004	0	1029382	0	0 +1019	1030386	0	1004	0	1030386	0	0 +1020	1031391	0	1005	0	1031391	0	0 +1021	1032394	0	1003	0	1032394	0	0 +1022	1033398	0	1004	0	1033398	0	0 +1023	1034403	0	1005	0	1034403	0	0 +1024	1035407	0	1004	0	1035407	0	0 +1025	1036412	0	1005	0	1036412	0	0 +1026	1037417	0	1005	0	1037417	0	0 +1027	1038422	0	1005	0	1038422	0	0 +1028	1039427	0	1005	0	1039427	0	0 +1029	1040431	0	1004	0	1040431	0	0 +1030	1041435	0	1004	0	1041435	0	0 +1031	1042439	0	1004	0	1042439	0	0 +1032	1043443	0	1004	0	1043443	0	0 +1033	1044447	0	1004	0	1044447	0	0 +1034	1045452	0	1005	0	1045452	0	0 +1035	1046457	0	1005	0	1046457	0	0 +1036	1047461	0	1004	0	1047461	0	0 +1037	1048465	0	1004	0	1048465	0	0 +1038	1049469	0	1004	0	1049469	0	0 +1039	1050474	0	1005	0	1050474	0	0 +1040	1051479	0	1005	0	1051479	0	0 +1041	1052483	0	1004	0	1052483	0	0 +1042	1053488	0	1005	0	1053488	0	0 +1043	1054493	0	1005	0	1054493	0	0 +1044	1055497	0	1004	0	1055497	0	0 +1045	1056502	0	1005	0	1056502	0	0 +1046	1057507	0	1005	0	1057507	0	0 +1047	1058512	0	1005	0	1058512	0	0 +1048	1059516	0	1004	0	1059516	0	0 +1049	1060521	0	1005	0	1060521	0	0 +1050	1061526	0	1005	0	1061526	0	0 +1051	1062530	0	1004	0	1062530	0	0 +1052	1063534	0	1004	0	1063534	0	0 +1053	1064538	0	1004	0	1064538	0	0 +1054	1065543	0	1005	0	1065543	0	0 +1055	1066546	0	1003	0	1066546	0	0 +1056	1067549	0	1003	0	1067549	0	0 +1057	1068552	0	1003	0	1068552	0	0 +1058	1069555	0	1003	0	1069555	0	0 +1059	1070558	0	1003	0	1070558	0	0 +1060	1071563	0	1005	0	1071563	0	0 +1061	1072568	0	1005	0	1072568	0	0 +1062	1073573	0	1005	0	1073573	0	0 +1063	1074578	0	1005	0	1074578	0	0 +1064	1075583	0	1005	0	1075583	0	0 +1065	1076588	0	1005	0	1076588	0	0 +1066	1077592	0	1004	0	1077592	0	0 +1067	1078597	0	1005	0	1078597	0	0 +1068	1079601	0	1004	0	1079601	0	0 +1069	1080606	0	1005	0	1080606	0	0 +1070	1081609	0	1003	0	1081609	0	0 +1071	1082613	0	1004	0	1082613	0	0 +1072	1083616	0	1003	0	1083616	0	0 +1073	1084620	0	1004	0	1084620	0	0 +1074	1085624	0	1004	0	1085624	0	0 +1075	1086629	0	1005	0	1086629	0	0 +1076	1087633	0	1004	0	1087633	0	0 +1077	1088636	0	1003	0	1088636	0	0 +1078	1089641	0	1005	0	1089641	0	0 +1079	1090644	0	1003	0	1090644	0	0 +1080	1091647	0	1003	0	1091647	0	0 +1081	1092652	0	1005	0	1092652	0	0 +1082	1093657	0	1005	0	1093657	0	0 +1083	1094660	0	1003	0	1094660	0	0 +1084	1095663	0	1003	0	1095663	0	0 +1085	1096666	0	1003	0	1096666	0	0 +1086	1097670	0	1004	0	1097670	0	0 +1087	1098675	0	1005	0	1098675	0	0 +1088	1099678	0	1003	0	1099678	0	0 +1089	1100682	0	1004	0	1100682	0	0 +1090	1101687	0	1005	0	1101687	0	0 +1091	1102692	0	1005	0	1102692	0	0 +1092	1103697	0	1005	0	1103697	0	0 +1093	1104701	0	1004	0	1104701	0	0 +1094	1105706	0	1005	0	1105706	0	0 +1095	1106710	0	1004	0	1106710	0	0 +1096	1107714	0	1004	0	1107714	0	0 +1097	1108719	0	1005	0	1108719	0	0 +1098	1109724	0	1005	0	1109724	0	0 +1099	1110728	0	1004	0	1110728	0	0 +1100	1111733	0	1005	0	1111733	0	0 +1101	1112738	0	1005	0	1112738	0	0 +1102	1113742	0	1004	0	1113742	0	0 +1103	1114749	0	1007	0	1114749	0	0 +1104	1115753	0	1004	0	1115753	0	0 +1105	1116758	0	1005	0	1116758	0	0 +1106	1117761	0	1003	0	1117761	0	0 +1107	1118766	0	1005	0	1118766	0	0 +1108	1119770	0	1004	0	1119770	0	0 +1109	1120774	0	1004	0	1120774	0	0 +1110	1121779	0	1005	0	1121779	0	0 +1111	1122784	0	1005	0	1122784	0	0 +1112	1123788	0	1004	0	1123788	0	0 +1113	1124792	0	1004	0	1124792	0	0 +1114	1125798	0	1006	0	1125798	0	0 +1115	1126802	0	1004	0	1126802	0	0 +1116	1127806	0	1004	0	1127806	0	0 +1117	1128812	0	1006	0	1128812	0	0 +1118	1129816	0	1004	0	1129816	0	0 +1119	1130819	0	1003	0	1130819	0	0 +1120	1131823	0	1004	0	1131823	0	0 +1121	1132827	0	1004	0	1132827	0	0 +1122	1133831	0	1004	0	1133831	0	0 +1123	1134835	0	1004	0	1134835	0	0 +1124	1135840	0	1005	0	1135840	0	0 +1125	1136844	0	1004	0	1136844	0	0 +1126	1137848	0	1004	0	1137848	0	0 +1127	1138852	0	1004	0	1138852	0	0 +1128	1139856	0	1004	0	1139856	0	0 +1129	1140871	0	1015	0	1140871	0	0 +1130	1141874	0	1003	0	1141874	0	0 +1131	1142879	0	1005	0	1142879	0	0 +1132	1143884	0	1005	0	1143884	0	0 +1133	1144889	0	1005	0	1144889	0	0 +1134	1145893	0	1004	0	1145893	0	0 +1135	1146897	0	1004	0	1146897	0	0 +1136	1147901	0	1004	0	1147901	0	0 +1137	1148906	0	1005	0	1148906	0	0 +1138	1149909	0	1003	0	1149909	0	0 +1139	1150912	0	1003	0	1150912	0	0 +1140	1151916	0	1004	0	1151916	0	0 +1141	1152921	0	1005	0	1152921	0	0 +1142	1153926	0	1005	0	1153926	0	0 +1143	1154930	0	1004	0	1154930	0	0 +1144	1155934	0	1004	0	1155934	0	0 +1145	1156939	0	1005	0	1156939	0	0 +1146	1157944	0	1005	0	1157944	0	0 +1147	1158949	0	1005	0	1158949	0	0 +1148	1159953	0	1004	0	1159953	0	0 +1149	1160957	0	1004	0	1160957	0	0 +1150	1161962	0	1005	0	1161962	0	0 +1151	1162966	0	1004	0	1162966	0	0 +1152	1163969	0	1003	0	1163969	0	0 +1153	1164975	0	1006	0	1164975	0	0 +1154	1165980	0	1005	0	1165980	0	0 +1155	1166984	0	1004	0	1166984	0	0 +1156	1167988	0	1004	0	1167988	0	0 +1157	1168994	0	1006	0	1168994	0	0 +1158	1169999	0	1005	0	1169999	0	0 +1159	1171003	0	1004	0	1171003	0	0 +1160	1172008	0	1005	0	1172008	0	0 +1161	1173013	0	1005	0	1173013	0	0 +1162	1174017	0	1004	0	1174017	0	0 +1163	1175022	0	1005	0	1175022	0	0 +1164	1176027	0	1005	0	1176027	0	0 +1165	1177031	0	1004	0	1177031	0	0 +1166	1178036	0	1005	0	1178036	0	0 +1167	1179041	0	1005	0	1179041	0	0 +1168	1180045	0	1004	0	1180045	0	0 +1169	1181050	0	1005	0	1181050	0	0 +1170	1182055	0	1005	0	1182055	0	0 +1171	1183061	0	1006	0	1183061	0	0 +1172	1184065	0	1004	0	1184065	0	0 +1173	1185070	0	1005	0	1185070	0	0 +1174	1186075	0	1005	0	1186075	0	0 +1175	1187079	0	1004	0	1187079	0	0 +1176	1188084	0	1005	0	1188084	0	0 +1177	1189088	0	1004	0	1189088	0	0 +1178	1190093	0	1005	0	1190093	0	0 +1179	1191098	0	1005	0	1191098	0	0 +1180	1192102	0	1004	0	1192102	0	0 +1181	1193106	0	1004	0	1193106	0	0 +1182	1194111	0	1005	0	1194111	0	0 +1183	1195118	0	1007	0	1195118	0	0 +1184	1196121	0	1003	0	1196121	0	0 +1185	1197128	0	1007	0	1197128	0	0 +1186	1198133	0	1005	0	1198133	0	0 +1187	1199138	0	1005	0	1199138	0	0 +1188	1200143	0	1005	0	1200143	0	0 +1189	1201147	0	1004	0	1201147	0	0 +1190	1202151	0	1004	0	1202151	0	0 +1191	1203156	0	1005	0	1203156	0	0 +1192	1204160	0	1004	0	1204160	0	0 +1193	1205164	0	1004	0	1205164	0	0 +1194	1206169	0	1005	0	1206169	0	0 +1195	1207173	0	1004	0	1207173	0	0 +1196	1208177	0	1004	0	1208177	0	0 +1197	1209181	0	1004	0	1209181	0	0 +1198	1210186	0	1005	0	1210186	0	0 +1199	1211190	0	1004	0	1211190	0	0 +1200	1212195	0	1005	0	1212195	0	0 +1201	1213199	0	1004	0	1213199	0	0 +1202	1214204	0	1005	0	1214204	0	0 +1203	1215208	0	1004	0	1215208	0	0 +1204	1216213	0	1005	0	1216213	0	0 +1205	1217218	0	1005	0	1217218	0	0 +1206	1218223	0	1005	0	1218223	0	0 +1207	1219227	0	1004	0	1219227	0	0 +1208	1220232	0	1005	0	1220232	0	0 +1209	1221236	0	1004	0	1221236	0	0 +1210	1222242	0	1006	0	1222242	0	0 +1211	1223246	0	1004	0	1223246	0	0 +1212	1224250	0	1004	0	1224250	0	0 +1213	1225254	0	1004	0	1225254	0	0 +1214	1226258	0	1004	0	1226258	0	0 +1215	1227263	0	1005	0	1227263	0	0 +1216	1228268	0	1005	0	1228268	0	0 +1217	1229273	0	1005	0	1229273	0	0 +1218	1230276	0	1003	0	1230276	0	0 +1219	1231281	0	1005	0	1231281	0	0 +1220	1232284	0	1003	0	1232284	0	0 +1221	1233290	0	1006	0	1233290	0	0 +1222	1234295	0	1005	0	1234295	0	0 +1223	1235300	0	1005	0	1235300	0	0 +1224	1236305	0	1005	0	1236305	0	0 +1225	1237310	0	1005	0	1237310	0	0 +1226	1238314	0	1004	0	1238314	0	0 +1227	1239318	0	1004	0	1239318	0	0 +1228	1240322	0	1004	0	1240322	0	0 +1229	1241326	0	1004	0	1241326	0	0 +1230	1242330	0	1004	0	1242330	0	0 +1231	1243334	0	1004	0	1243334	0	0 +1232	1244339	0	1005	0	1244339	0	0 +1233	1245342	0	1003	0	1245342	0	0 +1234	1246346	0	1004	0	1246346	0	0 +1235	1247350	0	1004	0	1247350	0	0 +1236	1248354	0	1004	0	1248354	0	0 +1237	1249358	0	1004	0	1249358	0	0 +1238	1250363	0	1005	0	1250363	0	0 +1239	1251368	0	1005	0	1251368	0	0 +1240	1252372	0	1004	0	1252372	0	0 +1241	1253376	0	1004	0	1253376	0	0 +1242	1254380	0	1004	0	1254380	0	0 +1243	1255385	0	1005	0	1255385	0	0 +1244	1256390	0	1005	0	1256390	0	0 +1245	1257396	0	1006	0	1257396	0	0 +1246	1258400	0	1004	0	1258400	0	0 +1247	1259404	0	1004	0	1259404	0	0 +1248	1260409	0	1005	0	1260409	0	0 +1249	1261414	0	1005	0	1261414	0	0 +1250	1262418	0	1004	0	1262418	0	0 +1251	1263422	0	1004	0	1263422	0	0 +1252	1264427	0	1005	0	1264427	0	0 +1253	1265430	0	1003	0	1265430	0	0 +1254	1266435	0	1005	0	1266435	0	0 +1255	1267438	0	1003	0	1267438	0	0 +1256	1268441	0	1003	0	1268441	0	0 +1257	1269445	0	1004	0	1269445	0	0 +1258	1270450	0	1005	0	1270450	0	0 +1259	1271454	0	1004	0	1271454	0	0 +1260	1272457	0	1003	0	1272457	0	0 +1261	1273462	0	1005	0	1273462	0	0 +1262	1274467	0	1005	0	1274467	0	0 +1263	1275471	0	1004	0	1275471	0	0 +1264	1276475	0	1004	0	1276475	0	0 +1265	1277478	0	1003	0	1277478	0	0 +1266	1278482	0	1004	0	1278482	0	0 +1267	1279486	0	1004	0	1279486	0	0 +1268	1280490	0	1004	0	1280490	0	0 +1269	1281495	0	1005	0	1281495	0	0 +1270	1282499	0	1004	0	1282499	0	0 +1271	1283504	0	1005	0	1283504	0	0 +1272	1284509	0	1005	0	1284509	0	0 +1273	1285513	0	1004	0	1285513	0	0 +1274	1286518	0	1005	0	1286518	0	0 +1275	1287522	0	1004	0	1287522	0	0 +1276	1288527	0	1005	0	1288527	0	0 +1277	1289532	0	1005	0	1289532	0	0 +1278	1290536	0	1004	0	1290536	0	0 +1279	1291541	0	1005	0	1291541	0	0 +1280	1292546	0	1005	0	1292546	0	0 +1281	1293550	0	1004	0	1293550	0	0 +1282	1294555	0	1005	0	1294555	0	0 +1283	1295560	0	1005	0	1295560	0	0 +1284	1296565	0	1005	0	1296565	0	0 +1285	1297569	0	1004	0	1297569	0	0 +1286	1298574	0	1005	0	1298574	0	0 +1287	1299578	0	1004	0	1299578	0	0 +1288	1300583	0	1005	0	1300583	0	0 +1289	1301588	0	1005	0	1301588	0	0 +1290	1302592	0	1004	0	1302592	0	0 +1291	1303597	0	1005	0	1303597	0	0 +1292	1304601	0	1004	0	1304601	0	0 +1293	1305606	0	1005	0	1305606	0	0 +1294	1306611	0	1005	0	1306611	0	0 +1295	1307616	0	1005	0	1307616	0	0 +1296	1308620	0	1004	0	1308620	0	0 +1297	1309625	0	1005	0	1309625	0	0 +1298	1310629	0	1004	0	1310629	0	0 +1299	1311634	0	1005	0	1311634	0	0 +1300	1312637	0	1003	0	1312637	0	0 +1301	1313642	0	1005	0	1313642	0	0 +1302	1314647	0	1005	0	1314647	0	0 +1303	1315650	0	1003	0	1315650	0	0 +1304	1316655	0	1005	0	1316655	0	0 +1305	1317660	0	1005	0	1317660	0	0 +1306	1318664	0	1004	0	1318664	0	0 +1307	1319669	0	1005	0	1319669	0	0 +1308	1320674	0	1005	0	1320674	0	0 +1309	1321678	0	1004	0	1321678	0	0 +1310	1322683	0	1005	0	1322683	0	0 +1311	1323687	0	1004	0	1323687	0	0 +1312	1324692	0	1005	0	1324692	0	0 +1313	1325697	0	1005	0	1325697	0	0 +1314	1326702	0	1005	0	1326702	0	0 +1315	1327706	0	1004	0	1327706	0	0 +1316	1328711	0	1005	0	1328711	0	0 +1317	1329715	0	1004	0	1329715	0	0 +1318	1330720	0	1005	0	1330720	0	0 +1319	1331725	0	1005	0	1331725	0	0 +1320	1332729	0	1004	0	1332729	0	0 +1321	1333733	0	1004	0	1333733	0	0 +1322	1334739	0	1006	0	1334739	0	0 +1323	1335742	0	1003	0	1335742	0	0 +1324	1336745	0	1003	0	1336745	0	0 +1325	1337750	0	1005	0	1337750	0	0 +1326	1338755	0	1005	0	1338755	0	0 +1327	1339759	0	1004	0	1339759	0	0 +1328	1340764	0	1005	0	1340764	0	0 +1329	1341769	0	1005	0	1341769	0	0 +1330	1342773	0	1004	0	1342773	0	0 +1331	1343778	0	1005	0	1343778	0	0 +1332	1344783	0	1005	0	1344783	0	0 +1333	1345787	0	1004	0	1345787	0	0 +1334	1346792	0	1005	0	1346792	0	0 +1335	1347797	0	1005	0	1347797	0	0 +1336	1348801	0	1004	0	1348801	0	0 +1337	1349806	0	1005	0	1349806	0	0 +1338	1350810	0	1004	0	1350810	0	0 +1339	1351815	0	1005	0	1351815	0	0 +1340	1352819	0	1004	0	1352819	0	0 +1341	1353824	0	1005	0	1353824	0	0 +1342	1354829	0	1005	0	1354829	0	0 +1343	1355834	0	1005	0	1355834	0	0 +1344	1356838	0	1004	0	1356838	0	0 +1345	1357842	0	1004	0	1357842	0	0 +1346	1358847	0	1005	0	1358847	0	0 +1347	1359852	0	1005	0	1359852	0	0 +1348	1360857	0	1005	0	1360857	0	0 +1349	1361860	0	1003	0	1361860	0	0 +1350	1362875	0	1015	0	1362875	0	0 +1351	1363880	0	1005	0	1363880	0	0 +1352	1364885	0	1005	0	1364885	0	0 +1353	1365889	0	1004	0	1365889	0	0 +1354	1366894	0	1005	0	1366894	0	0 +1355	1367899	0	1005	0	1367899	0	0 +1356	1368903	0	1004	0	1368903	0	0 +1357	1369908	0	1005	0	1369908	0	0 +1358	1370912	0	1004	0	1370912	0	0 +1359	1371916	0	1004	0	1371916	0	0 +1360	1372920	0	1004	0	1372920	0	0 +1361	1373925	0	1005	0	1373925	0	0 +1362	1374930	0	1005	0	1374930	0	0 +1363	1375935	0	1005	0	1375935	0	0 +1364	1376939	0	1004	0	1376939	0	0 +1365	1377943	0	1004	0	1377943	0	0 +1366	1378948	0	1005	0	1378948	0	0 +1367	1379953	0	1005	0	1379953	0	0 +1368	1380957	0	1004	0	1380957	0	0 +1369	1381960	0	1003	0	1381960	0	0 +1370	1382965	0	1005	0	1382965	0	0 +1371	1383970	0	1005	0	1383970	0	0 +1372	1384974	0	1004	0	1384974	0	0 +1373	1385978	0	1004	0	1385978	0	0 +1374	1386982	0	1004	0	1386982	0	0 +1375	1387987	0	1005	0	1387987	0	0 +1376	1388992	0	1005	0	1388992	0	0 +1377	1389996	0	1004	0	1389996	0	0 +1378	1391000	0	1004	0	1391000	0	0 +1379	1392005	0	1005	0	1392005	0	0 +1380	1393010	0	1005	0	1393010	0	0 +1381	1394015	0	1005	0	1394015	0	0 +1382	1395020	0	1005	0	1395020	0	0 +1383	1396024	0	1004	0	1396024	0	0 +1384	1397029	0	1005	0	1397029	0	0 +1385	1398034	0	1005	0	1398034	0	0 +1386	1399039	0	1005	0	1399039	0	0 +1387	1400044	0	1005	0	1400044	0	0 +1388	1401048	0	1004	0	1401048	0	0 +1389	1402053	0	1005	0	1402053	0	0 +1390	1403058	0	1005	0	1403058	0	0 +1391	1404062	0	1004	0	1404062	0	0 +1392	1405067	0	1005	0	1405067	0	0 +1393	1406073	0	1006	0	1406073	0	0 +1394	1407078	0	1005	0	1407078	0	0 +1395	1408082	0	1004	0	1408082	0	0 +1396	1409087	0	1005	0	1409087	0	0 +1397	1410091	0	1004	0	1410091	0	0 +1398	1411096	0	1005	0	1411096	0	0 +1399	1412101	0	1005	0	1412101	0	0 +1400	1413105	0	1004	0	1413105	0	0 +1401	1414110	0	1005	0	1414110	0	0 +1402	1415119	0	1009	0	1415119	0	0 +1403	1416125	0	1006	0	1416125	0	0 +1404	1417130	0	1005	0	1417130	0	0 +1405	1418135	0	1005	0	1418135	0	0 +1406	1419141	0	1006	0	1419141	0	0 +1407	1420146	0	1005	0	1420146	0	0 +1408	1421150	0	1004	0	1421150	0	0 +1409	1422156	0	1006	0	1422156	0	0 +1410	1423160	0	1004	0	1423160	0	0 +1411	1424165	0	1005	0	1424165	0	0 +1412	1425170	0	1005	0	1425170	0	0 +1413	1426175	0	1005	0	1426175	0	0 +1414	1427180	0	1005	0	1427180	0	0 +1415	1428185	0	1005	0	1428185	0	0 +1416	1429188	0	1003	0	1429188	0	0 +1417	1430192	0	1004	0	1430192	0	0 +1418	1431197	0	1005	0	1431197	0	0 +1419	1432202	0	1005	0	1432202	0	0 +1420	1433205	0	1003	0	1433205	0	0 +1421	1434209	0	1004	0	1434209	0	0 +1422	1435214	0	1005	0	1435214	0	0 +1423	1436219	0	1005	0	1436219	0	0 +1424	1437223	0	1004	0	1437223	0	0 +1425	1438227	0	1004	0	1438227	0	0 +1426	1439231	0	1004	0	1439231	0	0 +1427	1440235	0	1004	0	1440235	0	0 +1428	1441240	0	1005	0	1441240	0	0 +1429	1442243	0	1003	0	1442243	0	0 +1430	1443248	0	1005	0	1443248	0	0 +1431	1444251	0	1003	0	1444251	0	0 +1432	1445256	0	1005	0	1445256	0	0 +1433	1446261	0	1005	0	1446261	0	0 +1434	1447266	0	1005	0	1447266	0	0 +1435	1448271	0	1005	0	1448271	0	0 +1436	1449275	0	1004	0	1449275	0	0 +1437	1450280	0	1005	0	1450280	0	0 +1438	1451285	0	1005	0	1451285	0	0 +1439	1452289	0	1004	0	1452289	0	0 +1440	1453294	0	1005	0	1453294	0	0 +1441	1454298	0	1004	0	1454298	0	0 +1442	1455301	0	1003	0	1455301	0	0 +1443	1456305	0	1004	0	1456305	0	0 +1444	1457311	0	1006	0	1457311	0	0 +1445	1458315	0	1004	0	1458315	0	0 +1446	1459321	0	1006	0	1459321	0	0 +1447	1460326	0	1005	0	1460326	0	0 +1448	1461330	0	1004	0	1461330	0	0 +1449	1462336	0	1006	0	1462336	0	0 +1450	1463340	0	1004	0	1463340	0	0 +1451	1464344	0	1004	0	1464344	0	0 +1452	1465349	0	1005	0	1465349	0	0 +1453	1466355	0	1006	0	1466355	0	0 +1454	1467361	0	1006	0	1467361	0	0 +1455	1468365	0	1004	0	1468365	0	0 +1456	1469370	0	1005	0	1469370	0	0 +1457	1470374	0	1004	0	1470374	0	0 +1458	1471379	0	1005	0	1471379	0	0 +1459	1472384	0	1005	0	1472384	0	0 +1460	1473388	0	1004	0	1473388	0	0 +1461	1474393	0	1005	0	1474393	0	0 +1462	1475397	0	1004	0	1475397	0	0 +1463	1476402	0	1005	0	1476402	0	0 +1464	1477407	0	1005	0	1477407	0	0 +1465	1478411	0	1004	0	1478411	0	0 +1466	1479416	0	1005	0	1479416	0	0 +1467	1480421	0	1005	0	1480421	0	0 +1468	1481425	0	1004	0	1481425	0	0 +1469	1482430	0	1005	0	1482430	0	0 +1470	1483435	0	1005	0	1483435	0	0 +1471	1484440	0	1005	0	1484440	0	0 +1472	1485443	0	1003	0	1485443	0	0 +1473	1486447	0	1004	0	1486447	0	0 +1474	1487450	0	1003	0	1487450	0	0 +1475	1488453	0	1003	0	1488453	0	0 +1476	1489457	0	1004	0	1489457	0	0 +1477	1490462	0	1005	0	1490462	0	0 +1478	1491467	0	1005	0	1491467	0	0 +1479	1492472	0	1005	0	1492472	0	0 +1480	1493477	0	1005	0	1493477	0	0 +1481	1494481	0	1004	0	1494481	0	0 +1482	1495486	0	1005	0	1495486	0	0 +1483	1496491	0	1005	0	1496491	0	0 +1484	1497495	0	1004	0	1497495	0	0 +1485	1498500	0	1005	0	1498500	0	0 +1486	1499504	0	1004	0	1499504	0	0 +1487	1500509	0	1005	0	1500509	0	0 +1488	1501514	0	1005	0	1501514	0	0 +1489	1502519	0	1005	0	1502519	0	0 +1490	1503523	0	1004	0	1503523	0	0 +1491	1504528	0	1005	0	1504528	0	0 +1492	1505533	0	1005	0	1505533	0	0 +1493	1506536	0	1003	0	1506536	0	0 +1494	1507903	0	1367	0	1507903	0	0 +1495	1508907	0	1004	0	1508907	0	0 +1496	1509911	0	1004	0	1509911	0	0 +1497	1510916	0	1005	0	1510916	0	0 +1498	1512087	0	1171	0	1512087	0	0 +1499	1513091	0	1004	0	1513091	0	0 +1500	1514095	0	1004	0	1514095	0	0 +1501	1515099	0	1004	0	1515099	0	0 +1502	1516104	0	1005	0	1516104	0	0 +1503	1517110	0	1006	0	1517110	0	0 +1504	1518117	0	1007	0	1518117	0	0 +1505	1519122	0	1005	0	1519122	0	0 +1506	1520127	0	1005	0	1520127	0	0 +1507	1521132	0	1005	0	1521132	0	0 +1508	1522135	0	1003	0	1522135	0	0 +1509	1523141	0	1006	0	1523141	0	0 +1510	1524147	0	1006	0	1524147	0	0 +1511	1525152	0	1005	0	1525152	0	0 +1512	1526157	0	1005	0	1526157	0	0 +1513	1527161	0	1004	0	1527161	0	0 +1514	1528166	0	1005	0	1528166	0	0 +1515	1529171	0	1005	0	1529171	0	0 +1516	1530176	0	1005	0	1530176	0	0 +1517	1531180	0	1004	0	1531180	0	0 +1518	1532185	0	1005	0	1532185	0	0 +1519	1533190	0	1005	0	1533190	0	0 +1520	1534194	0	1004	0	1534194	0	0 +1521	1535198	0	1004	0	1535198	0	0 +1522	1536203	0	1005	0	1536203	0	0 +1523	1537208	0	1005	0	1537208	0	0 +1524	1538213	0	1005	0	1538213	0	0 +1525	1539218	0	1005	0	1539218	0	0 +1526	1540223	0	1005	0	1540223	0	0 +1527	1541227	0	1004	0	1541227	0	0 +1528	1542231	0	1004	0	1542231	0	0 +1529	1543236	0	1005	0	1543236	0	0 +1530	1544241	0	1005	0	1544241	0	0 +1531	1545246	0	1005	0	1545246	0	0 +1532	1546250	0	1004	0	1546250	0	0 +1533	1547254	0	1004	0	1547254	0	0 +1534	1548259	0	1005	0	1548259	0	0 +1535	1549264	0	1005	0	1549264	0	0 +1536	1550268	0	1004	0	1550268	0	0 +1537	1551272	0	1004	0	1551272	0	0 +1538	1552275	0	1003	0	1552275	0	0 +1539	1553280	0	1005	0	1553280	0	0 +1540	1554284	0	1004	0	1554284	0	0 +1541	1555288	0	1004	0	1555288	0	0 +1542	1556292	0	1004	0	1556292	0	0 +1543	1557296	0	1004	0	1557296	0	0 +1544	1558300	0	1004	0	1558300	0	0 +1545	1559304	0	1004	0	1559304	0	0 +1546	1560308	0	1004	0	1560308	0	0 +1547	1561312	0	1004	0	1561312	0	0 +1548	1562317	0	1005	0	1562317	0	0 +1549	1563322	0	1005	0	1563322	0	0 +1550	1564326	0	1004	0	1564326	0	0 +1551	1565331	0	1005	0	1565331	0	0 +1552	1566335	0	1004	0	1566335	0	0 +1553	1567340	0	1005	0	1567340	0	0 +1554	1568345	0	1005	0	1568345	0	0 +1555	1569351	0	1006	0	1569351	0	0 +1556	1570356	0	1005	0	1570356	0	0 +1557	1571360	0	1004	0	1571360	0	0 +1558	1572364	0	1004	0	1572364	0	0 +1559	1573368	0	1004	0	1573368	0	0 +1560	1574371	0	1003	0	1574371	0	0 +1561	1575376	0	1005	0	1575376	0	0 +1562	1576380	0	1004	0	1576380	0	0 +1563	1577384	0	1004	0	1577384	0	0 +1564	1578387	0	1003	0	1578387	0	0 +1565	1579392	0	1005	0	1579392	0	0 +1566	1580397	0	1005	0	1580397	0	0 +1567	1581402	0	1005	0	1581402	0	0 +1568	1582406	0	1004	0	1582406	0	0 +1569	1583412	0	1006	0	1583412	0	0 +1570	1584415	0	1003	0	1584415	0	0 +1571	1585419	0	1004	0	1585419	0	0 +1572	1586423	0	1004	0	1586423	0	0 +1573	1587427	0	1004	0	1587427	0	0 +1574	1588432	0	1005	0	1588432	0	0 +1575	1589436	0	1004	0	1589436	0	0 +1576	1590441	0	1005	0	1590441	0	0 +1577	1591445	0	1004	0	1591445	0	0 +1578	1592449	0	1004	0	1592449	0	0 +1579	1593452	0	1003	0	1593452	0	0 +1580	1594457	0	1005	0	1594457	0	0 +1581	1595461	0	1004	0	1595461	0	0 +1582	1596466	0	1005	0	1596466	0	0 +1583	1597469	0	1003	0	1597469	0	0 +1584	1598473	0	1004	0	1598473	0	0 +1585	1599476	0	1003	0	1599476	0	0 +1586	1600480	0	1004	0	1600480	0	0 +1587	1601484	0	1004	0	1601484	0	0 +1588	1602487	0	1003	0	1602487	0	0 +1589	1603492	0	1005	0	1603492	0	0 +1590	1604497	0	1005	0	1604497	0	0 +1591	1605502	0	1005	0	1605502	0	0 +1592	1606506	0	1004	0	1606506	0	0 +1593	1607511	0	1005	0	1607511	0	0 +1594	1608515	0	1004	0	1608515	0	0 +1595	1609520	0	1005	0	1609520	0	0 +1596	1610525	0	1005	0	1610525	0	0 +1597	1611530	0	1005	0	1611530	0	0 +1598	1612535	0	1005	0	1612535	0	0 +1599	1613540	0	1005	0	1613540	0	0 +1600	1614545	0	1005	0	1614545	0	0 +1601	1615550	0	1005	0	1615550	0	0 +1602	1616554	0	1004	0	1616554	0	0 +1603	1617559	0	1005	0	1617559	0	0 +1604	1618564	0	1005	0	1618564	0	0 +1605	1619569	0	1005	0	1619569	0	0 +1606	1620573	0	1004	0	1620573	0	0 +1607	1621576	0	1003	0	1621576	0	0 +1608	1622579	0	1003	0	1622579	0	0 +1609	1623582	0	1003	0	1623582	0	0 +1610	1624587	0	1005	0	1624587	0	0 +1611	1625591	0	1004	0	1625591	0	0 +1612	1626596	0	1005	0	1626596	0	0 +1613	1627601	0	1005	0	1627601	0	0 +1614	1628606	0	1005	0	1628606	0	0 +1615	1629610	0	1004	0	1629610	0	0 +1616	1630614	0	1004	0	1630614	0	0 +1617	1631619	0	1005	0	1631619	0	0 +1618	1632625	0	1006	0	1632625	0	0 +1619	1633628	0	1003	0	1633628	0	0 +1620	1634631	0	1003	0	1634631	0	0 +1621	1635636	0	1005	0	1635636	0	0 +1622	1636639	0	1003	0	1636639	0	0 +1623	1637643	0	1004	0	1637643	0	0 +1624	1638648	0	1005	0	1638648	0	0 +1625	1639652	0	1004	0	1639652	0	0 +1626	1640656	0	1004	0	1640656	0	0 +1627	1641659	0	1003	0	1641659	0	0 +1628	1642664	0	1005	0	1642664	0	0 +1629	1643668	0	1004	0	1643668	0	0 +1630	1644672	0	1004	0	1644672	0	0 +1631	1645678	0	1006	0	1645678	0	0 +1632	1646682	0	1004	0	1646682	0	0 +1633	1647687	0	1005	0	1647687	0	0 +1634	1648691	0	1004	0	1648691	0	0 +1635	1649696	0	1005	0	1649696	0	0 +1636	1650701	0	1005	0	1650701	0	0 +1637	1651706	0	1005	0	1651706	0	0 +1638	1652711	0	1005	0	1652711	0	0 +1639	1653714	0	1003	0	1653714	0	0 +1640	1654720	0	1006	0	1654720	0	0 +1641	1655724	0	1004	0	1655724	0	0 +1642	1656728	0	1004	0	1656728	0	0 +1643	1657733	0	1005	0	1657733	0	0 +1644	1658738	0	1005	0	1658738	0	0 +1645	1659743	0	1005	0	1659743	0	0 +1646	1660747	0	1004	0	1660747	0	0 +1647	1661752	0	1005	0	1661752	0	0 +1648	1662757	0	1005	0	1662757	0	0 +1649	1663761	0	1004	0	1663761	0	0 +1650	1664766	0	1005	0	1664766	0	0 +1651	1665771	0	1005	0	1665771	0	0 +1652	1666775	0	1004	0	1666775	0	0 +1653	1667828	0	1053	0	1667828	0	0 +1654	1668832	0	1004	0	1668832	0	0 +1655	1669836	0	1004	0	1669836	0	0 +1656	1670840	0	1004	0	1670840	0	0 +1657	1671845	0	1005	0	1671845	0	0 +1658	1672850	0	1005	0	1672850	0	0 +1659	1673854	0	1004	0	1673854	0	0 +1660	1674858	0	1004	0	1674858	0	0 +1661	1675872	0	1014	0	1675872	0	0 +1662	1676875	0	1003	0	1676875	0	0 +1663	1677880	0	1005	0	1677880	0	0 +1664	1678891	0	1011	0	1678891	0	0 +1665	1679896	0	1005	0	1679896	0	0 +1666	1680901	0	1005	0	1680901	0	0 +1667	1681905	0	1004	0	1681905	0	0 +1668	1682910	0	1005	0	1682910	0	0 +1669	1683914	0	1004	0	1683914	0	0 +1670	1684919	0	1005	0	1684919	0	0 +1671	1685924	0	1005	0	1685924	0	0 +1672	1686928	0	1004	0	1686928	0	0 +1673	1687933	0	1005	0	1687933	0	0 +1674	1688938	0	1005	0	1688938	0	0 +1675	1689942	0	1004	0	1689942	0	0 +1676	1690947	0	1005	0	1690947	0	0 +1677	1691952	0	1005	0	1691952	0	0 +1678	1692956	0	1004	0	1692956	0	0 +1679	1693961	0	1005	0	1693961	0	0 +1680	1694965	0	1004	0	1694965	0	0 +1681	1695970	0	1005	0	1695970	0	0 +1682	1696975	0	1005	0	1696975	0	0 +1683	1697979	0	1004	0	1697979	0	0 +1684	1698984	0	1005	0	1698984	0	0 +1685	1699989	0	1005	0	1699989	0	0 +1686	1700993	0	1004	0	1700993	0	0 +1687	1701998	0	1005	0	1701998	0	0 +1688	1703003	0	1005	0	1703003	0	0 +1689	1704007	0	1004	0	1704007	0	0 +1690	1705012	0	1005	0	1705012	0	0 +1691	1706015	0	1003	0	1706015	0	0 +1692	1707020	0	1005	0	1707020	0	0 +1693	1708024	0	1004	0	1708024	0	0 +1694	1709027	0	1003	0	1709027	0	0 +1695	1710032	0	1005	0	1710032	0	0 +1696	1711038	0	1006	0	1711038	0	0 +1697	1712042	0	1004	0	1712042	0	0 +1698	1713046	0	1004	0	1713046	0	0 +1699	1714050	0	1004	0	1714050	0	0 +1700	1715055	0	1005	0	1715055	0	0 +1701	1716060	0	1005	0	1716060	0	0 +1702	1717065	0	1005	0	1717065	0	0 +1703	1718068	0	1003	0	1718068	0	0 +1704	1719073	0	1005	0	1719073	0	0 +1705	1720077	0	1004	0	1720077	0	0 +1706	1721081	0	1004	0	1721081	0	0 +1707	1722087	0	1006	0	1722087	0	0 +1708	1723092	0	1005	0	1723092	0	0 +1709	1724096	0	1004	0	1724096	0	0 +1710	1725100	0	1004	0	1725100	0	0 +1711	1726105	0	1005	0	1726105	0	0 +1712	1727109	0	1004	0	1727109	0	0 +1713	1728113	0	1004	0	1728113	0	0 +1714	1729119	0	1006	0	1729119	0	0 +1715	1730125	0	1006	0	1730125	0	0 +1716	1731130	0	1005	0	1731130	0	0 +1717	1732135	0	1005	0	1732135	0	0 +1718	1733141	0	1006	0	1733141	0	0 +1719	1734145	0	1004	0	1734145	0	0 +1720	1735149	0	1004	0	1735149	0	0 +1721	1736155	0	1006	0	1736155	0	0 +1722	1737159	0	1004	0	1737159	0	0 +1723	1738164	0	1005	0	1738164	0	0 +1724	1739168	0	1004	0	1739168	0	0 +1725	1740172	0	1004	0	1740172	0	0 +1726	1741177	0	1005	0	1741177	0	0 +1727	1742180	0	1003	0	1742180	0	0 +1728	1743185	0	1005	0	1743185	0	0 +1729	1744189	0	1004	0	1744189	0	0 +1730	1745192	0	1003	0	1745192	0	0 +1731	1746196	0	1004	0	1746196	0	0 +1732	1747201	0	1005	0	1747201	0	0 +1733	1748206	0	1005	0	1748206	0	0 +1734	1749211	0	1005	0	1749211	0	0 +1735	1750216	0	1005	0	1750216	0	0 +1736	1751221	0	1005	0	1751221	0	0 +1737	1752225	0	1004	0	1752225	0	0 +1738	1753231	0	1006	0	1753231	0	0 +1739	1754235	0	1004	0	1754235	0	0 +1740	1755240	0	1005	0	1755240	0	0 +1741	1756244	0	1004	0	1756244	0	0 +1742	1757249	0	1005	0	1757249	0	0 +1743	1758254	0	1005	0	1758254	0	0 +1744	1759258	0	1004	0	1759258	0	0 +1745	1760263	0	1005	0	1760263	0	0 +1746	1761268	0	1005	0	1761268	0	0 +1747	1762272	0	1004	0	1762272	0	0 +1748	1763276	0	1004	0	1763276	0	0 +1749	1764280	0	1004	0	1764280	0	0 +1750	1765285	0	1005	0	1765285	0	0 +1751	1766290	0	1005	0	1766290	0	0 +1752	1767295	0	1005	0	1767295	0	0 +1753	1768300	0	1005	0	1768300	0	0 +1754	1769305	0	1005	0	1769305	0	0 +1755	1770309	0	1004	0	1770309	0	0 +1756	1771313	0	1004	0	1771313	0	0 +1757	1772318	0	1005	0	1772318	0	0 +1758	1773323	0	1005	0	1773323	0	0 +1759	1774328	0	1005	0	1774328	0	0 +1760	1775332	0	1004	0	1775332	0	0 +1761	1776337	0	1005	0	1776337	0	0 +1762	1777341	0	1004	0	1777341	0	0 +1763	1778346	0	1005	0	1778346	0	0 +1764	1779352	0	1006	0	1779352	0	0 +1765	1780355	0	1003	0	1780355	0	0 +1766	1781360	0	1005	0	1781360	0	0 +1767	1782366	0	1006	0	1782366	0	0 +1768	1783370	0	1004	0	1783370	0	0 +1769	1784374	0	1004	0	1784374	0	0 +1770	1785379	0	1005	0	1785379	0	0 +1771	1786384	0	1005	0	1786384	0	0 +1772	1787389	0	1005	0	1787389	0	0 +1773	1788394	0	1005	0	1788394	0	0 +1774	1789398	0	1004	0	1789398	0	0 +1775	1790402	0	1004	0	1790402	0	0 +1776	1791406	0	1004	0	1791406	0	0 +1777	1792410	0	1004	0	1792410	0	0 +1778	1793414	0	1004	0	1793414	0	0 +1779	1794419	0	1005	0	1794419	0	0 +1780	1795424	0	1005	0	1795424	0	0 +1781	1796429	0	1005	0	1796429	0	0 +1782	1797433	0	1004	0	1797433	0	0 +1783	1798436	0	1003	0	1798436	0	0 +1784	1799441	0	1005	0	1799441	0	0 +1785	1800445	0	1004	0	1800445	0	0 +1786	1801448	0	1003	0	1801448	0	0 +1787	1802453	0	1005	0	1802453	0	0 +1788	1803457	0	1004	0	1803457	0	0 +1789	1804462	0	1005	0	1804462	0	0 +1790	1805467	0	1005	0	1805467	0	0 +1791	1806471	0	1004	0	1806471	0	0 +1792	1807476	0	1005	0	1807476	0	0 +1793	1808479	0	1003	0	1808479	0	0 +1794	1809483	0	1004	0	1809483	0	0 +1795	1810488	0	1005	0	1810488	0	0 +1796	1811492	0	1004	0	1811492	0	0 +1797	1812497	0	1005	0	1812497	0	0 +1798	1813502	0	1005	0	1813502	0	0 +1799	1814506	0	1004	0	1814506	0	0 +1800	1815511	0	1005	0	1815511	0	0 +1801	1816516	0	1005	0	1816516	0	0 +1802	1817520	0	1004	0	1817520	0	0 +1803	1818525	0	1005	0	1818525	0	0 +1804	1819529	0	1004	0	1819529	0	0 +1805	1820534	0	1005	0	1820534	0	0 +1806	1821539	0	1005	0	1821539	0	0 +1807	1822544	0	1005	0	1822544	0	0 +1808	1823549	0	1005	0	1823549	0	0 +1809	1824552	0	1003	0	1824552	0	0 +1810	1825557	0	1005	0	1825557	0	0 +1811	1826561	0	1004	0	1826561	0	0 +1812	1827565	0	1004	0	1827565	0	0 +1813	1828569	0	1004	0	1828569	0	0 +1814	1829573	0	1004	0	1829573	0	0 +1815	1830578	0	1005	0	1830578	0	0 +1816	1831583	0	1005	0	1831583	0	0 +1817	1832588	0	1005	0	1832588	0	0 +1818	1833592	0	1004	0	1833592	0	0 +1819	1834597	0	1005	0	1834597	0	0 +1820	1835602	0	1005	0	1835602	0	0 +1821	1836607	0	1005	0	1836607	0	0 +1822	1837611	0	1004	0	1837611	0	0 +1823	1838616	0	1005	0	1838616	0	0 +1824	1839621	0	1005	0	1839621	0	0 +1825	1840625	0	1004	0	1840625	0	0 +1826	1841630	0	1005	0	1841630	0	0 +1827	1842634	0	1004	0	1842634	0	0 +1828	1843638	0	1004	0	1843638	0	0 +1829	1844642	0	1004	0	1844642	0	0 +1830	1845647	0	1005	0	1845647	0	0 +1831	1846651	0	1004	0	1846651	0	0 +1832	1847655	0	1004	0	1847655	0	0 +1833	1848659	0	1004	0	1848659	0	0 +1834	1849664	0	1005	0	1849664	0	0 +1835	1850668	0	1004	0	1850668	0	0 +1836	1851673	0	1005	0	1851673	0	0 +1837	1852678	0	1005	0	1852678	0	0 +1838	1853682	0	1004	0	1853682	0	0 +1839	1854686	0	1004	0	1854686	0	0 +1840	1855691	0	1005	0	1855691	0	0 +1841	1856695	0	1004	0	1856695	0	0 +1842	1857698	0	1003	0	1857698	0	0 +1843	1858703	0	1005	0	1858703	0	0 +1844	1859706	0	1003	0	1859706	0	0 +1845	1860710	0	1004	0	1860710	0	0 +1846	1861714	0	1004	0	1861714	0	0 +1847	1862718	0	1004	0	1862718	0	0 +1848	1863723	0	1005	0	1863723	0	0 +1849	1864726	0	1003	0	1864726	0	0 +1850	1865730	0	1004	0	1865730	0	0 +1851	1866734	0	1004	0	1866734	0	0 +1852	1867738	0	1004	0	1867738	0	0 +1853	1868742	0	1004	0	1868742	0	0 +1854	1869746	0	1004	0	1869746	0	0 +1855	1870751	0	1005	0	1870751	0	0 +1856	1871755	0	1004	0	1871755	0	0 +1857	1872759	0	1004	0	1872759	0	0 +1858	1873762	0	1003	0	1873762	0	0 +1859	1874767	0	1005	0	1874767	0	0 +1860	1875770	0	1003	0	1875770	0	0 +1861	1876774	0	1004	0	1876774	0	0 +1862	1877777	0	1003	0	1877777	0	0 +1863	1878780	0	1003	0	1878780	0	0 +1864	1879785	0	1005	0	1879785	0	0 +1865	1880790	0	1005	0	1880790	0	0 +1866	1881794	0	1004	0	1881794	0	0 +1867	1882799	0	1005	0	1882799	0	0 +1868	1883803	0	1004	0	1883803	0	0 +1869	1884808	0	1005	0	1884808	0	0 +1870	1885813	0	1005	0	1885813	0	0 +1871	1886817	0	1004	0	1886817	0	0 +1872	1887822	0	1005	0	1887822	0	0 +1873	1888827	0	1005	0	1888827	0	0 +1874	1889831	0	1004	0	1889831	0	0 +1875	1890836	0	1005	0	1890836	0	0 +1876	1891841	0	1005	0	1891841	0	0 +1877	1892845	0	1004	0	1892845	0	0 +1878	1893850	0	1005	0	1893850	0	0 +1879	1894854	0	1004	0	1894854	0	0 +1880	1895859	0	1005	0	1895859	0	0 +1881	1896872	0	1013	0	1896872	0	0 +1882	1897875	0	1003	0	1897875	0	0 +1883	1898889	0	1014	0	1898889	0	0 +1884	1899894	0	1005	0	1899894	0	0 +1885	1900898	0	1004	0	1900898	0	0 +1886	1901902	0	1004	0	1901902	0	0 +1887	1902906	0	1004	0	1902906	0	0 +1888	1903910	0	1004	0	1903910	0	0 +1889	1904915	0	1005	0	1904915	0	0 +1890	1906038	0	1123	0	1906038	0	0 +1891	1907043	0	1005	0	1907043	1	1 +1892	1908048	0	1005	0	1908048	0	1 +1893	1909053	0	1005	0	1909053	0	1 +1894	1910058	0	1005	0	1910058	0	1 +1895	1911063	0	1005	0	1911063	0	1 +1896	1912068	0	1005	0	1912068	0	1 +1897	1913072	0	1004	0	1913072	0	1 +1898	1914077	0	1005	0	1914077	0	1 +1899	1915082	0	1005	0	1915082	0	1 +1900	1916087	0	1005	0	1916087	0	1 +1901	1917090	0	1003	0	1917090	0	1 +1902	1918095	0	1005	0	1918095	0	1 +1903	1919100	0	1005	0	1919100	0	1 +1904	1920104	0	1004	0	1920104	0	1 +1905	1921107	0	1003	0	1921107	0	1 +1906	1922110	0	1003	0	1922110	0	1 +1907	1923119	0	1009	0	1923119	0	1 +1908	1924124	0	1005	0	1924124	0	1 +1909	1925129	0	1005	0	1925129	0	1 +1910	1926133	0	1004	0	1926133	0	1 +1911	1927139	0	1006	0	1927139	0	1 +1912	1928143	0	1004	0	1928143	0	1 +1913	1929149	0	1006	0	1929149	0	1 +1914	1930155	0	1006	0	1930155	0	1 +1915	1931160	0	1005	0	1931160	0	1 +1916	1932165	0	1005	0	1932165	0	1 +1917	1933170	0	1005	0	1933170	0	1 +1918	1934175	0	1005	0	1934175	0	1 +1919	1935181	0	1006	0	1935181	0	1 +1920	1936186	0	1005	0	1936186	0	1 +1921	1937190	0	1004	0	1937190	0	1 +1922	1938194	0	1004	0	1938194	0	1 +1923	1939200	0	1006	0	1939200	0	1 +1924	1940205	0	1005	0	1940205	0	1 +1925	1941210	0	1005	0	1941210	0	1 +1926	1942214	0	1004	0	1942214	0	1 +1927	1943218	0	1004	0	1943218	0	1 +1928	1944222	0	1004	0	1944222	0	1 +1929	1945225	0	1003	0	1945225	0	1 +1930	1946230	0	1005	0	1946230	0	1 +1931	1947235	0	1005	0	1947235	0	1 +1932	1948240	0	1005	0	1948240	0	1 +1933	1949243	0	1003	0	1949243	0	1 +1934	1950246	0	1003	0	1950246	0	1 +1935	1951250	0	1004	0	1951250	0	1 +1936	1952253	0	1003	0	1952253	0	1 +1937	1953256	0	1003	0	1953256	0	1 +1938	1954260	0	1004	0	1954260	0	1 +1939	1955264	0	1004	0	1955264	0	1 +1940	1956269	0	1005	0	1956269	0	1 +1941	1957274	0	1005	0	1957274	0	1 +1942	1958279	0	1005	0	1958279	0	1 +1943	1959284	0	1005	0	1959284	0	1 +1944	1960287	0	1003	0	1960287	0	1 +1945	1961290	0	1003	0	1961290	0	1 +1946	1962294	0	1004	0	1962294	0	1 +1947	1963298	0	1004	0	1963298	0	1 +1948	1964302	0	1004	0	1964302	0	1 +1949	1965305	0	1003	0	1965305	0	1 +1950	1966310	0	1005	0	1966310	0	1 +1951	1967315	0	1005	0	1967315	0	1 +1952	1968318	0	1003	0	1968318	0	1 +1953	1969321	0	1003	0	1969321	0	1 +1954	1970325	0	1004	0	1970325	0	1 +1955	1971329	0	1004	0	1971329	0	1 +1956	1972334	0	1005	0	1972334	0	1 +1957	1973339	0	1005	0	1973339	0	1 +1958	1974344	0	1005	0	1974344	0	1 +1959	1975348	0	1004	0	1975348	0	1 +1960	1976353	0	1005	0	1976353	0	1 +1961	1977357	0	1004	0	1977357	0	1 +1962	1978362	0	1005	0	1978362	0	1 +1963	1979367	0	1005	0	1979367	0	1 +1964	1980371	0	1004	0	1980371	0	1 +1965	1981375	0	1004	0	1981375	0	1 +1966	1982379	0	1004	0	1982379	0	1 +1967	1983384	0	1005	0	1983384	0	1 +1968	1984388	0	1004	0	1984388	0	1 +1969	1985392	0	1004	0	1985392	0	1 +1970	1986397	0	1005	0	1986397	0	1 +1971	1987401	0	1004	0	1987401	0	1 +1972	1988405	0	1004	0	1988405	0	1 +1973	1989409	0	1004	0	1989409	0	1 +1974	1990414	0	1005	0	1990414	0	1 +1975	1991417	0	1003	0	1991417	0	1 +1976	1992421	0	1004	0	1992421	0	1 +1977	1993426	0	1005	0	1993426	0	1 +1978	1994431	0	1005	0	1994431	0	1 +1979	1995437	0	1006	0	1995437	0	1 +1980	1996443	0	1006	0	1996443	0	1 +1981	1997448	0	1005	0	1997448	0	1 +1982	1998452	0	1004	0	1998452	0	1 +1983	1999457	0	1005	0	1999457	0	1 +1984	2000461	0	1004	0	2000461	0	1 +1985	2001466	0	1005	0	2001466	0	1 +1986	2002471	0	1005	0	2002471	0	1 +1987	2003476	0	1005	0	2003476	0	1 +1988	2004480	0	1004	0	2004480	0	1 +1989	2005485	0	1005	0	2005485	0	1 +1990	2006490	0	1005	0	2006490	0	1 +1991	2007494	0	1004	0	2007494	0	1 +1992	2008498	0	1004	0	2008498	0	1 +1993	2009502	0	1004	0	2009502	0	1 +1994	2010507	0	1005	0	2010507	0	1 +1995	2011511	0	1004	0	2011511	0	1 +1996	2012515	0	1004	0	2012515	0	1 +1997	2013520	0	1005	0	2013520	0	1 +1998	2014524	0	1004	0	2014524	0	1 +1999	2015528	0	1004	0	2015528	0	1 +2000	2016532	0	1004	0	2016532	0	1 +2001	2017537	0	1005	0	2017537	0	1 +2002	2018541	0	1004	0	2018541	0	1 +2003	2019545	0	1004	0	2019545	0	1 +2004	2020548	0	1003	0	2020548	0	1 +2005	2021553	0	1005	0	2021553	0	1 +2006	2022557	0	1004	0	2022557	0	1 +2007	2023562	0	1005	0	2023562	0	1 +2008	2024565	0	1003	0	2024565	0	1 +2009	2025569	0	1004	0	2025569	0	1 +2010	2026573	0	1004	0	2026573	0	1 +2011	2027581	0	1008	0	2027581	0	1 +2012	2028586	0	1005	0	2028586	0	1 +2013	2029590	0	1004	0	2029590	0	1 +2014	2030595	0	1005	0	2030595	0	1 +2015	2031600	0	1005	0	2031600	0	1 +2016	2032604	0	1004	0	2032604	0	1 +2017	2033609	0	1005	0	2033609	0	1 +2018	2034613	0	1004	0	2034613	0	1 +2019	2035617	0	1004	0	2035617	0	1 +2020	2036620	0	1003	0	2036620	0	1 +2021	2037623	0	1003	0	2037623	0	1 +2022	2038627	0	1004	0	2038627	0	1 +2023	2039631	0	1004	0	2039631	0	1 +2024	2040635	0	1004	0	2040635	0	1 +2025	2041639	0	1004	0	2041639	0	1 +2026	2042644	0	1005	0	2042644	0	1 +2027	2043648	0	1004	0	2043648	0	1 +2028	2044654	0	1006	0	2044654	0	1 +2029	2045657	0	1003	0	2045657	0	1 +2030	2046661	0	1004	0	2046661	0	1 +2031	2047664	0	1003	0	2047664	0	1 +2032	2048669	0	1005	0	2048669	0	1 +2033	2049674	0	1005	0	2049674	0	1 +2034	2050679	0	1005	0	2050679	0	1 +2035	2051683	0	1004	0	2051683	0	1 +2036	2052687	0	1004	0	2052687	0	1 +2037	2053692	0	1005	0	2053692	0	1 +2038	2054699	0	1007	0	2054699	0	1 +2039	2055702	0	1003	0	2055702	0	1 +2040	2056706	0	1004	0	2056706	0	1 +2041	2057711	0	1005	0	2057711	0	1 +2042	2058715	0	1004	0	2058715	0	1 +2043	2059720	0	1005	0	2059720	0	1 +2044	2060725	0	1005	0	2060725	0	1 +2045	2061730	0	1005	0	2061730	0	1 +2046	2062733	0	1003	0	2062733	0	1 +2047	2063738	0	1005	0	2063738	0	1 +2048	2064743	0	1005	0	2064743	0	1 +2049	2065748	0	1005	0	2065748	0	1 +2050	2066752	0	1004	0	2066752	0	1 +2051	2067757	0	1005	0	2067757	0	1 +2052	2068762	0	1005	0	2068762	0	1 +2053	2069767	0	1005	0	2069767	0	1 +2054	2070771	0	1004	0	2070771	0	1 +2055	2071776	0	1005	0	2071776	0	1 +2056	2072780	0	1004	0	2072780	0	1 +2057	2073785	0	1005	0	2073785	0	1 +2058	2074789	0	1004	0	2074789	0	1 +2059	2075794	0	1005	0	2075794	0	1 +2060	2076798	0	1004	0	2076798	0	1 +2061	2077802	0	1004	0	2077802	0	1 +2062	2078806	0	1004	0	2078806	0	1 +2063	2079809	0	1003	0	2079809	0	1 +2064	2080812	0	1003	0	2080812	0	1 +2065	2081817	0	1005	0	2081817	0	1 +2066	2082822	0	1005	0	2082822	0	1 +2067	2083826	0	1004	0	2083826	0	1 +2068	2084830	0	1004	0	2084830	0	1 +2069	2085835	0	1005	0	2085835	0	1 +2070	2086838	0	1003	0	2086838	0	1 +2071	2087842	0	1004	0	2087842	0	1 +2072	2088846	0	1004	0	2088846	0	1 +2073	2089849	0	1003	0	2089849	0	1 +2074	2090854	0	1005	0	2090854	0	1 +2075	2091858	0	1004	0	2091858	0	1 +2076	2092872	0	1014	0	2092872	0	1 +2077	2093878	0	1006	0	2093878	0	1 +2078	2094883	0	1005	0	2094883	0	1 +2079	2095888	0	1005	0	2095888	0	1 +2080	2096891	94305	1003	94305	2002586	1	1 +2081	2097896	94305	1005	0	2003591	0	1 +2082	2098900	94305	1004	0	2004595	0	1 +2083	2099905	94305	1005	0	2005600	0	1 +2084	2100910	94305	1005	0	2006605	0	1 +2085	2101915	94305	1005	0	2007610	0	1 +2086	2102919	94305	1004	0	2008614	0	1 +2087	2103924	94305	1005	0	2009619	0	1 +2088	2104928	94305	1004	0	2010623	0	1 +2089	2105933	94305	1005	0	2011628	0	1 +2090	2106938	94305	1005	0	2012633	0	1 +2091	2107942	94305	1004	0	2013637	0	1 +2092	2108947	94305	1005	0	2014642	0	1 +2093	2109951	94305	1004	0	2015646	0	1 +2094	2110955	94305	1004	0	2016650	0	1 +2095	2111960	94305	1005	0	2017655	0	1 +2096	2112964	94305	1004	0	2018659	0	1 +2097	2113979	94305	1015	0	2019674	0	1 +2098	2114983	94305	1004	0	2020678	0	1 +2099	2116034	94305	1051	0	2021729	0	1 +2100	2117039	114108	1005	19803	2002931	1	1 +2101	2118044	114108	1005	0	2003936	0	1 +2102	2119048	114108	1004	0	2004940	0	1 +2103	2120052	114108	1004	0	2005944	0	1 +2104	2121056	114108	1004	0	2006948	0	1 +2105	2122061	114108	1005	0	2007953	0	1 +2106	2123065	114108	1004	0	2008957	0	1 +2107	2124070	114108	1005	0	2009962	0	1 +2108	2125074	114108	1004	0	2010966	0	1 +2109	2126078	114108	1004	0	2011970	0	1 +2110	2127082	114108	1004	0	2012974	0	1 +2111	2128086	114108	1004	0	2013978	0	1 +2112	2129090	114108	1004	0	2014982	0	1 +2113	2130095	114108	1005	0	2015987	0	1 +2114	2131098	114108	1003	0	2016990	0	1 +2115	2132101	114108	1003	0	2017993	0	1 +2116	2133105	114108	1004	0	2018997	0	1 +2117	2134109	114108	1004	0	2020001	0	1 +2118	2135112	114108	1003	0	2021004	0	1 +2119	2136119	114108	1007	0	2022011	0	1 +2120	2137125	114108	1006	0	2023017	0	1 +2121	2138130	114108	1005	0	2024022	0	1 +2122	2139136	114108	1006	0	2025028	0	1 +2123	2140141	114108	1005	0	2026033	0	1 +2124	2141145	114108	1004	0	2027037	0	1 +2125	2142150	114108	1005	0	2028042	0	1 +2126	2143157	114108	1007	0	2029049	0	1 +2127	2144160	114108	1003	0	2030052	0	1 +2128	2145164	114108	1004	0	2031056	0	1 +2129	2146169	114108	1005	0	2032061	0	1 +2130	2147174	114108	1005	0	2033066	0	1 +2131	2148178	114108	1004	0	2034070	0	1 +2132	2149183	114108	1005	0	2035075	0	1 +2133	2150188	114108	1005	0	2036080	0	1 +2134	2151191	114108	1003	0	2037083	0	1 +2135	2152196	114108	1005	0	2038088	0	1 +2136	2153201	114108	1005	0	2039093	0	1 +2137	2154206	114108	1005	0	2040098	0	1 +2138	2155210	114108	1004	0	2041102	0	1 +2139	2156244	154012	1034	39904	2002232	1	1 +2140	2157248	154012	1004	0	2003236	0	1 +2141	2158253	154012	1005	0	2004241	0	1 +2142	2159257	154012	1004	0	2005245	0	1 +2143	2160261	154012	1004	0	2006249	0	1 +2144	2161265	154012	1004	0	2007253	0	1 +2145	2162269	154012	1004	0	2008257	0	1 +2146	2163274	154012	1005	0	2009262	0	1 +2147	2164278	154012	1004	0	2010266	0	1 +2148	2165283	154012	1005	0	2011271	0	1 +2149	2166287	154012	1004	0	2012275	0	1 +2150	2167292	154012	1005	0	2013280	0	1 +2151	2168296	154012	1004	0	2014284	0	1 +2152	2169300	154012	1004	0	2015288	0	1 +2153	2170305	154012	1005	0	2016293	0	1 +2154	2171310	154012	1005	0	2017298	0	1 +2155	2172315	154012	1005	0	2018303	0	1 +2156	2173319	154012	1004	0	2019307	0	1 +2157	2174324	154012	1005	0	2020312	0	1 +2158	2175328	154012	1004	0	2021316	0	1 +2159	2176333	154012	1005	0	2022321	0	1 +2160	2177338	154012	1005	0	2023326	0	1 +2161	2178615	154012	1277	0	2024603	0	1 +2162	2179620	154012	1005	0	2025608	0	1 +2163	2180625	154012	1005	0	2026613	0	1 +2164	2181630	154012	1005	0	2027618	0	1 +2165	2182634	154012	1004	0	2028622	0	1 +2166	2183637	154012	1003	0	2029625	0	1 +2167	2184641	154012	1004	0	2030629	0	1 +2168	2185646	154012	1005	0	2031634	0	1 +2169	2186651	154012	1005	0	2032639	0	1 +2170	2187656	154012	1005	0	2033644	0	1 +2171	2188660	154012	1004	0	2034648	0	1 +2172	2189664	154012	1004	0	2035652	0	1 +2173	2190669	154012	1005	0	2036657	0	1 +2174	2191674	154012	1005	0	2037662	0	1 +2175	2192677	154012	1003	0	2038665	0	1 +2176	2193681	154012	1004	0	2039669	0	1 +2177	2194686	154012	1005	0	2040674	0	1 +2178	2195690	154012	1004	0	2041678	0	1 +2179	2196694	154012	1004	0	2042682	0	1 +2180	2197700	154012	1006	0	2043688	0	1 +2181	2198706	154012	1006	0	2044694	0	1 +2182	2199713	154012	1007	0	2045701	0	1 +2183	2200717	154012	1004	0	2046705	0	1 +2184	2201722	154012	1005	0	2047710	0	1 +2185	2202727	154012	1005	0	2048715	0	1 +2186	2203731	154012	1004	0	2049719	0	1 +2187	2204735	154012	1004	0	2050723	0	1 +2188	2205739	154012	1004	0	2051727	0	1 +2189	2206743	154012	1004	0	2052731	0	1 +2190	2207746	154012	1003	0	2053734	0	1 +2191	2208749	154012	1003	0	2054737	0	1 +2192	2209753	154012	1004	0	2055741	0	1 +2193	2210757	154012	1004	0	2056745	0	1 +2194	2211762	154012	1005	0	2057750	0	1 +2195	2212766	154012	1004	0	2058754	0	1 +2196	2213771	154012	1005	0	2059759	0	1 +2197	2214775	154012	1004	0	2060763	0	1 +2198	2215780	154012	1005	0	2061768	0	1 +2199	2216784	154012	1004	0	2062772	0	1 +2200	2217789	154012	1005	0	2063777	0	1 +2201	2218794	154012	1005	0	2064782	0	1 +2202	2219798	154012	1004	0	2065786	0	1 +2203	2220803	154012	1005	0	2066791	0	1 +2204	2221807	154012	1004	0	2067795	0	1 +2205	2222810	154012	1003	0	2068798	0	1 +2206	2223814	154012	1004	0	2069802	0	1 +2207	2224819	154012	1005	0	2070807	0	1 +2208	2225823	154012	1004	0	2071811	0	1 +2209	2226828	154012	1005	0	2072816	0	1 +2210	2227831	154012	1003	0	2073819	0	1 +2211	2228835	154012	1004	0	2074823	0	1 +2212	2229840	154012	1005	0	2075828	0	1 +2213	2230845	154012	1005	0	2076833	0	1 +2214	2231849	154012	1004	0	2077837	0	1 +2215	2232854	154012	1005	0	2078842	0	1 +2216	2233858	154012	1004	0	2079846	0	1 +2217	2234872	154012	1014	0	2080860	0	1 +2218	2235910	154012	1038	0	2081898	0	1 +2219	2236916	234389	1006	80377	2002527	1	1 +2220	2237919	234389	1003	0	2003530	0	1 +2221	2238924	234389	1005	0	2004535	0	1 +2222	2239929	234389	1005	0	2005540	0	1 +2223	2240934	234389	1005	0	2006545	0	1 +2224	2241938	234389	1004	0	2007549	0	1 +2225	2242941	234389	1003	0	2008552	0	1 +2226	2243945	234389	1004	0	2009556	0	1 +2227	2244950	234389	1005	0	2010561	0	1 +2228	2245955	234389	1005	0	2011566	0	1 +2229	2246960	234389	1005	0	2012571	0	1 +2230	2247964	234389	1004	0	2013575	0	1 +2231	2248969	234389	1005	0	2014580	0	1 +2232	2249974	234389	1005	0	2015585	0	1 +2233	2250979	234389	1005	0	2016590	0	1 +2234	2251982	234389	1003	0	2017593	0	1 +2235	2252986	234389	1004	0	2018597	0	1 +2236	2253990	234389	1004	0	2019601	0	1 +2237	2254994	234389	1004	0	2020605	0	1 +2238	2255999	234389	1005	0	2021610	0	1 +2239	2257002	234389	1003	0	2022613	0	1 +2240	2258007	234389	1005	0	2023618	0	1 +2241	2259011	234389	1004	0	2024622	0	1 +2242	2260014	234389	1003	0	2025625	0	1 +2243	2261018	234389	1004	0	2026629	0	1 +2244	2262022	234389	1004	0	2027633	0	1 +2245	2263026	234389	1004	0	2028637	0	1 +2246	2264031	234389	1005	0	2029642	0	1 +2247	2265035	234389	1004	0	2030646	0	1 +2248	2266039	234389	1004	0	2031650	0	1 +2249	2267043	234389	1004	0	2032654	0	1 +2250	2268048	234389	1005	0	2033659	0	1 +2251	2269052	234389	1004	0	2034663	0	1 +2252	2270056	234389	1004	0	2035667	0	1 +2253	2271059	234389	1003	0	2036670	0	1 +2254	2272063	234389	1004	0	2037674	0	1 +2255	2273067	234389	1004	0	2038678	0	1 +2256	2274070	234389	1003	0	2039681	0	1 +2257	2275074	234389	1004	0	2040685	0	1 +2258	2276079	234389	1005	0	2041690	0	1 +2259	2277083	234389	1004	0	2042694	0	1 +2260	2278087	234389	1004	0	2043698	0	1 +2261	2279090	234389	1003	0	2044701	0	1 +2262	2280094	234389	1004	0	2045705	0	1 +2263	2281099	234389	1005	0	2046710	0	1 +2264	2282103	234389	1004	0	2047714	0	1 +2265	2283107	234389	1004	0	2048718	0	1 +2266	2284110	234389	1003	0	2049721	0	1 +2267	2285156	234389	1046	0	2050767	0	1 +2268	2286163	234389	1007	0	2051774	0	1 +2269	2287167	234389	1004	0	2052778	0	1 +2270	2288172	234389	1005	0	2053783	0	1 +2271	2289176	234389	1004	0	2054787	0	1 +2272	2290180	234389	1004	0	2055791	0	1 +2273	2291186	234389	1006	0	2056797	0	1 +2274	2292190	234389	1004	0	2057801	0	1 +2275	2293194	234389	1004	0	2058805	0	1 +2276	2294198	234389	1004	0	2059809	0	1 +2277	2295201	234389	1003	0	2060812	0	1 +2278	2296206	234389	1005	0	2061817	0	1 +2279	2297211	234389	1005	0	2062822	0	1 +2280	2298215	234389	1004	0	2063826	0	1 +2281	2299219	234389	1004	0	2064830	0	1 +2282	2300223	234389	1004	0	2065834	0	1 +2283	2301227	234389	1004	0	2066838	0	1 +2284	2302231	234389	1004	0	2067842	0	1 +2285	2303236	234389	1005	0	2068847	0	1 +2286	2304241	234389	1005	0	2069852	0	1 +2287	2305244	234389	1003	0	2070855	0	1 +2288	2306249	234389	1005	0	2071860	0	1 +2289	2307253	234389	1004	0	2072864	0	1 +2290	2308257	234389	1004	0	2073868	0	1 +2291	2309261	234389	1004	0	2074872	0	1 +2292	2310266	234389	1005	0	2075877	0	1 +2293	2311270	234389	1004	0	2076881	0	1 +2294	2312274	234389	1004	0	2077885	0	1 +2295	2313278	234389	1004	0	2078889	0	1 +2296	2314283	234389	1005	0	2079894	0	1 +2297	2315288	234389	1005	0	2080899	0	1 +2298	2316291	234389	1003	0	2081902	0	1 +2299	2317295	234389	1004	0	2082906	0	1 +2300	2318299	234389	1004	0	2083910	0	1 +2301	2319303	234389	1004	0	2084914	0	1 +2302	2320306	234389	1003	0	2085917	0	1 +2303	2321310	234389	1004	0	2086921	0	1 +2304	2322313	234389	1003	0	2087924	0	1 +2305	2323317	234389	1004	0	2088928	0	1 +2306	2324322	234389	1005	0	2089933	0	1 +2307	2325326	234389	1004	0	2090937	0	1 +2308	2326330	234389	1004	0	2091941	0	1 +2309	2327335	234389	1005	0	2092946	0	1 +2310	2328340	234389	1005	0	2093951	0	1 +2311	2329343	234389	1003	0	2094954	0	1 +2312	2330347	234389	1004	0	2095958	0	1 +2313	2331351	234389	1004	0	2096962	0	1 +2314	2332356	234389	1005	0	2097967	0	1 +2315	2333360	234389	1004	0	2098971	0	1 +2316	2334365	234389	1005	0	2099976	0	1 +2317	2335369	234389	1004	0	2100980	0	1 +2318	2336373	234389	1004	0	2101984	0	1 +2319	2337377	234389	1004	0	2102988	0	1 +2320	2338381	234389	1004	0	2103992	0	1 +2321	2339385	234389	1004	0	2104996	0	1 +2322	2340389	234389	1004	0	2106000	0	1 +2323	2341393	234389	1004	0	2107004	0	1 +2324	2342397	234389	1004	0	2108008	0	1 +2325	2343401	234389	1004	0	2109012	0	1 +2326	2344406	234389	1005	0	2110017	0	1 +2327	2345410	234389	1004	0	2111021	0	1 +2328	2346414	234389	1004	0	2112025	0	1 +2329	2347417	234389	1003	0	2113028	0	1 +2330	2348422	234389	1005	0	2114033	0	1 +2331	2349426	234389	1004	0	2115037	0	1 +2332	2350430	234389	1004	0	2116041	0	1 +2333	2351434	234389	1004	0	2117045	0	1 +2334	2352438	234389	1004	0	2118049	0	1 +2335	2353442	234389	1004	0	2119053	0	1 +2336	2354447	234389	1005	0	2120058	0	1 +2337	2355452	234389	1005	0	2121063	0	1 +2338	2356455	234389	1003	0	2122066	0	1 +2339	2357460	234389	1005	0	2123071	0	1 +2340	2358465	234389	1005	0	2124076	0	1 +2341	2359469	234389	1004	0	2125080	0	1 +2342	2360474	234389	1005	0	2126085	0	1 +2343	2361479	234389	1005	0	2127090	0	1 +2344	2362483	234389	1004	0	2128094	0	1 +2345	2363488	234389	1005	0	2129099	0	1 +2346	2364493	234389	1005	0	2130104	0	1 +2347	2365496	234389	1003	0	2131107	0	1 +2348	2366501	234389	1005	0	2132112	0	1 +2349	2367506	234389	1005	0	2133117	0	1 +2350	2368510	234389	1004	0	2134121	0	1 +2351	2369513	234389	1003	0	2135124	0	1 +2352	2370518	234389	1005	0	2136129	0	1 +2353	2371523	234389	1005	0	2137134	0	1 +2354	2372528	234389	1005	0	2138139	0	1 +2355	2373533	234389	1005	0	2139144	0	1 +2356	2374537	234389	1004	0	2140148	0	1 +2357	2375541	234389	1004	0	2141152	0	1 +2358	2376546	234389	1005	0	2142157	0	1 +2359	2377550	234389	1004	0	2143161	0	1 +2360	2378555	234389	1005	0	2144166	0	1 +2361	2379559	234389	1004	0	2145170	0	1 +2362	2380562	234389	1003	0	2146173	0	1 +2363	2381567	234389	1005	0	2147178	0	1 +2364	2382571	234389	1004	0	2148182	0	1 +2365	2383576	234389	1005	0	2149187	0	1 +2366	2384580	234389	1004	0	2150191	0	1 +2367	2385584	234389	1004	0	2151195	0	1 +2368	2386590	234389	1006	0	2152201	0	1 +2369	2387594	234389	1004	0	2153205	0	1 +2370	2388598	234389	1004	0	2154209	0	1 +2371	2389602	234389	1004	0	2155213	0	1 +2372	2390607	234389	1005	0	2156218	0	1 +2373	2391611	234389	1004	0	2157222	0	1 +2374	2392616	234389	1005	0	2158227	0	1 +2375	2393620	234389	1004	0	2159231	0	1 +2376	2394625	234389	1005	0	2160236	0	1 +2377	2395630	234389	1005	0	2161241	0	1 +2378	2396635	234389	1005	0	2162246	0	1 +2379	2397639	234389	1004	0	2163250	0	1 +2380	2398644	234389	1005	0	2164255	0	1 +2381	2399649	234389	1005	0	2165260	0	1 +2382	2400654	234389	1005	0	2166265	0	1 +2383	2401660	234389	1006	0	2167271	0	1 +2384	2402663	234389	1003	0	2168274	0	1 +2385	2403668	234389	1005	0	2169279	0	1 +2386	2404673	234389	1005	0	2170284	0	1 +2387	2405676	234389	1003	0	2171287	0	1 +2388	2406680	234389	1004	0	2172291	0	1 +2389	2407685	234389	1005	0	2173296	0	1 +2390	2408690	234389	1005	0	2174301	0	1 +2391	2409695	234389	1005	0	2175306	0	1 +2392	2410699	234389	1004	0	2176310	0	1 +2393	2411704	234389	1005	0	2177315	0	1 +2394	2412709	234389	1005	0	2178320	0	1 +2395	2413713	234389	1004	0	2179324	0	1 +2396	2414718	234389	1005	0	2180329	0	1 +2397	2415723	234389	1005	0	2181334	0	1 +2398	2416727	234389	1004	0	2182338	0	1 +2399	2417732	234389	1005	0	2183343	0	1 +2400	2418737	234389	1005	0	2184348	0	1 +2401	2419741	234389	1004	0	2185352	0	1 +2402	2420746	234389	1005	0	2186357	0	1 +2403	2421751	234389	1005	0	2187362	0	1 +2404	2422756	234389	1005	0	2188367	0	1 +2405	2423760	234389	1004	0	2189371	0	1 +2406	2424764	234389	1004	0	2190375	0	1 +2407	2425769	234389	1005	0	2191380	0	1 +2408	2426941	234389	1172	0	2192552	0	1 +2409	2427946	424894	1005	190505	2003052	1	1 +2410	2428951	424894	1005	0	2004057	0	1 +2411	2429956	424894	1005	0	2005062	0	1 +2412	2430961	424894	1005	0	2006067	0	1 +2413	2431965	424894	1004	0	2007071	0	1 +2414	2432969	424894	1004	0	2008075	0	1 +2415	2433974	424894	1005	0	2009080	0	1 +2416	2434978	424894	1004	0	2010084	0	1 +2417	2435983	424894	1005	0	2011089	0	1 +2418	2436988	424894	1005	0	2012094	0	1 +2419	2437993	424894	1005	0	2013099	0	1 +2420	2438999	424894	1006	0	2014105	0	1 +2421	2440003	424894	1004	0	2015109	0	1 +2422	2441008	424894	1005	0	2016114	0	1 +2423	2442012	424894	1004	0	2017118	0	1 +2424	2443016	424894	1004	0	2018122	0	1 +2425	2444019	424894	1003	0	2019125	0	1 +2426	2445024	424894	1005	0	2020130	0	1 +2427	2446029	424894	1005	0	2021135	0	1 +2428	2447033	424894	1004	0	2022139	0	1 +2429	2448038	424894	1005	0	2023144	0	1 +2430	2449042	424894	1004	0	2024148	0	1 +2431	2450046	424894	1004	0	2025152	0	1 +2432	2451051	424894	1005	0	2026157	0	1 +2433	2452056	424894	1005	0	2027162	0	1 +2434	2453060	424894	1004	0	2028166	0	1 +2435	2454064	424894	1004	0	2029170	0	1 +2436	2455068	424894	1004	0	2030174	0	1 +2437	2456073	424894	1005	0	2031179	0	1 +2438	2457077	424894	1004	0	2032183	0	1 +2439	2458082	424894	1005	0	2033188	0	1 +2440	2459086	424894	1004	0	2034192	0	1 +2441	2460091	424894	1005	0	2035197	0	1 +2442	2461094	424894	1003	0	2036200	0	1 +2443	2462099	424894	1005	0	2037205	0	1 +2444	2463103	424894	1004	0	2038209	0	1 +2445	2464107	424894	1004	0	2039213	0	1 +2446	2465112	424894	1005	0	2040218	0	1 +2447	2466119	424894	1007	0	2041225	0	1 +2448	2467125	424894	1006	0	2042231	0	1 +2449	2468131	424894	1006	0	2043237	0	1 +2450	2469135	424894	1004	0	2044241	0	1 +2451	2470140	424894	1005	0	2045246	0	1 +2452	2471145	424894	1005	0	2046251	0	1 +2453	2472151	424894	1006	0	2047257	0	1 +2454	2473157	424894	1006	0	2048263	0	1 +2455	2474161	424894	1004	0	2049267	0	1 +2456	2475166	424894	1005	0	2050272	0	1 +2457	2476171	424894	1005	0	2051277	0	1 +2458	2477175	424894	1004	0	2052281	0	1 +2459	2478179	424894	1004	0	2053285	0	1 +2460	2479184	424894	1005	0	2054290	0	1 +2461	2480188	424894	1004	0	2055294	0	1 +2462	2481192	424894	1004	0	2056298	0	1 +2463	2482196	424894	1004	0	2057302	0	1 +2464	2483201	424894	1005	0	2058307	0	1 +2465	2484206	424894	1005	0	2059312	0	1 +2466	2485210	424894	1004	0	2060316	0	1 +2467	2486214	424894	1004	0	2061320	0	1 +2468	2487219	424894	1005	0	2062325	0	1 +2469	2488223	424894	1004	0	2063329	0	1 +2470	2489227	424894	1004	0	2064333	0	1 +2471	2490231	424894	1004	0	2065337	0	1 +2472	2491236	424894	1005	0	2066342	0	1 +2473	2492239	424894	1003	0	2067345	0	1 +2474	2493244	424894	1005	0	2068350	0	1 +2475	2494247	424894	1003	0	2069353	0	1 +2476	2495252	424894	1005	0	2070358	0	1 +2477	2496255	424894	1003	0	2071361	0	1 +2478	2497260	424894	1005	0	2072366	0	1 +2479	2498263	424894	1003	0	2073369	0	1 +2480	2499268	424894	1005	0	2074374	0	1 +2481	2500272	424894	1004	0	2075378	0	1 +2482	2501275	424894	1003	0	2076381	0	1 +2483	2502280	424894	1005	0	2077386	0	1 +2484	2503284	424894	1004	0	2078390	0	1 +2485	2504287	424894	1003	0	2079393	0	1 +2486	2505291	424894	1004	0	2080397	0	1 +2487	2506295	424894	1004	0	2081401	0	1 +2488	2507300	424894	1005	0	2082406	0	1 +2489	2508303	424894	1003	0	2083409	0	1 +2490	2509307	424894	1004	0	2084413	0	1 +2491	2510312	424894	1005	0	2085418	0	1 +2492	2511317	424894	1005	0	2086423	0	1 +2493	2512321	424894	1004	0	2087427	0	1 +2494	2513326	424894	1005	0	2088432	0	1 +2495	2514330	424894	1004	0	2089436	0	1 +2496	2515335	424894	1005	0	2090441	0	1 +2497	2516340	424894	1005	0	2091446	0	1 +2498	2517344	424894	1004	0	2092450	0	1 +2499	2518349	424894	1005	0	2093455	0	1 +2500	2519354	424894	1005	0	2094460	0	1 +2501	2520357	424894	1003	0	2095463	0	1 +2502	2521360	424894	1003	0	2096466	0	1 +2503	2522365	424894	1005	0	2097471	0	1 +2504	2523370	424894	1005	0	2098476	0	1 +2505	2524374	424894	1004	0	2099480	0	1 +2506	2525379	424894	1005	0	2100485	0	1 +2507	2526384	424894	1005	0	2101490	0	1 +2508	2527388	424894	1004	0	2102494	0	1 +2509	2528391	424894	1003	0	2103497	0	1 +2510	2529395	424894	1004	0	2104501	0	1 +2511	2530400	424894	1005	0	2105506	0	1 +2512	2531405	424894	1005	0	2106511	0	1 +2513	2532409	424894	1004	0	2107515	0	1 +2514	2533412	424894	1003	0	2108518	0	1 +2515	2534416	424894	1004	0	2109522	0	1 +2516	2535421	424894	1005	0	2110527	0	1 +2517	2536426	424894	1005	0	2111532	0	1 +2518	2537432	424894	1006	0	2112538	0	1 +2519	2538437	424894	1005	0	2113543	0	1 +2520	2539441	424894	1004	0	2114547	0	1 +2521	2540445	424894	1004	0	2115551	0	1 +2522	2541449	424894	1004	0	2116555	0	1 +2523	2542453	424894	1004	0	2117559	0	1 +2524	2543458	424894	1005	0	2118564	0	1 +2525	2544463	424894	1005	0	2119569	0	1 +2526	2545467	424894	1004	0	2120573	0	1 +2527	2546472	424894	1005	0	2121578	0	1 +2528	2547476	424894	1004	0	2122582	0	1 +2529	2548481	424894	1005	0	2123587	0	1 +2530	2549485	424894	1004	0	2124591	0	1 +2531	2550488	424894	1003	0	2125594	0	1 +2532	2551493	424894	1005	0	2126599	0	1 +2533	2552498	424894	1005	0	2127604	0	1 +2534	2553501	424894	1003	0	2128607	0	1 +2535	2554505	424894	1004	0	2129611	0	1 +2536	2555509	424894	1004	0	2130615	0	1 +2537	2556514	424894	1005	0	2131620	0	1 +2538	2557519	424894	1005	0	2132625	0	1 +2539	2558523	424894	1004	0	2133629	0	1 +2540	2559528	424894	1005	0	2134634	0	1 +2541	2560532	424894	1004	0	2135638	0	1 +2542	2561537	424894	1005	0	2136643	0	1 +2543	2562542	424894	1005	0	2137648	0	1 +2544	2563545	424894	1003	0	2138651	0	1 +2545	2564549	424894	1004	0	2139655	0	1 +2546	2565552	424894	1003	0	2140658	0	1 +2547	2566556	424894	1004	0	2141662	0	1 +2548	2567560	424894	1004	0	2142666	0	1 +2549	2568564	424894	1004	0	2143670	0	1 +2550	2569567	424894	1003	0	2144673	0	1 +2551	2570573	424894	1006	0	2145679	0	1 +2552	2571577	424894	1004	0	2146683	0	1 +2553	2572580	424894	1003	0	2147686	0	1 +2554	2573585	424894	1005	0	2148691	0	1 diff --git a/src/tests/alsa-time-test.c b/src/tests/alsa-time-test.c index e852c3f7..233bbe64 100644 --- a/src/tests/alsa-time-test.c +++ b/src/tests/alsa-time-test.c @@ -23,7 +23,7 @@ int main(int argc, char *argv[]) {      int dir = 1;      struct timespec start, last_timestamp = { 0, 0 };      uint64_t start_us; -    snd_pcm_sframes_t last_avail, last_delay; +    snd_pcm_sframes_t last_avail = 0, last_delay = 0;      struct pollfd *pollfds;      int n_pollfd;      int64_t sample_count = 0; diff --git a/src/tests/gtk-test.c b/src/tests/gtk-test.c index f82aca58..6470e484 100644 --- a/src/tests/gtk-test.c +++ b/src/tests/gtk-test.c @@ -29,12 +29,38 @@  #include <pulse/context.h>  #include <pulse/glib-mainloop.h> +pa_context *ctxt; +pa_glib_mainloop *m; + +static void context_state_callback(pa_context *c, void *userdata); + +static void connect(void) { +    int r; + +    ctxt = pa_context_new(pa_glib_mainloop_get_api(m), NULL); +    g_assert(ctxt); + +    r = pa_context_connect(ctxt, NULL, PA_CONTEXT_NOAUTOSPAWN|PA_CONTEXT_NOFAIL, NULL); +    g_assert(r == 0); + +    pa_context_set_state_callback(ctxt, context_state_callback, NULL); +} + +static void context_state_callback(pa_context *c, void *userdata) { +    switch (pa_context_get_state(c)) { +        case PA_CONTEXT_FAILED: +            pa_context_unref(ctxt); +            ctxt = NULL; +            connect(); +            break; +        default: +            break; +    } +} +  int main(int argc, char *argv[]) { -    pa_context *c; -    pa_glib_mainloop *m;      GtkWidget *window; -    int r;      gtk_init(&argc, &argv); @@ -49,15 +75,10 @@ int main(int argc, char *argv[]) {      m = pa_glib_mainloop_new(NULL);      g_assert(m); -    c = pa_context_new(pa_glib_mainloop_get_api(m), NULL); -    g_assert(c); - -    r = pa_context_connect(c, NULL, 0, NULL); -    g_assert(r == 0); - +    connect();      gtk_main(); -    pa_context_unref(c); +    pa_context_unref(ctxt);      pa_glib_mainloop_free(m);      return 0; diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c index dd24e829..0c906d3e 100644 --- a/src/tests/interpol-test.c +++ b/src/tests/interpol-test.c @@ -36,6 +36,9 @@  #include <pulsecore/thread.h> +#define INTERPOLATE +//#define CORK +  static pa_context *context = NULL;  static pa_stream *stream = NULL;  static pa_mainloop_api *mainloop_api = NULL; @@ -58,6 +61,15 @@ static void stream_read_cb(pa_stream *p, size_t nbytes, void *userdata) {      }  } +static void stream_latency_cb(pa_stream *p, void *userdata) { +#ifndef INTERPOLATE +    pa_operation *o; + +    o = pa_stream_update_timing_info(p, NULL, NULL); +    pa_operation_unref(o); +#endif +} +  /* This is called whenever the context status changes */  static void context_state_callback(pa_context *c, void *userdata) {      assert(c); @@ -69,6 +81,7 @@ static void context_state_callback(pa_context *c, void *userdata) {              break;          case PA_CONTEXT_READY: { +            pa_stream_flags_t flags = PA_STREAM_AUTO_TIMING_UPDATE;              static const pa_sample_spec ss = {                  .format = PA_SAMPLE_S16LE, @@ -76,19 +89,25 @@ static void context_state_callback(pa_context *c, void *userdata) {                  .channels = 2              }; +#ifdef INTERPOLATE +            flags |= PA_STREAM_INTERPOLATE_TIMING; +#endif +              fprintf(stderr, "Connection established.\n");              stream = pa_stream_new(c, "interpol-test", &ss, NULL);              assert(stream);              if (playback) { -                pa_assert_se(pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) == 0); +                pa_assert_se(pa_stream_connect_playback(stream, NULL, NULL, flags, NULL, NULL) == 0);                  pa_stream_set_write_callback(stream, stream_write_cb, NULL);              } else { -                pa_assert_se(pa_stream_connect_record(stream, NULL, NULL, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE) == 0); +                pa_assert_se(pa_stream_connect_record(stream, NULL, NULL, flags) == 0);                  pa_stream_set_read_callback(stream, stream_read_cb, NULL);              } +            pa_stream_set_latency_update_callback(stream, stream_latency_cb, NULL); +              break;          } @@ -107,7 +126,11 @@ int main(int argc, char *argv[]) {      int k, r;      struct timeval start, last_info = { 0, 0 };      pa_usec_t old_t = 0, old_rtc = 0; +#ifdef CORK      pa_bool_t corked = FALSE; +#endif + +    pa_log_set_level(PA_LOG_DEBUG);      playback = argc <= 1 || !pa_streq(argv[1], "-r"); @@ -130,7 +153,12 @@ int main(int argc, char *argv[]) {      r = pa_threaded_mainloop_start(m);      assert(r >= 0); -    for (k = 0; k < 20000; k++) { +/* #ifdef CORK */ +    for (k = 0; k < 20000; k++) +/* #else */ +/*     for (k = 0; k < 2000; k++) */ +/* #endif */ +    {          pa_bool_t success = FALSE, changed = FALSE;          pa_usec_t t, rtc;          struct timeval now, tv; @@ -159,14 +187,16 @@ int main(int argc, char *argv[]) {          pa_gettimeofday(&now);          if (success) { +#ifdef CORK              pa_bool_t cork_now; - +#endif              rtc = pa_timeval_diff(&now, &start); -            printf("%i\t%llu\t%llu\t%llu\t%llu\t%u\t%u\n", k, +            printf("%i\t%llu\t%llu\t%llu\t%llu\t%lli\t%u\t%u\n", k,                     (unsigned long long) rtc,                     (unsigned long long) t,                     (unsigned long long) (rtc-old_rtc),                     (unsigned long long) (t-old_t), +                   (signed long long) rtc - (signed long long) t,                     changed,                     playing); @@ -174,6 +204,7 @@ int main(int argc, char *argv[]) {              old_t = t;              old_rtc = rtc; +#ifdef CORK              cork_now = (rtc / (2*PA_USEC_PER_SEC)) % 2 == 1;              if (corked != cork_now) { @@ -185,6 +216,7 @@ int main(int argc, char *argv[]) {                  corked = cork_now;              } +#endif          }          /* Spin loop, ugly but normal usleep() is just too badly grained */ diff --git a/src/tests/ipacl-test.c b/src/tests/ipacl-test.c index f89665cd..57b70685 100644 --- a/src/tests/ipacl-test.c +++ b/src/tests/ipacl-test.c @@ -91,8 +91,10 @@ int main(int argc, char *argv[]) {      close(fd);  #ifdef HAVE_IPV6 -    fd = socket(PF_INET6, SOCK_STREAM, 0); -    assert(fd >= 0); +    if ( (fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0 ) { +      printf("Unable to open IPv6 socket, IPv6 tests ignored"); +      return 0; +    }      memset(&sa6, 0, sizeof(sa6));      sa6.sin6_family = AF_INET6; diff --git a/src/tests/mainloop-test.c b/src/tests/mainloop-test.c index d8926233..3ec6d115 100644 --- a/src/tests/mainloop-test.c +++ b/src/tests/mainloop-test.c @@ -26,10 +26,12 @@  #include <sys/time.h>  #include <assert.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/gccmacro.h>  #include <pulsecore/core-util.h> +#include <pulsecore/core-rtclock.h>  #ifdef GLIB_MAIN_LOOP @@ -46,7 +48,7 @@ static pa_defer_event *de;  static void iocb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {      unsigned char c; -    read(fd, &c, sizeof(c)); +    (void) read(fd, &c, sizeof(c));      fprintf(stderr, "IO EVENT: %c\n", c < 32 ? '.' : c);      a->defer_enable(de, 1);  } @@ -99,9 +101,7 @@ int main(int argc, char *argv[]) {      de = a->defer_new(a, dcb, NULL);      assert(de); -    pa_gettimeofday(&tv); -    tv.tv_sec += 10; -    te = a->time_new(a, &tv, tcb, NULL); +    te = a->time_new(a, pa_timeval_rtstore(&tv, pa_rtclock_now() + 2 * PA_USEC_PER_SEC, TRUE), tcb, NULL);  #if defined(GLIB_MAIN_LOOP)      g_main_loop_run(glib_main_loop); diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c index 127fb197..ec3f5426 100644 --- a/src/tests/memblockq-test.c +++ b/src/tests/memblockq-test.c @@ -105,45 +105,45 @@ int main(int argc, char *argv[]) {      ret = pa_memblockq_push(bq, &chunk4);      assert(ret == 0); -    pa_memblockq_seek(bq, -6, 0); +    pa_memblockq_seek(bq, -6, 0, TRUE);      ret = pa_memblockq_push(bq, &chunk3);      assert(ret == 0); -    pa_memblockq_seek(bq, -2, 0); +    pa_memblockq_seek(bq, -2, 0, TRUE);      ret = pa_memblockq_push(bq, &chunk1);      assert(ret == 0); -    pa_memblockq_seek(bq, -10, 0); +    pa_memblockq_seek(bq, -10, 0, TRUE);      ret = pa_memblockq_push(bq, &chunk4);      assert(ret == 0); -    pa_memblockq_seek(bq, 10, 0); +    pa_memblockq_seek(bq, 10, 0, TRUE);      ret = pa_memblockq_push(bq, &chunk1);      assert(ret == 0); -    pa_memblockq_seek(bq, -6, 0); +    pa_memblockq_seek(bq, -6, 0, TRUE);      ret = pa_memblockq_push(bq, &chunk2);      assert(ret == 0);      /* Test splitting */ -    pa_memblockq_seek(bq, -12, 0); +    pa_memblockq_seek(bq, -12, 0, TRUE);      ret = pa_memblockq_push(bq, &chunk1);      assert(ret == 0); -    pa_memblockq_seek(bq, 20, 0); +    pa_memblockq_seek(bq, 20, 0, TRUE);      /* Test merging */      ret = pa_memblockq_push(bq, &chunk3);      assert(ret == 0); -    pa_memblockq_seek(bq, -2, 0); +    pa_memblockq_seek(bq, -2, 0, TRUE);      chunk3.index += 2;      chunk3.length -= 2;      ret = pa_memblockq_push(bq, &chunk3);      assert(ret == 0); -    pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE); +    pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE, TRUE);      dump(bq); diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c index ac4b57b5..3f65cbac 100644 --- a/src/tests/mix-test.c +++ b/src/tests/mix-test.c @@ -79,6 +79,18 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {              break;          } +        case PA_SAMPLE_S24NE: +        case PA_SAMPLE_S24RE: { +            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)); +                u += 3; +            } + +            break; +        } +          case PA_SAMPLE_FLOAT32NE:          case PA_SAMPLE_FLOAT32RE: {              float *u = d; @@ -113,73 +125,66 @@ 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: { -            uint8_t *u = d; +	    static const uint8_t u8_samples[] = +	      { 0x00, 0xFF, 0x7F, 0x80, 0x9f, +		0x3f, 0x01, 0xF0, 0x20, 0x21 }; -            u[0] = 0x00; -            u[1] = 0xFF; -            u[2] = 0x7F; -            u[3] = 0x80; -            u[4] = 0x9f; -            u[5] = 0x3f; -            u[6] = 0x1; -            u[7] = 0xF0; -            u[8] = 0x20; -            u[9] = 0x21; +	    memcpy(d, &u8_samples[0], sizeof(u8_samples));              break;          }          case PA_SAMPLE_S16NE:          case PA_SAMPLE_S16RE: { -            uint16_t *u = d; +	    static const uint16_t u16_samples[] = +	      { 0x0000, 0xFFFF, 0x7FFF, 0x8000, 0x9fff, +		0x3fff, 0x0001, 0xF000, 0x0020, 0x0021 }; -            u[0] = 0x0000; -            u[1] = 0xFFFF; -            u[2] = 0x7FFF; -            u[3] = 0x8000; -            u[4] = 0x9fff; -            u[5] = 0x3fff; -            u[6] = 0x1; -            u[7] = 0xF000; -            u[8] = 0x20; -            u[9] = 0x21; +	    memcpy(d, &u16_samples[0], sizeof(u16_samples));              break;          }          case PA_SAMPLE_S32NE:          case PA_SAMPLE_S32RE: { -            uint32_t *u = d; +	    static const uint32_t u32_samples[] = +	      { 0x00000001, 0xFFFF0002, 0x7FFF0003, 0x80000004, 0x9fff0005, +		0x3fff0006, 0x00010007, 0xF0000008, 0x00200009, 0x0021000A }; + +	    memcpy(d, &u32_samples[0], sizeof(u32_samples)); +            break; +        } -            u[0] = 0x00000001; -            u[1] = 0xFFFF0002; -            u[2] = 0x7FFF0003; -            u[3] = 0x80000004; -            u[4] = 0x9fff0005; -            u[5] = 0x3fff0006; -            u[6] =    0x10007; -            u[7] = 0xF0000008; -            u[8] =   0x200009; -            u[9] =   0x21000A; +        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));              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 }; -            u[0] = 0.0f; -            u[1] = -1.0f; -            u[2] = 1.0f; -            u[3] = 4711.0f; -            u[4] = 0.222f; -            u[5] = 0.33f; -            u[6] = -.3f; -            u[7] = 99.0f; -            u[8] = -0.555f; -            u[9] = -.123f; - -            if (ss->format == PA_SAMPLE_FLOAT32RE) +            if (ss->format == PA_SAMPLE_FLOAT32RE) {                  for (i = 0; i < 10; i++) -                    u[i] = swap_float(u[i]); +                    u[i] = swap_float(float_samples[i]); +	    } else { +	      memcpy(d, &float_samples[0], sizeof(float_samples)); +	    }              break;          } diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c index 3e723561..27a0d3fe 100644 --- a/src/tests/proplist-test.c +++ b/src/tests/proplist-test.c @@ -27,11 +27,14 @@  #include <pulse/xmalloc.h>  #include <pulsecore/macro.h>  #include <pulsecore/core-util.h> +#include <pulsecore/modargs.h>  int main(int argc, char*argv[]) { +    pa_modargs *ma;      pa_proplist *a, *b, *c, *d;      char *s, *t, *u, *v;      const char *text; +    const char *x[] = { "foo", NULL };      a = pa_proplist_new();      pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0); @@ -78,5 +81,16 @@ int main(int argc, char*argv[]) {      printf("%s\n", v);      pa_xfree(v); +    pa_assert_se(ma = pa_modargs_new("foo='foobar=waldo foo2=\"lj\\\\\"dhflh\" foo3=\\'kjlskj\\\\\\'\\''", x)); +    pa_assert_se(a = pa_proplist_new()); + +    pa_assert_se(pa_modargs_get_proplist(ma, "foo", a, PA_UPDATE_REPLACE) >= 0); + +    printf("%s\n", v = pa_proplist_to_string(a)); +    pa_xfree(v); + +    pa_proplist_free(a); +    pa_modargs_free(ma); +      return 0;  } diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c index 6b4a64ca..7236265a 100644 --- a/src/tests/resampler-test.c +++ b/src/tests/resampler-test.c @@ -34,12 +34,6 @@  #include <liboil/liboil.h> -static float swap_float(float a) { -    uint32_t *b = (uint32_t*) &a; -    *b = PA_UINT32_SWAP(*b); -    return a; -} -  static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {      void *d;      unsigned i; @@ -54,7 +48,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 ", *(u++)); +                printf("      0x%02x ", *(u++));              break;          } @@ -64,7 +58,7 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {              uint16_t *u = d;              for (i = 0; i < chunk->length / pa_frame_size(ss); i++) -                printf("0x%04x ", *(u++)); +                printf("    0x%04x ", *(u++));              break;          } @@ -79,18 +73,40 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {              break;          } +        case PA_SAMPLE_S24_32NE: +        case PA_SAMPLE_S24_32RE: { +            uint32_t *u = d; + +            for (i = 0; i < chunk->length / pa_frame_size(ss); i++) +                printf("0x%08x ", *(u++)); + +            break; +        } +          case PA_SAMPLE_FLOAT32NE:          case PA_SAMPLE_FLOAT32RE: {              float *u = d;              for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { -                printf("%1.3g ",  ss->format == PA_SAMPLE_FLOAT32NE ? *u : swap_float(*u)); +                printf("%4.3g ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : PA_FLOAT32_SWAP(*u));                  u++;              }              break;          } +        case PA_SAMPLE_S24LE: +        case PA_SAMPLE_S24BE: { +            uint8_t *u = d; + +            for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { +                printf("  0x%06x ", PA_READ24NE(u)); +                u += pa_frame_size(ss); +            } + +            break; +        } +          default:              pa_assert_not_reached();      } @@ -162,6 +178,23 @@ static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {              break;          } +        case PA_SAMPLE_S24_32NE: +        case PA_SAMPLE_S24_32RE: { +            uint32_t *u = d; + +            u[0] = 0x000001; +            u[1] = 0xFF0002; +            u[2] = 0x7F0003; +            u[3] = 0x800004; +            u[4] = 0x9f0005; +            u[5] = 0x3f0006; +            u[6] =    0x107; +            u[7] = 0xF00008; +            u[8] =   0x2009; +            u[9] =   0x210A; +            break; +        } +          case PA_SAMPLE_FLOAT32NE:          case PA_SAMPLE_FLOAT32RE: {              float *u = d; @@ -179,11 +212,28 @@ static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {              if (ss->format == PA_SAMPLE_FLOAT32RE)                  for (i = 0; i < 10; i++) -                    u[i] = swap_float(u[i]); +                    u[i] = PA_FLOAT32_SWAP(u[i]);              break;          } +        case PA_SAMPLE_S24NE: +        case PA_SAMPLE_S24RE: { +            uint8_t *u = d; + +            PA_WRITE24NE(u,    0x000001); +            PA_WRITE24NE(u+3,  0xFF0002); +            PA_WRITE24NE(u+6,  0x7F0003); +            PA_WRITE24NE(u+9,  0x800004); +            PA_WRITE24NE(u+12, 0x9f0005); +            PA_WRITE24NE(u+15, 0x3f0006); +            PA_WRITE24NE(u+18,    0x107); +            PA_WRITE24NE(u+21, 0xF00008); +            PA_WRITE24NE(u+24,   0x2009); +            PA_WRITE24NE(u+27,   0x210A); +            break; +        } +          default:              pa_assert_not_reached();      } @@ -211,7 +261,6 @@ int main(int argc, char *argv[]) {      for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) {          for (b.format = 0; b.format < PA_SAMPLE_MAX; b.format ++) { -              pa_resampler *forth, *back;              pa_memchunk i, j, k; @@ -229,14 +278,18 @@ int main(int argc, char *argv[]) {              pa_resampler_run(forth, &i, &j);              pa_resampler_run(back, &j, &k); +            printf("before:  ");              dump_block(&a, &i); +            printf("after :  ");              dump_block(&b, &j); +            printf("reverse: ");              dump_block(&a, &k);              pa_memblock_unref(j.memblock);              pa_memblock_unref(k.memblock);              pa_volume_memchunk(&i, &a, &v); +            printf("volume:  ");              dump_block(&a, &i);              pa_memblock_unref(i.memblock); diff --git a/src/tests/rtpoll-test.c b/src/tests/rtpoll-test.c index 4ac96446..1706cdfa 100644 --- a/src/tests/rtpoll-test.c +++ b/src/tests/rtpoll-test.c @@ -26,7 +26,6 @@  #include <pulsecore/log.h>  #include <pulsecore/rtpoll.h> -#include <pulsecore/rtsig.h>  static int before(pa_rtpoll_item *i) {      pa_log("before"); @@ -47,10 +46,6 @@ int main(int argc, char *argv[]) {      pa_rtpoll_item *i, *w;      struct pollfd *pollfd; -#ifdef SIGRTMIN -    pa_rtsig_configure(SIGRTMIN+10, SIGRTMAX); -#endif -      p = pa_rtpoll_new();      i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1); @@ -64,7 +59,6 @@ int main(int argc, char *argv[]) {      w = pa_rtpoll_item_new(p, PA_RTPOLL_NORMAL, 0);      pa_rtpoll_item_set_before_callback(w, worker); -    pa_rtpoll_install(p);      pa_rtpoll_set_timer_relative(p, 10000000); /* 10 s */      pa_rtpoll_run(p, 1); diff --git a/src/tests/rtstutter.c b/src/tests/rtstutter.c index f04d43af..a4b5d596 100644 --- a/src/tests/rtstutter.c +++ b/src/tests/rtstutter.c @@ -93,6 +93,8 @@ static void* work(void *p) {  int main(int argc, char*argv[]) {      unsigned n; +    pa_log_set_level(PA_LOG_DEBUG); +      srand((unsigned) time(NULL));      if (argc >= 3) { diff --git a/src/tests/sigbus-test.c b/src/tests/sigbus-test.c new file mode 100644 index 00000000..4b9ca840 --- /dev/null +++ b/src/tests/sigbus-test.c @@ -0,0 +1,70 @@ +/*** +  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 <fcntl.h> +#include <sys/mman.h> + +#include <pulsecore/memtrap.h> +#include <pulsecore/core-util.h> + +int main(int argc, char *argv[]) { +    void *p; +    int fd; +    pa_memtrap *m; + +    pa_log_set_level(PA_LOG_DEBUG); +    pa_memtrap_install(); + +    /* Create the memory map */ +    pa_assert_se((fd = open("sigbus-test-map", O_RDWR|O_TRUNC|O_CREAT, 0660)) >= 0); +    pa_assert_se(unlink("sigbus-test-map") == 0); +    pa_assert_se(ftruncate(fd, PA_PAGE_SIZE) >= 0); +    pa_assert_se((p = mmap(NULL, PA_PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) != MAP_FAILED); + +    /* Register memory map */ +    m = pa_memtrap_add(p, PA_PAGE_SIZE); + +    /* Use memory map */ +    pa_snprintf(p, PA_PAGE_SIZE, "This is a test that should work fine."); + +    /* Verify memory map */ +    pa_log("Let's see if this worked: %s", (char*) p); +    pa_log("And memtrap says it is good: %s", pa_yes_no(pa_memtrap_is_good(m))); + +    /* Invalidate mapping */ +    pa_assert_se(ftruncate(fd, 0) >= 0); + +    /* Use memory map */ +    pa_snprintf(p, PA_PAGE_SIZE, "This is a test that should fail but get caught."); + +    /* Verify memory map */ +    pa_log("Let's see if this worked: %s", (char*) p); +    pa_log("And memtrap says it is good: %s", pa_yes_no(pa_memtrap_is_good(m))); + +    pa_memtrap_remove(m); +    munmap(p, PA_PAGE_SIZE); + +    return 0; +} diff --git a/src/tests/smoother-test.c b/src/tests/smoother-test.c index 798dfed5..2cc9f58b 100644 --- a/src/tests/smoother-test.c +++ b/src/tests/smoother-test.c @@ -45,10 +45,12 @@ int main(int argc, char*argv[]) {      srand(0); +    pa_log_set_level(PA_LOG_DEBUG); +      for (m = 0, u = 0; u < PA_ELEMENTSOF(msec); u+= 2) {          msec[u] = m+1 + (rand() % 100) - 50; -        msec[u+1] = m + (rand() % 2000) - 1000; +        msec[u+1] = m + (rand() % 2000) - 1000   + 5000;          m += rand() % 100; @@ -59,7 +61,7 @@ int main(int argc, char*argv[]) {              msec[u+1] = 0;      } -    s = pa_smoother_new(700*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, TRUE, 6); +    s = pa_smoother_new(700*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, FALSE, TRUE, 6, 0, TRUE);      for (x = 0, u = 0; x < PA_USEC_PER_SEC * 10; x += PA_USEC_PER_MSEC) { @@ -67,6 +69,8 @@ int main(int argc, char*argv[]) {              pa_smoother_put(s, (pa_usec_t) msec[u] * PA_USEC_PER_MSEC, (pa_usec_t) msec[u+1] * PA_USEC_PER_MSEC);              printf("%i\t\t%i\n", msec[u],  msec[u+1]);              u += 2; + +            pa_smoother_resume(s, (pa_usec_t) msec[u] * PA_USEC_PER_MSEC, TRUE);          }          printf("%llu\t%llu\n", (unsigned long long) (x/PA_USEC_PER_MSEC), (unsigned long long) (pa_smoother_get(s, x)/PA_USEC_PER_MSEC)); diff --git a/src/tests/thread-mainloop-test.c b/src/tests/thread-mainloop-test.c index ad89414f..4696fb01 100644 --- a/src/tests/thread-mainloop-test.c +++ b/src/tests/thread-mainloop-test.c @@ -25,14 +25,16 @@  #include <unistd.h>  #include <stdio.h> +#include <pulse/rtclock.h>  #include <pulse/timeval.h>  #include <pulse/util.h>  #include <pulse/thread-mainloop.h>  #include <pulse/gccmacro.h>  #include <pulsecore/macro.h> +#include <pulsecore/core-rtclock.h> -static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void tcb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) {      pa_assert_se(pa_threaded_mainloop_in_thread(userdata));      fprintf(stderr, "TIME EVENT START\n");      pa_threaded_mainloop_signal(userdata, 1); @@ -53,9 +55,7 @@ int main(int argc, char *argv[]) {      pa_assert_se(!pa_threaded_mainloop_in_thread(m)); -    pa_gettimeofday(&tv); -    tv.tv_sec += 5; -    a->time_new(a, &tv, tcb, m); +    a->time_new(a, pa_timeval_rtstore(&tv, pa_rtclock_now() + 5 * PA_USEC_PER_SEC, TRUE), tcb, m);      fprintf(stderr, "waiting 5s (signal)\n");      pa_threaded_mainloop_wait(m); diff --git a/src/tests/voltest.c b/src/tests/voltest.c index 0c6d2ea6..2dcfa53c 100644 --- a/src/tests/voltest.c +++ b/src/tests/voltest.c @@ -9,6 +9,9 @@ int main(int argc, char *argv[]) {      float b;      pa_channel_map map; +    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))); +      for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 256) {          double dB = pa_sw_volume_to_dB(v); diff --git a/src/tests/volume-ui.py b/src/tests/volume-ui.py index 6dc1c47d..7909b801 100644 --- a/src/tests/volume-ui.py +++ b/src/tests/volume-ui.py @@ -111,6 +111,10 @@ class CVolume(Structure):      _set_fade.restype = c_void_p      _set_fade.argtypes = [c_void_p, c_void_p, c_float] +    _to_dB = libpulse.pa_sw_volume_to_dB +    _to_dB.restype = c_double +    _to_dB.argytpes = [c_uint32] +      def snprint(this):          s = create_string_buffer(320)          r = this._snprint(s, len(s), byref(this)) @@ -138,6 +142,12 @@ class CVolume(Structure):      def set_fade(this, cm, f):          return this._set_fade(byref(this), byref(cm), f) +    def to_dB(this, channel = None): +        if channel is None: +            return this._to_dB(this.max()) + +        return this._to_dB(this.values[channel]) +  cm = ChannelMap()  if len(sys.argv) > 1: @@ -149,7 +159,7 @@ v = CVolume()  v.channels = cm.channels  for i in range(cm.channels): -    v.values[i] = 65536/2 +    v.values[i] = 65536  title = cm.to_pretty_name()  if title is None: @@ -163,6 +173,7 @@ vbox = gtk.VBox(spacing=6)  channel_labels = {}  channel_scales = {} +channel_dB_labels = {}  def update_volume(update_channels = True, update_fade = True, update_balance = True, update_scale = True):      if update_channels: @@ -178,6 +189,11 @@ def update_volume(update_channels = True, update_fade = True, update_balance = T      if update_fade:          fade_scale.set_value(v.get_fade(cm)) +    for i in range(cm.channels): +        channel_dB_labels[i].set_label("%0.2f dB" % v.to_dB(i)) + +    value_dB_label.set_label("%0.2f dB" % v.to_dB()) +  def fade_value_changed(fs):      v.set_fade(cm, fade_scale.get_value())      update_volume(update_fade = False) @@ -200,19 +216,26 @@ for i in range(cm.channels):      vbox.pack_start(channel_labels[i], expand=False, fill=True)      channel_scales[i] = gtk.HScale() -    channel_scales[i].set_range(0, 65536) +    channel_scales[i].set_range(0, 65536*3/2)      channel_scales[i].set_digits(0)      channel_scales[i].set_value_pos(gtk.POS_RIGHT)      vbox.pack_start(channel_scales[i], expand=False, fill=True) +    channel_dB_labels[i] = gtk.Label("-xxx dB") +    channel_dB_labels[i].set_alignment(1, 1) +    vbox.pack_start(channel_dB_labels[i], expand=False, fill=True) +  value_label = gtk.Label("Value")  value_label.set_alignment(0, .5)  vbox.pack_start(value_label, expand=False, fill=True)  value_scale = gtk.HScale() -value_scale.set_range(0, 65536) +value_scale.set_range(0, 65536*3/2)  value_scale.set_value_pos(gtk.POS_RIGHT)  value_scale.set_digits(0)  vbox.pack_start(value_scale, expand=False, fill=True) +value_dB_label = gtk.Label("-xxx dB") +value_dB_label.set_alignment(1, 1) +vbox.pack_start(value_dB_label, expand=False, fill=True)  balance_label = gtk.Label("Balance")  balance_label.set_alignment(0, .5) diff --git a/src/utils/pabrowse.c b/src/utils/pabrowse.c index 288d44a9..a349e414 100644 --- a/src/utils/pabrowse.c +++ b/src/utils/pabrowse.c @@ -27,8 +27,11 @@  #include <assert.h>  #include <signal.h> -#include <pulse/pulseaudio.h>  #include <pulse/browser.h> +#include <pulse/pulseaudio.h> +#include <pulse/rtclock.h> + +#include <pulsecore/core-util.h>  static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {      fprintf(stderr, "Got signal, exiting\n"); @@ -127,7 +130,7 @@ int main(int argc, char *argv[]) {      assert(r == 0);      pa_signal_new(SIGINT, exit_signal_callback, NULL);      pa_signal_new(SIGTERM, exit_signal_callback, NULL); -    signal(SIGPIPE, SIG_IGN); +    pa_disable_sigpipe();      if (!(browser = pa_browser_new_full(pa_mainloop_get_api(mainloop), PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, &s))) {          fprintf(stderr, "pa_browse_new_full(): %s\n", s); diff --git a/src/utils/pacat.c b/src/utils/pacat.c index e886c15c..f00a32eb 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -35,8 +35,16 @@  #include <fcntl.h>  #include <locale.h> +#include <sndfile.h> +  #include <pulse/i18n.h>  #include <pulse/pulseaudio.h> +#include <pulse/rtclock.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sndfile-util.h>  #define TIME_EVENT_USEC 50000 @@ -53,35 +61,92 @@ static size_t buffer_length = 0, buffer_index = 0;  static pa_io_event* stdio_event = NULL; -static char *stream_name = NULL, *client_name = NULL, *device = NULL; +static pa_proplist *proplist = NULL; +static char *device = NULL; -static int verbose = 0; +static SNDFILE* sndfile = NULL; + +static pa_bool_t verbose = FALSE;  static pa_volume_t volume = PA_VOLUME_NORM; -static int volume_is_set = 0; +static pa_bool_t volume_is_set = FALSE;  static pa_sample_spec sample_spec = {      .format = PA_SAMPLE_S16LE,      .rate = 44100,      .channels = 2  }; +static pa_bool_t sample_spec_set = FALSE;  static pa_channel_map channel_map; -static int channel_map_set = 0; +static pa_bool_t channel_map_set = FALSE; + +static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL; +static sf_count_t (*writef_function)(SNDFILE *_sndfile, const void *ptr, sf_count_t frames) = NULL;  static pa_stream_flags_t flags = 0; -static size_t latency = 0, process_time=0; +static size_t latency = 0, process_time = 0; + +static pa_bool_t raw = TRUE; +static int file_format = -1;  /* A shortcut for terminating the application */  static void quit(int ret) { -    assert(mainloop_api); +    pa_assert(mainloop_api);      mainloop_api->quit(mainloop_api, ret);  } +/* Connection draining complete */ +static void context_drain_complete(pa_context*c, void *userdata) { +    pa_context_disconnect(c); +} + +/* Stream draining complete */ +static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + +    if (!success) { +        pa_log(_("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); +        quit(1); +    } + +    if (verbose) +        pa_log(_("Playback stream drained.\n")); + +    pa_stream_disconnect(stream); +    pa_stream_unref(stream); +    stream = NULL; + +    if (!pa_context_drain(context, context_drain_complete, NULL)) +        pa_context_disconnect(context); +    else { +        if (verbose) +            pa_log(_("Draining connection to server.\n")); +    } +} + +/* Start draining */ +static void start_drain(void) { + +    if (stream) { +        pa_operation *o; + +        pa_stream_set_write_callback(stream, NULL, NULL); + +        if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { +            pa_log(_("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context))); +            quit(1); +            return; +        } + +        pa_operation_unref(o); +    } else +        quit(0); +} +  /* Write some data to the stream */  static void do_stream_write(size_t length) {      size_t l; -    assert(length); +    pa_assert(length);      if (!buffer || !buffer_length)          return; @@ -91,7 +156,7 @@ static void do_stream_write(size_t length) {          l = buffer_length;      if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) { -        fprintf(stderr, _("pa_stream_write() failed: %s\n"), pa_strerror(pa_context_errno(context))); +        pa_log(_("pa_stream_write() failed: %s\n"), pa_strerror(pa_context_errno(context)));          quit(1);          return;      } @@ -108,53 +173,121 @@ static void do_stream_write(size_t length) {  /* This is called whenever new data may be written to the stream */  static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { -    assert(s); -    assert(length > 0); +    pa_assert(s); +    pa_assert(length > 0); -    if (stdio_event) -        mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); +    if (raw) { +        pa_assert(!sndfile); -    if (!buffer) -        return; +        if (stdio_event) +            mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); + +        if (!buffer) +            return; + +        do_stream_write(length); + +    } else { +        sf_count_t bytes; +        void *data; + +        pa_assert(sndfile); + +        data = pa_xmalloc(length); + +        if (readf_function) { +            size_t k = pa_frame_size(&sample_spec); + +            if ((bytes = readf_function(sndfile, data, (sf_count_t) (length/k))) > 0) +                bytes *= (sf_count_t) k; + +        } else +            bytes = sf_read_raw(sndfile, data, (sf_count_t) length); -    do_stream_write(length); +        if (bytes > 0) +            pa_stream_write(s, data, (size_t) bytes, pa_xfree, 0, PA_SEEK_RELATIVE); +        else +            pa_xfree(data); + +        if (bytes < (sf_count_t) length) +            start_drain(); +    }  }  /* This is called whenever new data may is available */  static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { -    const void *data; -    assert(s); -    assert(length > 0); -    if (stdio_event) -        mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); +    pa_assert(s); +    pa_assert(length > 0); -    if (pa_stream_peek(s, &data, &length) < 0) { -        fprintf(stderr, _("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); -        quit(1); -        return; -    } +    if (raw) { +        pa_assert(!sndfile); -    assert(data); -    assert(length > 0); +        if (stdio_event) +            mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); + + +        while (pa_stream_readable_size(s) > 0) { +            const void *data; + +            if (pa_stream_peek(s, &data, &length) < 0) { +                pa_log(_("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); +                quit(1); +                return; +            } + +            pa_assert(data); +            pa_assert(length > 0); + +            if (buffer) { +                buffer = pa_xrealloc(buffer, buffer_length + length); +                memcpy((uint8_t*) buffer + buffer_length, data, length); +                buffer_length += length; +            } else { +                buffer = pa_xmalloc(length); +                memcpy(buffer, data, length); +                buffer_length = length; +                buffer_index = 0; +            } +            pa_stream_drop(s); +        } -    if (buffer) { -        buffer = pa_xrealloc(buffer, buffer_length + length); -        memcpy((uint8_t*) buffer + buffer_length, data, length); -        buffer_length += length;      } else { -        buffer = pa_xmalloc(length); -        memcpy(buffer, data, length); -        buffer_length = length; -        buffer_index = 0; -    } +        pa_assert(sndfile); + +        while (pa_stream_readable_size(s) > 0) { +            sf_count_t bytes; +            const void *data; + +            if (pa_stream_peek(s, &data, &length) < 0) { +                pa_log(_("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); +                quit(1); +                return; +            } + +            pa_assert(data); +            pa_assert(length > 0); + +            if (writef_function) { +                size_t k = pa_frame_size(&sample_spec); + +                if ((bytes = writef_function(sndfile, data, (sf_count_t) (length/k))) > 0) +                    bytes *= (sf_count_t) k; + +            } else +                bytes = sf_write_raw(sndfile, data, (sf_count_t) length); -    pa_stream_drop(s); +            if (bytes < (sf_count_t) length) +                quit(1); + +            pa_stream_drop(s); +        } +    }  }  /* This routine is called whenever the stream state changes */  static void stream_state_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s);      switch (pa_stream_get_state(s)) {          case PA_STREAM_CREATING: @@ -162,29 +295,30 @@ static void stream_state_callback(pa_stream *s, void *userdata) {              break;          case PA_STREAM_READY: +              if (verbose) {                  const pa_buffer_attr *a;                  char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; -                fprintf(stderr, _("Stream successfully created.\n")); +                pa_log(_("Stream successfully created.\n"));                  if (!(a = pa_stream_get_buffer_attr(s))) -                    fprintf(stderr, _("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); +                    pa_log(_("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));                  else {                      if (mode == PLAYBACK) -                        fprintf(stderr, _("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq); +                        pa_log(_("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq);                      else { -                        assert(mode == RECORD); -                        fprintf(stderr, _("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize); +                        pa_assert(mode == RECORD); +                        pa_log(_("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize);                      }                  } -                fprintf(stderr, _("Using sample spec '%s', channel map '%s'.\n"), +                pa_log(_("Using sample spec '%s', channel map '%s'.\n"),                          pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),                          pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s))); -                fprintf(stderr, _("Connected to device %s (%u, %ssuspended).\n"), +                pa_log(_("Connected to device %s (%u, %ssuspended).\n"),                          pa_stream_get_device_name(s),                          pa_stream_get_device_index(s),                          pa_stream_is_suspended(s) ? "" : "not "); @@ -194,65 +328,72 @@ static void stream_state_callback(pa_stream *s, void *userdata) {          case PA_STREAM_FAILED:          default: -            fprintf(stderr, _("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); +            pa_log(_("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));              quit(1);      }  }  static void stream_suspended_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s);      if (verbose) {          if (pa_stream_is_suspended(s)) -            fprintf(stderr, _("Stream device suspended.%s \n"), CLEAR_LINE); +            pa_log(_("Stream device suspended.%s \n"), CLEAR_LINE);          else -            fprintf(stderr, _("Stream device resumed.%s \n"), CLEAR_LINE); +            pa_log(_("Stream device resumed.%s \n"), CLEAR_LINE);      }  }  static void stream_underflow_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s);      if (verbose) -        fprintf(stderr, _("Stream underrun.%s \n"),  CLEAR_LINE); +        pa_log(_("Stream underrun.%s \n"),  CLEAR_LINE);  }  static void stream_overflow_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s);      if (verbose) -        fprintf(stderr, _("Stream overrun.%s \n"), CLEAR_LINE); +        pa_log(_("Stream overrun.%s \n"), CLEAR_LINE);  }  static void stream_started_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s);      if (verbose) -        fprintf(stderr, _("Stream started.%s \n"), CLEAR_LINE); +        pa_log(_("Stream started.%s \n"), CLEAR_LINE);  }  static void stream_moved_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s); + +    if (verbose) +        pa_log(_("Stream moved to device %s (%u, %ssuspended).%s \n"), pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : _("not "),  CLEAR_LINE); +} + +static void stream_buffer_attr_callback(pa_stream *s, void *userdata) { +    pa_assert(s);      if (verbose) -        fprintf(stderr, _("Stream moved to device %s (%u, %ssuspended).%s \n"), pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : _("not "),  CLEAR_LINE); +        pa_log(_("Stream buffer attributes changed.%s \n"),  CLEAR_LINE);  }  static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *pl, void *userdata) {      char *t; -    assert(s); -    assert(name); -    assert(pl); +    pa_assert(s); +    pa_assert(name); +    pa_assert(pl);      t = pa_proplist_to_string_sep(pl, ", "); -    fprintf(stderr, "Got event '%s', properties '%s'\n", name, t); +    pa_log("Got event '%s', properties '%s'\n", name, t);      pa_xfree(t);  }  /* This is called whenever the context status changes */  static void context_state_callback(pa_context *c, void *userdata) { -    assert(c); +    pa_assert(c);      switch (pa_context_get_state(c)) {          case PA_CONTEXT_CONNECTING: @@ -264,14 +405,14 @@ static void context_state_callback(pa_context *c, void *userdata) {              int r;              pa_buffer_attr buffer_attr; -            assert(c); -            assert(!stream); +            pa_assert(c); +            pa_assert(!stream);              if (verbose) -                fprintf(stderr, _("Connection established.%s \n"), CLEAR_LINE); +                pa_log(_("Connection established.%s \n"), CLEAR_LINE); -            if (!(stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL))) { -                fprintf(stderr, _("pa_stream_new() failed: %s\n"), pa_strerror(pa_context_errno(c))); +            if (!(stream = pa_stream_new_with_proplist(c, NULL, &sample_spec, &channel_map, proplist))) { +                pa_log(_("pa_stream_new() failed: %s\n"), pa_strerror(pa_context_errno(c)));                  goto fail;              } @@ -284,6 +425,7 @@ static void context_state_callback(pa_context *c, void *userdata) {              pa_stream_set_overflow_callback(stream, stream_overflow_callback, NULL);              pa_stream_set_started_callback(stream, stream_started_callback, NULL);              pa_stream_set_event_callback(stream, stream_event_callback, NULL); +            pa_stream_set_buffer_attr_callback(stream, stream_buffer_attr_callback, NULL);              if (latency > 0) {                  memset(&buffer_attr, 0, sizeof(buffer_attr)); @@ -298,13 +440,13 @@ static void context_state_callback(pa_context *c, void *userdata) {              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) { -                    fprintf(stderr, _("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c))); +                    pa_log(_("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c)));                      goto fail;                  }              } else {                  if ((r = pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : NULL, flags)) < 0) { -                    fprintf(stderr, _("pa_stream_connect_record() failed: %s\n"), pa_strerror(pa_context_errno(c))); +                    pa_log(_("pa_stream_connect_record() failed: %s\n"), pa_strerror(pa_context_errno(c)));                      goto fail;                  }              } @@ -318,7 +460,7 @@ static void context_state_callback(pa_context *c, void *userdata) {          case PA_CONTEXT_FAILED:          default: -            fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); +            pa_log(_("Connection failure: %s\n"), pa_strerror(pa_context_errno(c)));              goto fail;      } @@ -329,42 +471,14 @@ fail:  } -/* Connection draining complete */ -static void context_drain_complete(pa_context*c, void *userdata) { -    pa_context_disconnect(c); -} - -/* Stream draining complete */ -static void stream_drain_complete(pa_stream*s, int success, void *userdata) { - -    if (!success) { -        fprintf(stderr, _("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); -        quit(1); -    } - -    if (verbose) -        fprintf(stderr, _("Playback stream drained.\n")); - -    pa_stream_disconnect(stream); -    pa_stream_unref(stream); -    stream = NULL; - -    if (!pa_context_drain(context, context_drain_complete, NULL)) -        pa_context_disconnect(context); -    else { -        if (verbose) -            fprintf(stderr, _("Draining connection to server.\n")); -    } -} -  /* New data on STDIN **/  static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {      size_t l, w = 0;      ssize_t r; -    assert(a == mainloop_api); -    assert(e); -    assert(stdio_event == e); +    pa_assert(a == mainloop_api); +    pa_assert(e); +    pa_assert(stdio_event == e);      if (buffer) {          mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -379,23 +493,12 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even      if ((r = read(fd, buffer, l)) <= 0) {          if (r == 0) {              if (verbose) -                fprintf(stderr, _("Got EOF.\n")); - -            if (stream) { -                pa_operation *o; +                pa_log(_("Got EOF.\n")); -                if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { -                    fprintf(stderr, _("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context))); -                    quit(1); -                    return; -                } - -                pa_operation_unref(o); -            } else -                quit(0); +            start_drain();          } else { -            fprintf(stderr, _("read() failed: %s\n"), strerror(errno)); +            pa_log(_("read() failed: %s\n"), strerror(errno));              quit(1);          } @@ -415,19 +518,19 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even  static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {      ssize_t r; -    assert(a == mainloop_api); -    assert(e); -    assert(stdio_event == e); +    pa_assert(a == mainloop_api); +    pa_assert(e); +    pa_assert(stdio_event == e);      if (!buffer) {          mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL);          return;      } -    assert(buffer_length); +    pa_assert(buffer_length);      if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) { -        fprintf(stderr, _("write() failed: %s\n"), strerror(errno)); +        pa_log(_("write() failed: %s\n"), strerror(errno));          quit(1);          mainloop_api->io_free(stdio_event); @@ -448,7 +551,7 @@ static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_eve  /* UNIX signal to quit recieved */  static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {      if (verbose) -        fprintf(stderr, _("Got signal, exiting.\n")); +        pa_log(_("Got signal, exiting.\n"));      quit(0);  } @@ -457,17 +560,17 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd      pa_usec_t l, usec;      int negative = 0; -    assert(s); +    pa_assert(s);      if (!success ||          pa_stream_get_time(s, &usec) < 0 ||          pa_stream_get_latency(s, &l, &negative) < 0) { -        fprintf(stderr, _("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context))); +        pa_log(_("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context)));          quit(1);          return;      } -    fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec.  \r"), +    pa_log(_("Time: %0.3f sec; Latency: %0.0f usec.  \r"),              (float) usec / 1000000,              (float) l * (negative?-1.0f:1.0f));  } @@ -481,54 +584,53 @@ static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int s      pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL));  } -static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { -    struct timeval next; - +static void time_event_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {      if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) {          pa_operation *o;          if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL))) -            fprintf(stderr, _("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context))); +            pa_log(_("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context)));          else              pa_operation_unref(o);      } -    pa_gettimeofday(&next); -    pa_timeval_add(&next, TIME_EVENT_USEC); - -    m->time_restart(e, &next); +    pa_context_rttime_restart(context, e, pa_rtclock_now() + TIME_EVENT_USEC);  }  static void help(const char *argv0) {      printf(_("%s [options]\n\n" -           "  -h, --help                            Show this help\n" -           "      --version                         Show version\n\n" -           "  -r, --record                          Create a connection for recording\n" -           "  -p, --playback                        Create a connection for playback\n\n" -           "  -v, --verbose                         Enable verbose operations\n\n" -           "  -s, --server=SERVER                   The name of the server to connect to\n" -           "  -d, --device=DEVICE                   The name of the sink/source to connect to\n" -           "  -n, --client-name=NAME                How to call this client on the server\n" -           "      --stream-name=NAME                How to call this stream on the server\n" -           "      --volume=VOLUME                   Specify the initial (linear) volume in range 0...65536\n" -           "      --rate=SAMPLERATE                 The sample rate in Hz (defaults to 44100)\n" -           "      --format=SAMPLEFORMAT             The sample type, one of s16le, s16be, u8, float32le,\n" -           "                                        float32be, ulaw, alaw, s32le, s32be (defaults to s16ne)\n" -           "      --channels=CHANNELS               The number of channels, 1 for mono, 2 for stereo\n" -           "                                        (defaults to 2)\n" -           "      --channel-map=CHANNELMAP          Channel map to use instead of the default\n" -           "      --fix-format                      Take the sample format from the sink the stream is\n" -           "                                        being connected to.\n" -           "      --fix-rate                        Take the sampling rate from the sink the stream is\n" -           "                                        being connected to.\n" -           "      --fix-channels                    Take the number of channels and the channel map\n" -           "                                        from the sink the stream is being connected to.\n" -           "      --no-remix                        Don't upmix or downmix channels.\n" -           "      --no-remap                        Map channels by index instead of name.\n" -           "      --latency=BYTES                   Request the specified latency in bytes.\n" -           "      --process-time=BYTES              Request the specified process time per request in bytes.\n") -           , -           argv0); +             "  -h, --help                            Show this help\n" +             "      --version                         Show version\n\n" +             "  -r, --record                          Create a connection for recording\n" +             "  -p, --playback                        Create a connection for playback\n\n" +             "  -v, --verbose                         Enable verbose operations\n\n" +             "  -s, --server=SERVER                   The name of the server to connect to\n" +             "  -d, --device=DEVICE                   The name of the sink/source to connect to\n" +             "  -n, --client-name=NAME                How to call this client on the server\n" +             "      --stream-name=NAME                How to call this stream on the server\n" +             "      --volume=VOLUME                   Specify the initial (linear) volume in range 0...65536\n" +             "      --rate=SAMPLERATE                 The sample rate in Hz (defaults to 44100)\n" +             "      --format=SAMPLEFORMAT             The sample type, one of s16le, s16be, u8, float32le,\n" +             "                                        float32be, ulaw, alaw, s32le, s32be, s24le, s24be,\n" +             "                                        s24-32le, s24-32be (defaults to s16ne)\n" +             "      --channels=CHANNELS               The number of channels, 1 for mono, 2 for stereo\n" +             "                                        (defaults to 2)\n" +             "      --channel-map=CHANNELMAP          Channel map to use instead of the default\n" +             "      --fix-format                      Take the sample format from the sink the stream is\n" +             "                                        being connected to.\n" +             "      --fix-rate                        Take the sampling rate from the sink the stream is\n" +             "                                        being connected to.\n" +             "      --fix-channels                    Take the number of channels and the channel map\n" +             "                                        from the sink the stream is being connected to.\n" +             "      --no-remix                        Don't upmix or downmix channels.\n" +             "      --no-remap                        Map channels by index instead of name.\n" +             "      --latency=BYTES                   Request the specified latency in bytes.\n" +             "      --process-time=BYTES              Request the specified process time per request in bytes.\n" +             "      --property=PROPERTY=VALUE         Set the specified property to the specified value.\n" +             "      --raw                             Record/play raw PCM data.\n" +             "      --file-format=FFORMAT             Record/play formatted PCM data.\n" +             "      --list-file-formats               List available file formats.\n") +           , argv0);  }  enum { @@ -545,14 +647,19 @@ enum {      ARG_NO_REMAP,      ARG_NO_REMIX,      ARG_LATENCY, -    ARG_PROCESS_TIME +    ARG_PROCESS_TIME, +    ARG_RAW, +    ARG_PROPERTY, +    ARG_FILE_FORMAT, +    ARG_LIST_FILE_FORMATS  };  int main(int argc, char *argv[]) {      pa_mainloop* m = NULL; -    int ret = 1, r, c; +    int ret = 1, c;      char *bn, *server = NULL;      pa_time_event *time_event = NULL; +    const char *filename = NULL;      static const struct option long_options[] = {          {"record",       0, NULL, 'r'}, @@ -576,21 +683,33 @@ int main(int argc, char *argv[]) {          {"no-remix",     0, NULL, ARG_NO_REMIX},          {"latency",      1, NULL, ARG_LATENCY},          {"process-time", 1, NULL, ARG_PROCESS_TIME}, +        {"property",     1, NULL, ARG_PROPERTY}, +        {"raw",          0, NULL, ARG_RAW}, +        {"file-format",  2, NULL, ARG_FILE_FORMAT}, +        {"list-file-formats", 0, NULL, ARG_LIST_FILE_FORMATS},          {NULL,           0, NULL, 0}      };      setlocale(LC_ALL, "");      bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); -    if (!(bn = strrchr(argv[0], '/'))) -        bn = argv[0]; -    else -        bn++; +    bn = pa_path_get_filename(argv[0]); -    if (strstr(bn, "rec") || strstr(bn, "mon")) +    if (strstr(bn, "play")) { +        mode = PLAYBACK; +        raw = FALSE; +    } else if (strstr(bn, "record")) {          mode = RECORD; -    else if (strstr(bn, "cat") || strstr(bn, "play")) +        raw = FALSE; +    } else if (strstr(bn, "cat")) {          mode = PLAYBACK; +        raw = TRUE; +    } if (strstr(bn, "rec") || strstr(bn, "mon")) { +        mode = RECORD; +        raw = TRUE; +    } + +    proplist = pa_proplist_new();      while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) { @@ -601,7 +720,12 @@ int main(int argc, char *argv[]) {                  goto quit;              case ARG_VERSION: -                printf(_("pacat %s\nCompiled with libpulse %s\nLinked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version()); +                printf(_("pacat %s\n" +                         "Compiled with libpulse %s\n" +                         "Linked with libpulse %s\n"), +                       PACKAGE_VERSION, +                       pa_get_headers_version(), +                       pa_get_library_version());                  ret = 0;                  goto quit; @@ -623,15 +747,36 @@ int main(int argc, char *argv[]) {                  server = pa_xstrdup(optarg);                  break; -            case 'n': -                pa_xfree(client_name); -                client_name = pa_xstrdup(optarg); +            case 'n': { +                char *t; + +                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_xfree(t); +                    goto quit; +                } + +                pa_xfree(t);                  break; +            } -            case ARG_STREAM_NAME: -                pa_xfree(stream_name); -                stream_name = pa_xstrdup(optarg); +            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) { + +                    pa_log(_("Invalid stream name '%s'\n"), t ? t : optarg); +                    pa_xfree(t); +                    goto quit; +                } + +                pa_xfree(t);                  break; +            }              case 'v':                  verbose = 1; @@ -640,29 +785,32 @@ int main(int argc, char *argv[]) {              case ARG_VOLUME: {                  int v = atoi(optarg);                  volume = v < 0 ? 0U : (pa_volume_t) v; -                volume_is_set = 1; +                volume_is_set = TRUE;                  break;              }              case ARG_CHANNELS:                  sample_spec.channels = (uint8_t) atoi(optarg); +                sample_spec_set = TRUE;                  break;              case ARG_SAMPLEFORMAT:                  sample_spec.format = pa_parse_sample_format(optarg); +                sample_spec_set = TRUE;                  break;              case ARG_SAMPLERATE:                  sample_spec.rate = (uint32_t) atoi(optarg); +                sample_spec_set = TRUE;                  break;              case ARG_CHANNELMAP:                  if (!pa_channel_map_parse(&channel_map, optarg)) { -                    fprintf(stderr, _("Invalid channel map '%s'\n"), optarg); +                    pa_log(_("Invalid channel map '%s'\n"), optarg);                      goto quit;                  } -                channel_map_set = 1; +                channel_map_set = TRUE;                  break;              case ARG_FIX_CHANNELS: @@ -687,100 +835,223 @@ int main(int argc, char *argv[]) {              case ARG_LATENCY:                  if (((latency = (size_t) atoi(optarg))) <= 0) { -                    fprintf(stderr, _("Invalid latency specification '%s'\n"), optarg); +                    pa_log(_("Invalid latency specification '%s'\n"), optarg);                      goto quit;                  }                  break;              case ARG_PROCESS_TIME:                  if (((process_time = (size_t) atoi(optarg))) <= 0) { -                    fprintf(stderr, _("Invalid process time specification '%s'\n"), optarg); +                    pa_log(_("Invalid process time specification '%s'\n"), optarg); +                    goto quit; +                } +                break; + +            case ARG_PROPERTY: { +                char *t; + +                if (!(t = pa_locale_to_utf8(optarg)) || +                    pa_proplist_setp(proplist, t) < 0) { + +                    pa_xfree(t); +                    pa_log(_("Invalid property '%s'\n"), optarg);                      goto quit;                  } + +                pa_xfree(t); +                break; +            } + +            case ARG_RAW: +                raw = TRUE; +                break; + +            case ARG_FILE_FORMAT: +                raw = FALSE; + +                if (optarg) { +                    if ((file_format = pa_sndfile_format_from_string(optarg)) < 0) { +                        pa_log(_("Unknown file format %s."), optarg); +                        goto quit; +                    } +                } + +                raw = FALSE;                  break; +            case ARG_LIST_FILE_FORMATS: +                pa_sndfile_dump_formats(); +                ret = 0; +                goto quit; +              default:                  goto quit;          }      }      if (!pa_sample_spec_valid(&sample_spec)) { -        fprintf(stderr, _("Invalid sample specification\n")); +        pa_log(_("Invalid sample specification\n"));          goto quit;      } -    if (channel_map_set && pa_channel_map_compatible(&channel_map, &sample_spec)) { -        fprintf(stderr, _("Channel map doesn't match sample specification\n")); -        goto quit; -    } +    if (optind+1 == argc) { +        int fd; -    if (verbose) { -        char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; -        pa_sample_spec_snprint(t, sizeof(t), &sample_spec); -        fprintf(stderr, _("Opening a %s stream with sample specification '%s'.\n"), mode == RECORD ? _("recording") : _("playback"), t); +        filename = argv[optind]; + +        if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { +            pa_log(_("open(): %s\n"), strerror(errno)); +            goto quit; +        } + +        if (dup2(fd, mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO) < 0) { +            pa_log(_("dup2(): %s\n"), strerror(errno)); +            goto quit; +        } + +        pa_close(fd); + +    } else if (optind+1 <= argc) { +        pa_log(_("Too many arguments.\n")); +        goto quit;      } -    if (!(optind >= argc)) { -        if (optind+1 == argc) { -            int fd; +    if (!raw) { +        SF_INFO sfi; +        pa_zero(sfi); -            if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { -                fprintf(stderr, _("open(): %s\n"), strerror(errno)); +        if (mode == RECORD) { +            /* This might patch up the sample spec */ +            if (pa_sndfile_write_sample_spec(&sfi, &sample_spec) < 0) { +                pa_log(_("Failed to generate sample specification for file.\n"));                  goto quit;              } -            if (dup2(fd, mode == PLAYBACK ? 0 : 1) < 0) { -                fprintf(stderr, _("dup2(): %s\n"), strerror(errno)); +            /* Transparently upgrade classic .wav to wavex for multichannel audio */ +            if (file_format <= 0) { +                if ((sample_spec.channels == 2 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_LEFT && +                                                                        channel_map.map[1] == PA_CHANNEL_POSITION_RIGHT))) || +                    (sample_spec.channels == 1 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_MONO)))) +                    file_format = SF_FORMAT_WAV; +                else +                    file_format = SF_FORMAT_WAVEX; +            } + +            sfi.format |= file_format; +        } + +        if (!(sndfile = sf_open_fd(mode == RECORD ? STDOUT_FILENO : STDIN_FILENO, +                                   mode == RECORD ? SFM_WRITE : SFM_READ, +                                   &sfi, 0))) { +            pa_log(_("Failed to open audio file.\n")); +            goto quit; +        } + +        if (mode == PLAYBACK) { +            if (sample_spec_set) +                pa_log(_("Warning: specified sample specification will be overwritten with specification from file.\n")); + +            if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { +                pa_log(_("Failed to determine sample specification from file.\n"));                  goto quit;              } +            sample_spec_set = TRUE; + +            if (!channel_map_set) { +                /* Allow the user to overwrite the channel map on the command line */ +                if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { +                    if (sample_spec.channels > 2) +                        pa_log(_("Warning: Failed to determine channel map from file.\n")); +                } else +                    channel_map_set = TRUE; +            } +        } +    } -            close(fd); +    if (!channel_map_set) +        pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); -            if (!stream_name) -                stream_name = pa_xstrdup(argv[optind]); +    if (!pa_channel_map_compatible(&channel_map, &sample_spec)) { +        pa_log(_("Channel map doesn't match sample specification\n")); +        goto quit; +    } -        } else { -            fprintf(stderr, _("Too many arguments.\n")); -            goto quit; +    if (!raw) { +        pa_proplist *sfp; + +        if (mode == PLAYBACK) +            readf_function = pa_sndfile_readf_function(&sample_spec); +        else { +            if (pa_sndfile_write_channel_map(sndfile, &channel_map) < 0) +                pa_log(_("Warning: failed to write channel map to file.\n")); + +            writef_function = pa_sndfile_writef_function(&sample_spec); +        } + +        /* Fill in libsndfile prop list data */ +        sfp = pa_proplist_new(); +        pa_sndfile_init_proplist(sndfile, sfp); +        pa_proplist_update(proplist, PA_UPDATE_MERGE, sfp); +        pa_proplist_free(sfp); +    } + +    if (verbose) { +        char tss[PA_SAMPLE_SPEC_SNPRINT_MAX], tcm[PA_CHANNEL_MAP_SNPRINT_MAX]; + +        pa_log(_("Opening a %s stream with sample specification '%s' and channel map '%s'.\n"), +                mode == RECORD ? _("recording") : _("playback"), +                pa_sample_spec_snprint(tss, sizeof(tss), &sample_spec), +                pa_channel_map_snprint(tcm, sizeof(tcm), &channel_map)); +    } + +    /* Fill in client name if none was set */ +    if (!pa_proplist_contains(proplist, PA_PROP_APPLICATION_NAME)) { +        char *t; + +        if ((t = pa_locale_to_utf8(bn))) { +            pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t); +            pa_xfree(t);          }      } -    if (!client_name) -        client_name = pa_xstrdup(bn); +    /* Fill in media name if none was set */ +    if (!pa_proplist_contains(proplist, PA_PROP_MEDIA_NAME)) { +        const char *t; -    if (!stream_name) -        stream_name = pa_xstrdup(client_name); +        if ((t = filename) || +            (t = pa_proplist_gets(proplist, PA_PROP_APPLICATION_NAME))) +            pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t); +    }      /* Set up a new main loop */      if (!(m = pa_mainloop_new())) { -        fprintf(stderr, _("pa_mainloop_new() failed.\n")); +        pa_log(_("pa_mainloop_new() failed.\n"));          goto quit;      }      mainloop_api = pa_mainloop_get_api(m); -    r = pa_signal_init(mainloop_api); -    assert(r == 0); +    pa_assert_se(pa_signal_init(mainloop_api) == 0);      pa_signal_new(SIGINT, exit_signal_callback, NULL);      pa_signal_new(SIGTERM, exit_signal_callback, NULL);  #ifdef SIGUSR1      pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL);  #endif -#ifdef SIGPIPE -    signal(SIGPIPE, SIG_IGN); -#endif - -    if (!(stdio_event = mainloop_api->io_new(mainloop_api, -                                             mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, -                                             mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, -                                             mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { -        fprintf(stderr, _("io_new() failed.\n")); -        goto quit; +    pa_disable_sigpipe(); + +    if (raw) { +        if (!(stdio_event = mainloop_api->io_new(mainloop_api, +                                                 mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, +                                                 mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, +                                                 mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { +            pa_log(_("io_new() failed.\n")); +            goto quit; +        }      }      /* Create a new connection context */ -    if (!(context = pa_context_new(mainloop_api, client_name))) { -        fprintf(stderr, _("pa_context_new() failed.\n")); +    if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { +        pa_log(_("pa_context_new() failed.\n"));          goto quit;      } @@ -788,25 +1059,20 @@ int main(int argc, char *argv[]) {      /* Connect the context */      if (pa_context_connect(context, server, 0, NULL) < 0) { -        fprintf(stderr, _("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); +        pa_log(_("pa_context_connect() failed: %s\n"), pa_strerror(pa_context_errno(context)));          goto quit;      }      if (verbose) { -        struct timeval tv; - -        pa_gettimeofday(&tv); -        pa_timeval_add(&tv, TIME_EVENT_USEC); - -        if (!(time_event = mainloop_api->time_new(mainloop_api, &tv, time_event_callback, NULL))) { -            fprintf(stderr, _("time_new() failed.\n")); +        if (!(time_event = pa_context_rttime_new(context, pa_rtclock_now() + TIME_EVENT_USEC, time_event_callback, NULL))) { +            pa_log(_("pa_context_rttime_new() failed.\n"));              goto quit;          }      }      /* Run the main loop */      if (pa_mainloop_run(m, &ret) < 0) { -        fprintf(stderr, _("pa_mainloop_run() failed.\n")); +        pa_log(_("pa_mainloop_run() failed.\n"));          goto quit;      } @@ -818,12 +1084,12 @@ quit:          pa_context_unref(context);      if (stdio_event) { -        assert(mainloop_api); +        pa_assert(mainloop_api);          mainloop_api->io_free(stdio_event);      }      if (time_event) { -        assert(mainloop_api); +        pa_assert(mainloop_api);          mainloop_api->time_free(time_event);      } @@ -836,8 +1102,12 @@ quit:      pa_xfree(server);      pa_xfree(device); -    pa_xfree(client_name); -    pa_xfree(stream_name); + +    if (sndfile) +        sf_close(sndfile); + +    if (proplist) +        pa_proplist_free(proplist);      return ret;  } diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index d94f2665..ac60a0bc 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -38,11 +38,13 @@  #include <pulse/xmalloc.h>  #include <pulse/i18n.h> +#include <pulsecore/macro.h>  #include <pulsecore/core-util.h>  #include <pulsecore/log.h>  #include <pulsecore/pid.h>  int main(int argc, char*argv[]) { +      pid_t pid ;      int fd = -1;      int ret = 1, i; @@ -56,7 +58,7 @@ int main(int argc, char*argv[]) {      bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);      if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) { -        pa_log("No PulseAudio daemon running, or not running as session daemon."); +        pa_log(_("No PulseAudio daemon running, or not running as session daemon."));          goto fail;      } @@ -65,7 +67,7 @@ int main(int argc, char*argv[]) {          goto fail;      } -    memset(&sa, 0, sizeof(sa)); +    pa_zero(sa);      sa.sun_family = AF_UNIX;      if (!(cli = pa_runtime_path("cli"))) @@ -147,9 +149,9 @@ int main(int argc, char*argv[]) {          if (FD_ISSET(0, &ifds)) {              ssize_t r; -            assert(!ibuf_length); +            pa_assert(!ibuf_length); -            if ((r = read(0, ibuf, sizeof(ibuf))) <= 0) { +            if ((r = pa_read(0, ibuf, sizeof(ibuf), NULL)) <= 0) {                  if (r < 0) {                      pa_log(_("read(): %s"), strerror(errno));                      goto fail; @@ -164,9 +166,9 @@ int main(int argc, char*argv[]) {          if (FD_ISSET(fd, &ifds)) {              ssize_t r; -            assert(!obuf_length); +            pa_assert(!obuf_length); -            if ((r = read(fd, obuf, sizeof(obuf))) <= 0) { +            if ((r = pa_read(fd, obuf, sizeof(obuf), NULL)) <= 0) {                  if (r < 0) {                      pa_log(_("read(): %s"), strerror(errno));                      goto fail; @@ -181,9 +183,9 @@ int main(int argc, char*argv[]) {          if (FD_ISSET(1, &ofds)) {              ssize_t r; -            assert(obuf_length); +            pa_assert(obuf_length); -            if ((r = write(1, obuf + obuf_index, obuf_length)) < 0) { +            if ((r = pa_write(1, obuf + obuf_index, obuf_length, NULL)) < 0) {                  pa_log(_("write(): %s"), strerror(errno));                  goto fail;              } @@ -195,9 +197,9 @@ int main(int argc, char*argv[]) {          if (FD_ISSET(fd, &ofds)) {              ssize_t r; -            assert(ibuf_length); +            pa_assert(ibuf_length); -            if ((r = write(fd, ibuf + ibuf_index, ibuf_length)) < 0) { +            if ((r = pa_write(fd, ibuf + ibuf_index, ibuf_length, NULL)) < 0) {                  pa_log(_("write(): %s"), strerror(errno));                  goto fail;              } @@ -207,14 +209,14 @@ int main(int argc, char*argv[]) {          }          if (ibuf_length <= 0 && ibuf_eof && !ibuf_closed) { -            close(0); +            pa_close(0);              shutdown(fd, SHUT_WR);              ibuf_closed = TRUE;          }          if (obuf_length <= 0 && obuf_eof && !obuf_closed) {              shutdown(fd, SHUT_RD); -            close(1); +            pa_close(1);              obuf_closed = TRUE;          }      } diff --git a/src/utils/pactl.c b/src/utils/pactl.c index de1c6d3d..c8c3a437 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -38,26 +38,46 @@  #include <pulse/i18n.h>  #include <pulse/pulseaudio.h> + +#include <pulsecore/macro.h>  #include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sndfile-util.h> -#define BUFSIZE 1024 +#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, *module_name = NULL, *module_args = NULL, *card_name = NULL, *profile_name = NULL; -static uint32_t sink_input_idx = PA_INVALID_INDEX, source_output_idx = PA_INVALID_INDEX; +static char +    *device = NULL, +    *sample_name = NULL, +    *sink_name = NULL, +    *source_name = NULL, +    *module_name = NULL, +    *module_args = NULL, +    *card_name = NULL, +    *profile_name = NULL, +    *port_name = NULL; + +static uint32_t +    sink_input_idx = PA_INVALID_INDEX, +    source_output_idx = PA_INVALID_INDEX; +  static uint32_t module_index; -static int suspend; +static pa_bool_t suspend; + +static pa_proplist *proplist = NULL;  static SNDFILE *sndfile = NULL;  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 int nl = 0; +static pa_bool_t nl = FALSE;  static enum {      NONE, @@ -73,15 +93,16 @@ static enum {      UNLOAD_MODULE,      SUSPEND_SINK,      SUSPEND_SOURCE, -    SET_CARD_PROFILE +    SET_CARD_PROFILE, +    SET_SINK_PORT, +    SET_SOURCE_PORT  } action = NONE;  static void quit(int ret) { -    assert(mainloop_api); +    pa_assert(mainloop_api);      mainloop_api->quit(mainloop_api, ret);  } -  static void context_drain_complete(pa_context *c, void *userdata) {      pa_context_disconnect(c);  } @@ -94,9 +115,8 @@ static void drain(void) {          pa_operation_unref(o);  } -  static void complete_action(void) { -    assert(actions > 0); +    pa_assert(actions > 0);      if (!(--actions))          drain(); @@ -105,7 +125,7 @@ static void complete_action(void) {  static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) {      char s[128];      if (!i) { -        fprintf(stderr, _("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -126,7 +146,7 @@ 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) { -        fprintf(stderr, _("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -175,7 +195,7 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -185,11 +205,11 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE;      printf(_("Sink #%u\n"               "\tState: %s\n" @@ -234,6 +254,18 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_             pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t"));      pa_xfree(pl); + +    if (i->ports) { +        pa_sink_port_info **p; + +        printf(_("\tPorts:\n")); +        for (p = i->ports; *p; p++) +            printf("\t\t%s: %s (priority. %u)\n", (*p)->name, (*p)->description, (*p)->priority); +    } + +    if (i->active_port) +        printf(_("\tActive Port: %s\n"), +               i->active_port->name);  }  static void get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata) { @@ -255,7 +287,7 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -265,11 +297,11 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE;      printf(_("Source #%u\n"               "\tState: %s\n" @@ -314,6 +346,18 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int             pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t"));      pa_xfree(pl); + +    if (i->ports) { +        pa_source_port_info **p; + +        printf(_("\tPorts:\n")); +        for (p = i->ports; *p; p++) +            printf("\t\t%s: %s (priority. %u)\n", (*p)->name, (*p)->description, (*p)->priority); +    } + +    if (i->active_port) +        printf(_("\tActive Port: %s\n"), +               i->active_port->name);  }  static void get_module_info_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) { @@ -321,7 +365,7 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -331,13 +375,13 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE; -    snprintf(t, sizeof(t), "%u", i->n_used); +    pa_snprintf(t, sizeof(t), "%u", i->n_used);      printf(_("Module #%u\n"               "\tName: %s\n" @@ -358,7 +402,7 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -368,13 +412,13 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE; -    snprintf(t, sizeof(t), "%u", i->owner_module); +    pa_snprintf(t, sizeof(t), "%u", i->owner_module);      printf(_("Client #%u\n"               "\tDriver: %s\n" @@ -393,7 +437,7 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c)));          complete_action();          return;      } @@ -403,13 +447,13 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE; -    snprintf(t, sizeof(t), "%u", i->owner_module); +    pa_snprintf(t, sizeof(t), "%u", i->owner_module);      printf(_("Card #%u\n"               "\tName: %s\n" @@ -442,7 +486,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -452,14 +496,14 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE; -    snprintf(t, sizeof(t), "%u", i->owner_module); -    snprintf(k, sizeof(k), "%u", i->client); +    pa_snprintf(t, sizeof(t), "%u", i->owner_module); +    pa_snprintf(k, sizeof(k), "%u", i->client);      printf(_("Sink Input #%u\n"               "\tDriver: %s\n" @@ -500,7 +544,7 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -510,15 +554,15 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE; -    snprintf(t, sizeof(t), "%u", i->owner_module); -    snprintf(k, sizeof(k), "%u", i->client); +    pa_snprintf(t, sizeof(t), "%u", i->owner_module); +    pa_snprintf(k, sizeof(k), "%u", i->client);      printf(_("Source Output #%u\n"               "\tDriver: %s\n" @@ -551,7 +595,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int      char *pl;      if (is_last < 0) { -        fprintf(stderr, _("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -561,11 +605,11 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int          return;      } -    assert(i); +    pa_assert(i);      if (nl)          printf("\n"); -    nl = 1; +    nl = TRUE;      pa_bytes_snprint(t, sizeof(t), i->bytes); @@ -599,7 +643,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) { -        fprintf(stderr, _("Failure: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -609,7 +653,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) { -        fprintf(stderr, _("Failure: %s\n"), pa_strerror(pa_context_errno(c))); +        pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c)));          quit(1);          return;      } @@ -620,7 +664,7 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) {  }  static void stream_state_callback(pa_stream *s, void *userdata) { -    assert(s); +    pa_assert(s);      switch (pa_stream_get_state(s)) {          case PA_STREAM_CREATING: @@ -633,7 +677,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) {          case PA_STREAM_FAILED:          default: -            fprintf(stderr, _("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); +            pa_log(_("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));              quit(1);      }  } @@ -641,16 +685,16 @@ static void stream_state_callback(pa_stream *s, void *userdata) {  static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {      sf_count_t l;      float *d; -    assert(s && length && sndfile); +    pa_assert(s && length && sndfile);      d = pa_xmalloc(length); -    assert(sample_length >= length); +    pa_assert(sample_length >= length);      l = (sf_count_t) (length/pa_frame_size(&sample_spec));      if ((sf_readf_float(sndfile, d, l)) != l) {          pa_xfree(d); -        fprintf(stderr, _("Premature end of file\n")); +        pa_log(_("Premature end of file\n"));          quit(1);          return;      } @@ -666,7 +710,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {  }  static void context_state_callback(pa_context *c, void *userdata) { -    assert(c); +    pa_assert(c);      switch (pa_context_get_state(c)) {          case PA_CONTEXT_CONNECTING:          case PA_CONTEXT_AUTHORIZING: @@ -691,7 +735,7 @@ static void context_state_callback(pa_context *c, void *userdata) {                  case UPLOAD_SAMPLE:                      sample_stream = pa_stream_new(c, sample_name, &sample_spec, NULL); -                    assert(sample_stream); +                    pa_assert(sample_stream);                      pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL);                      pa_stream_set_write_callback(sample_stream, stream_write_callback, NULL); @@ -748,8 +792,16 @@ static void context_state_callback(pa_context *c, void *userdata) {                      pa_operation_unref(pa_context_set_card_profile_by_name(c, card_name, profile_name, simple_callback, NULL));                      break; +                case SET_SINK_PORT: +                    pa_operation_unref(pa_context_set_sink_port_by_name(c, sink_name, port_name, simple_callback, NULL)); +                    break; + +                case SET_SOURCE_PORT: +                    pa_operation_unref(pa_context_set_source_port_by_name(c, source_name, port_name, simple_callback, NULL)); +                    break; +                  default: -                    assert(0); +                    pa_assert_not_reached();              }              break; @@ -759,13 +811,13 @@ static void context_state_callback(pa_context *c, void *userdata) {          case PA_CONTEXT_FAILED:          default: -            fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); +            pa_log(_("Connection failure: %s\n"), 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) { -    fprintf(stderr, _("Got SIGINT, exiting.\n")); +    pa_log(_("Got SIGINT, exiting.\n"));      quit(0);  } @@ -783,21 +835,24 @@ static void help(const char *argv0) {               "%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\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"               "  -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);  } -enum { ARG_VERSION = 256 }; +enum { +    ARG_VERSION = 256 +};  int main(int argc, char *argv[]) {      pa_mainloop* m = NULL; -    char tmp[PATH_MAX]; -    int ret = 1, r, c; -    char *server = NULL, *client_name = NULL, *bn; +    int ret = 1, c; +    char *server = NULL, *bn;      static const struct option long_options[] = {          {"server",      1, NULL, 's'}, @@ -810,10 +865,9 @@ int main(int argc, char *argv[]) {      setlocale(LC_ALL, "");      bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); -    if (!(bn = strrchr(argv[0], '/'))) -        bn = argv[0]; -    else -        bn++; +    bn = pa_path_get_filename(argv[0]); + +    proplist = pa_proplist_new();      while ((c = getopt_long(argc, argv, "s:n:h", long_options, NULL)) != -1) {          switch (c) { @@ -837,66 +891,74 @@ int main(int argc, char *argv[]) {                  server = pa_xstrdup(optarg);                  break; -            case 'n': -                pa_xfree(client_name); -                client_name = pa_xstrdup(optarg); +            case 'n': { +                char *t; + +                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_xfree(t); +                    goto quit; +                } + +                pa_xfree(t);                  break; +            }              default:                  goto quit;          }      } -    if (!client_name) -        client_name = pa_xstrdup(bn); -      if (optind < argc) { -        if (!strcmp(argv[optind], "stat")) +        if (pa_streq(argv[optind], "stat"))              action = STAT; -        else if (!strcmp(argv[optind], "exit")) +        else if (pa_streq(argv[optind], "exit"))              action = EXIT; -        else if (!strcmp(argv[optind], "list")) +        else if (pa_streq(argv[optind], "list"))              action = LIST; -        else if (!strcmp(argv[optind], "upload-sample")) { -            struct SF_INFO sfinfo; +        else if (pa_streq(argv[optind], "upload-sample")) { +            struct SF_INFO sfi;              action = UPLOAD_SAMPLE;              if (optind+1 >= argc) { -                fprintf(stderr, _("Please specify a sample file to load\n")); +                pa_log(_("Please specify a sample file to load\n"));                  goto quit;              }              if (optind+2 < argc)                  sample_name = pa_xstrdup(argv[optind+2]);              else { -                char *f = strrchr(argv[optind+1], '/'); -                size_t n; -                if (f) -                    f++; -                else -                    f = argv[optind]; - -                n = strcspn(f, "."); -                strncpy(tmp, f, n); -                tmp[n] = 0; -                sample_name = pa_xstrdup(tmp); +                char *f = pa_path_get_filename(argv[optind+1]); +                sample_name = pa_xstrndup(f, strcspn(f, "."));              } -            memset(&sfinfo, 0, sizeof(sfinfo)); -            if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfinfo))) { -                fprintf(stderr, _("Failed to open sound file.\n")); +            pa_zero(sfi); +            if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfi))) { +                pa_log(_("Failed to open sound file.\n"));                  goto quit;              } +            if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { +                pa_log(_("Failed to determine sample specification from file.\n")); +                goto quit; +            }              sample_spec.format = PA_SAMPLE_FLOAT32; -            sample_spec.rate = (uint32_t) sfinfo.samplerate; -            sample_spec.channels = (uint8_t) sfinfo.channels; -            sample_length = (size_t)sfinfo.frames*pa_frame_size(&sample_spec); -        } else if (!strcmp(argv[optind], "play-sample")) { +            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_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); +            } + +            pa_assert(pa_channel_map_compatible(&channel_map, &sample_spec)); +            sample_length = (size_t) sfi.frames*pa_frame_size(&sample_spec); + +        } else if (pa_streq(argv[optind], "play-sample")) {              action = PLAY_SAMPLE;              if (argc != optind+2 && argc != optind+3) { -                fprintf(stderr, _("You have to specify a sample name to play\n")); +                pa_log(_("You have to specify a sample name to play\n"));                  goto quit;              } @@ -905,33 +967,36 @@ int main(int argc, char *argv[]) {              if (optind+2 < argc)                  device = pa_xstrdup(argv[optind+2]); -        } else if (!strcmp(argv[optind], "remove-sample")) { +        } else if (pa_streq(argv[optind], "remove-sample")) {              action = REMOVE_SAMPLE;              if (argc != optind+2) { -                fprintf(stderr, _("You have to specify a sample name to remove\n")); +                pa_log(_("You have to specify a sample name to remove\n"));                  goto quit;              }              sample_name = pa_xstrdup(argv[optind+1]); -        } else if (!strcmp(argv[optind], "move-sink-input")) { + +        } else if (pa_streq(argv[optind], "move-sink-input")) {              action = MOVE_SINK_INPUT;              if (argc != optind+3) { -                fprintf(stderr, _("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\n"));                  goto quit;              }              sink_input_idx = (uint32_t) atoi(argv[optind+1]);              sink_name = pa_xstrdup(argv[optind+2]); -        } else if (!strcmp(argv[optind], "move-source-output")) { + +        } else if (pa_streq(argv[optind], "move-source-output")) {              action = MOVE_SOURCE_OUTPUT;              if (argc != optind+3) { -                fprintf(stderr, _("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\n"));                  goto quit;              }              source_output_idx = (uint32_t) atoi(argv[optind+1]);              source_name = pa_xstrdup(argv[optind+2]); -        } else if (!strcmp(argv[optind], "load-module")) { + +        } else if (pa_streq(argv[optind], "load-module")) {              int i;              size_t n = 0;              char *p; @@ -939,7 +1004,7 @@ int main(int argc, char *argv[]) {              action = LOAD_MODULE;              if (argc <= optind+1) { -                fprintf(stderr, _("You have to specify a module name and arguments.\n")); +                pa_log(_("You have to specify a module name and arguments.\n"));                  goto quit;              } @@ -955,21 +1020,21 @@ int main(int argc, char *argv[]) {                      p += sprintf(p, "%s%s", p == module_args ? "" : " ", argv[i]);              } -        } else if (!strcmp(argv[optind], "unload-module")) { +        } else if (pa_streq(argv[optind], "unload-module")) {              action = UNLOAD_MODULE;              if (argc != optind+2) { -                fprintf(stderr, _("You have to specify a module index\n")); +                pa_log(_("You have to specify a module index\n"));                  goto quit;              }              module_index = (uint32_t) atoi(argv[optind+1]); -        } else if (!strcmp(argv[optind], "suspend-sink")) { +        } else if (pa_streq(argv[optind], "suspend-sink")) {              action = SUSPEND_SINK;              if (argc > optind+3 || optind+1 >= argc) { -                fprintf(stderr, _("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.\n"));                  goto quit;              } @@ -978,11 +1043,11 @@ int main(int argc, char *argv[]) {              if (argc > optind+2)                  sink_name = pa_xstrdup(argv[optind+1]); -        } else if (!strcmp(argv[optind], "suspend-source")) { +        } else if (pa_streq(argv[optind], "suspend-source")) {              action = SUSPEND_SOURCE;              if (argc > optind+3 || optind+1 >= argc) { -                fprintf(stderr, _("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.\n"));                  goto quit;              } @@ -990,18 +1055,40 @@ int main(int argc, char *argv[]) {              if (argc > optind+2)                  source_name = pa_xstrdup(argv[optind+1]); -        } else if (!strcmp(argv[optind], "set-card-profile")) { +        } else if (pa_streq(argv[optind], "set-card-profile")) {              action = SET_CARD_PROFILE;              if (argc != optind+3) { -                fprintf(stderr, _("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\n"));                  goto quit;              }              card_name = pa_xstrdup(argv[optind+1]);              profile_name = pa_xstrdup(argv[optind+2]); -        } else if (!strcmp(argv[optind], "help")) { +        } else if (pa_streq(argv[optind], "set-sink-port")) { +            action = SET_SINK_PORT; + +            if (argc != optind+3) { +                pa_log(_("You have to specify a sink name/index and a port name\n")); +                goto quit; +            } + +            sink_name = pa_xstrdup(argv[optind+1]); +            port_name = pa_xstrdup(argv[optind+2]); + +        } else if (pa_streq(argv[optind], "set-source-port")) { +            action = SET_SOURCE_PORT; + +            if (argc != optind+3) { +                pa_log(_("You have to specify a source name/index and a port name\n")); +                goto quit; +            } + +            source_name = pa_xstrdup(argv[optind+1]); +            port_name = pa_xstrdup(argv[optind+2]); + +        } else if (pa_streq(argv[optind], "help")) {              help(bn);              ret = 0;              goto quit; @@ -1009,37 +1096,35 @@ int main(int argc, char *argv[]) {      }      if (action == NONE) { -        fprintf(stderr, _("No valid command specified.\n")); +        pa_log(_("No valid command specified.\n"));          goto quit;      }      if (!(m = pa_mainloop_new())) { -        fprintf(stderr, _("pa_mainloop_new() failed.\n")); +        pa_log(_("pa_mainloop_new() failed.\n"));          goto quit;      }      mainloop_api = pa_mainloop_get_api(m); -    r = pa_signal_init(mainloop_api); -    assert(r == 0); +    pa_assert_se(pa_signal_init(mainloop_api) == 0);      pa_signal_new(SIGINT, exit_signal_callback, NULL); -#ifdef SIGPIPE -    signal(SIGPIPE, SIG_IGN); -#endif +    pa_signal_new(SIGTERM, exit_signal_callback, NULL); +    pa_disable_sigpipe(); -    if (!(context = pa_context_new(mainloop_api, client_name))) { -        fprintf(stderr, _("pa_context_new() failed.\n")); +    if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { +        pa_log(_("pa_context_new() failed.\n"));          goto quit;      }      pa_context_set_state_callback(context, context_state_callback, NULL);      if (pa_context_connect(context, server, 0, NULL) < 0) { -        fprintf(stderr, _("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); +        pa_log(_("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context)));          goto quit;      }      if (pa_mainloop_run(m, &ret) < 0) { -        fprintf(stderr, _("pa_mainloop_run() failed.\n")); +        pa_log(_("pa_mainloop_run() failed.\n"));          goto quit;      } @@ -1055,18 +1140,20 @@ quit:          pa_mainloop_free(m);      } -    if (sndfile) -        sf_close(sndfile); -      pa_xfree(server);      pa_xfree(device);      pa_xfree(sample_name);      pa_xfree(sink_name);      pa_xfree(source_name);      pa_xfree(module_args); -    pa_xfree(client_name);      pa_xfree(card_name);      pa_xfree(profile_name); +    if (sndfile) +        sf_close(sndfile); + +    if (proplist) +        pa_proplist_free(proplist); +      return ret;  } diff --git a/src/utils/pasuspender.c b/src/utils/pasuspender.c index b4bccd56..c327ee41 100644 --- a/src/utils/pasuspender.c +++ b/src/utils/pasuspender.c @@ -235,10 +235,7 @@ int main(int argc, char *argv[]) {      setlocale(LC_ALL, "");      bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); -    if (!(bn = strrchr(argv[0], '/'))) -        bn = argv[0]; -    else -        bn++; +    bn = pa_path_get_filename(argv[0]);      while ((c = getopt_long(argc, argv, "s:h", long_options, NULL)) != -1) {          switch (c) {  | 
