summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am266
-rw-r--r--src/daemon/caps.c99
-rw-r--r--src/daemon/caps.h3
-rw-r--r--src/daemon/cmdline.c11
-rw-r--r--src/daemon/cpulimit.c9
-rw-r--r--src/daemon/daemon-conf.c21
-rw-r--r--src/daemon/daemon-conf.h3
-rw-r--r--src/daemon/daemon.conf.in7
-rwxr-xr-xsrc/daemon/default.pa.in15
-rw-r--r--src/daemon/dumpmodules.c2
-rw-r--r--src/daemon/main.c308
-rwxr-xr-xsrc/daemon/start-pulseaudio-x11.in2
-rw-r--r--src/log1555
-rw-r--r--src/map-file21
-rw-r--r--src/modules/alsa/alsa-mixer.c3406
-rw-r--r--src/modules/alsa/alsa-mixer.h292
-rw-r--r--src/modules/alsa/alsa-sink.c678
-rw-r--r--src/modules/alsa/alsa-sink.h3
-rw-r--r--src/modules/alsa/alsa-source.c660
-rw-r--r--src/modules/alsa/alsa-source.h2
-rw-r--r--src/modules/alsa/alsa-util.c1191
-rw-r--r--src/modules/alsa/alsa-util.h122
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-aux.conf62
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-fm.conf62
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-linein.conf61
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic-line.conf63
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic.conf63
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic.conf.common63
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-tvtuner.conf62
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-video.conf61
-rw-r--r--src/modules/alsa/mixer/paths/analog-input.conf54
-rw-r--r--src/modules/alsa/mixer/paths/analog-input.conf.common257
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-headphones.conf71
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf72
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-mono.conf69
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf80
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf.common111
-rw-r--r--src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules26
-rw-r--r--src/modules/alsa/mixer/profile-sets/default.conf144
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf91
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf162
-rw-r--r--src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0150
-rw-r--r--src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x24
-rw-r--r--src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3135
-rw-r--r--src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI4
-rw-r--r--src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD198162
-rw-r--r--src/modules/alsa/mixer/samples/HDA Intel--Conexant CX20551 (Waikiki)42
-rw-r--r--src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A113
-rw-r--r--src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A128
-rw-r--r--src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer27
-rw-r--r--src/modules/alsa/mixer/samples/USB Audio--USB Mixer37
-rw-r--r--src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer5
-rw-r--r--src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888211
-rw-r--r--src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+160
-rw-r--r--src/modules/alsa/module-alsa-card.c290
-rw-r--r--src/modules/alsa/module-alsa-sink.c12
-rw-r--r--src/modules/alsa/module-alsa-source.c14
-rw-r--r--src/modules/bluetooth/bluetooth-util.c437
-rw-r--r--src/modules/bluetooth/bluetooth-util.h58
-rw-r--r--src/modules/bluetooth/ipc.c1
-rw-r--r--src/modules/bluetooth/ipc.h73
-rw-r--r--src/modules/bluetooth/module-bluetooth-device.c1136
-rw-r--r--src/modules/bluetooth/module-bluetooth-discover.c108
-rw-r--r--src/modules/bluetooth/module-bluetooth-proximity.c6
-rw-r--r--src/modules/bluetooth/sbc.c43
-rw-r--r--src/modules/bluetooth/sbc.h14
-rw-r--r--src/modules/hal-util.c2
-rw-r--r--src/modules/jack/module-jack-sink.c504
-rw-r--r--src/modules/jack/module-jack-source.c454
-rw-r--r--src/modules/module-augment-properties.c73
-rw-r--r--src/modules/module-card-restore.c85
-rw-r--r--src/modules/module-combine.c82
-rw-r--r--src/modules/module-console-kit.c2
-rw-r--r--src/modules/module-cork-music-on-phone.c1
-rw-r--r--src/modules/module-default-device-restore.c17
-rw-r--r--src/modules/module-defs.h.m42
-rw-r--r--src/modules/module-detect.c1
-rw-r--r--src/modules/module-device-restore.c283
-rw-r--r--src/modules/module-esound-sink.c56
-rw-r--r--src/modules/module-hal-detect.c47
-rw-r--r--src/modules/module-intended-roles.c428
-rw-r--r--src/modules/module-ladspa-sink.c43
-rw-r--r--src/modules/module-lirc.c37
-rw-r--r--src/modules/module-match.c2
-rw-r--r--src/modules/module-mmkbd-evdev.c41
-rw-r--r--src/modules/module-null-sink.c53
-rw-r--r--src/modules/module-pipe-sink.c17
-rw-r--r--src/modules/module-pipe-source.c16
-rw-r--r--src/modules/module-protocol-stub.c38
-rw-r--r--src/modules/module-remap-sink.c20
-rw-r--r--src/modules/module-rescue-streams.c60
-rw-r--r--src/modules/module-rygel-media-server.c847
-rw-r--r--src/modules/module-sine-source.c34
-rw-r--r--src/modules/module-solaris.c132
-rw-r--r--src/modules/module-stream-restore.c516
-rw-r--r--src/modules/module-suspend-on-idle.c41
-rw-r--r--src/modules/module-tunnel.c306
-rw-r--r--src/modules/module-udev-detect.c457
-rw-r--r--src/modules/module-volume-restore.c3
-rw-r--r--src/modules/module-waveout.c4
-rw-r--r--src/modules/module-zeroconf-discover.c2
-rw-r--r--src/modules/module-zeroconf-publish.c111
-rw-r--r--src/modules/oss/module-oss.c39
-rw-r--r--src/modules/raop/module-raop-discover.c397
-rw-r--r--src/modules/raop/module-raop-sink.c697
-rw-r--r--src/modules/raop/raop_client.c2
-rw-r--r--src/modules/reserve-monitor.c269
-rw-r--r--src/modules/reserve-monitor.h72
-rw-r--r--src/modules/reserve-wrap.c200
-rw-r--r--src/modules/reserve-wrap.h9
-rw-r--r--src/modules/reserve.c24
-rw-r--r--src/modules/reserve.h12
-rw-r--r--src/modules/rtp/module-rtp-recv.c107
-rw-r--r--src/modules/rtp/module-rtp-send.c20
-rw-r--r--src/modules/rtp/rtp.c21
-rw-r--r--src/modules/rtp/rtp.h2
-rw-r--r--src/modules/rtp/rtsp_client.c7
-rw-r--r--src/modules/udev-util.c67
-rw-r--r--src/modules/udev-util.h3
-rw-r--r--src/modules/x11/module-x11-bell.c190
-rw-r--r--src/modules/x11/module-x11-cork-request.c189
-rw-r--r--src/modules/x11/module-x11-publish.c245
-rw-r--r--src/modules/x11/module-x11-xsmp.c254
-rw-r--r--src/pulse/channelmap.c166
-rw-r--r--src/pulse/channelmap.h68
-rw-r--r--src/pulse/client-conf-x11.c15
-rw-r--r--src/pulse/client-conf.c31
-rw-r--r--src/pulse/client-conf.h2
-rw-r--r--src/pulse/context.c185
-rw-r--r--src/pulse/context.h15
-rw-r--r--src/pulse/def.h40
-rw-r--r--src/pulse/fork-detect.c2
-rw-r--r--src/pulse/internal.h27
-rw-r--r--src/pulse/introspect.c214
-rw-r--r--src/pulse/introspect.h56
-rw-r--r--src/pulse/mainloop-api.h1
-rw-r--r--src/pulse/mainloop-signal.c5
-rw-r--r--src/pulse/mainloop.c88
-rw-r--r--src/pulse/mainloop.h2
-rw-r--r--src/pulse/proplist.c15
-rw-r--r--src/pulse/proplist.h19
-rw-r--r--src/pulse/pulseaudio.h2
-rw-r--r--src/pulse/rtclock.c35
-rw-r--r--src/pulse/rtclock.h41
-rw-r--r--src/pulse/sample.c41
-rw-r--r--src/pulse/sample.h24
-rw-r--r--src/pulse/simple.c71
-rw-r--r--src/pulse/stream.c323
-rw-r--r--src/pulse/stream.h158
-rw-r--r--src/pulse/subscribe.h2
-rw-r--r--src/pulse/thread-mainloop.c4
-rw-r--r--src/pulse/timeval.h11
-rw-r--r--src/pulse/util.c3
-rw-r--r--src/pulse/util.h2
-rw-r--r--src/pulse/version.h.in7
-rw-r--r--src/pulse/volume.c267
-rw-r--r--src/pulse/volume.h59
-rw-r--r--src/pulse/xmalloc.h13
-rw-r--r--src/pulsecore/asyncq.c18
-rw-r--r--src/pulsecore/aupdate.c129
-rw-r--r--src/pulsecore/aupdate.h98
-rw-r--r--src/pulsecore/authkey.c2
-rw-r--r--src/pulsecore/avahi-wrap.c9
-rw-r--r--src/pulsecore/card.c60
-rw-r--r--src/pulsecore/card.h11
-rw-r--r--src/pulsecore/cli-command.c116
-rw-r--r--src/pulsecore/cli-text.c114
-rw-r--r--src/pulsecore/cli.c13
-rw-r--r--src/pulsecore/conf-parser.c50
-rw-r--r--src/pulsecore/core-rtclock.c199
-rw-r--r--src/pulsecore/core-rtclock.h51
-rw-r--r--src/pulsecore/core-scache.c49
-rw-r--r--src/pulsecore/core-util.c454
-rw-r--r--src/pulsecore/core-util.h19
-rw-r--r--src/pulsecore/core.c28
-rw-r--r--src/pulsecore/core.h14
-rw-r--r--src/pulsecore/database-gdbm.c249
-rw-r--r--src/pulsecore/database-tdb.c227
-rw-r--r--src/pulsecore/database.h61
-rw-r--r--src/pulsecore/dbus-shared.c107
-rw-r--r--src/pulsecore/dbus-shared.h42
-rw-r--r--src/pulsecore/dbus-util.c424
-rw-r--r--src/pulsecore/dbus-util.h63
-rw-r--r--src/pulsecore/endianmacros.h8
-rw-r--r--src/pulsecore/hashmap.c42
-rw-r--r--src/pulsecore/hashmap.h17
-rw-r--r--src/pulsecore/hook-list.c6
-rw-r--r--src/pulsecore/hook-list.h2
-rw-r--r--src/pulsecore/idxset.c14
-rw-r--r--src/pulsecore/idxset.h7
-rw-r--r--src/pulsecore/ioline.c48
-rw-r--r--src/pulsecore/ioline.h11
-rw-r--r--src/pulsecore/ipacl.h4
-rw-r--r--src/pulsecore/llist.h6
-rw-r--r--src/pulsecore/log.c48
-rw-r--r--src/pulsecore/log.h4
-rw-r--r--src/pulsecore/ltdl-helper.h1
-rw-r--r--src/pulsecore/macro.h51
-rw-r--r--src/pulsecore/memblock.c88
-rw-r--r--src/pulsecore/memblockq.c82
-rw-r--r--src/pulsecore/memblockq.h7
-rw-r--r--src/pulsecore/memtrap.c230
-rw-r--r--src/pulsecore/memtrap.h51
-rw-r--r--src/pulsecore/mime-type.c182
-rw-r--r--src/pulsecore/mime-type.h37
-rw-r--r--src/pulsecore/modargs.c73
-rw-r--r--src/pulsecore/modargs.h2
-rw-r--r--src/pulsecore/modinfo.c5
-rw-r--r--src/pulsecore/modinfo.h1
-rw-r--r--src/pulsecore/module.c9
-rw-r--r--src/pulsecore/module.h4
-rw-r--r--src/pulsecore/mutex-posix.c2
-rw-r--r--src/pulsecore/mutex.h4
-rw-r--r--src/pulsecore/native-common.h10
-rw-r--r--src/pulsecore/object.c4
-rw-r--r--src/pulsecore/parseaddr.c26
-rw-r--r--src/pulsecore/parseaddr.h8
-rw-r--r--src/pulsecore/pdispatch.c32
-rw-r--r--src/pulsecore/pdispatch.h2
-rw-r--r--src/pulsecore/proplist-util.c29
-rw-r--r--src/pulsecore/protocol-esound.c34
-rw-r--r--src/pulsecore/protocol-http.c681
-rw-r--r--src/pulsecore/protocol-http.h6
-rw-r--r--src/pulsecore/protocol-native.c794
-rw-r--r--src/pulsecore/protocol-simple.c2
-rw-r--r--src/pulsecore/pstream.c8
-rw-r--r--src/pulsecore/ratelimit.c7
-rw-r--r--src/pulsecore/refcnt.h44
-rw-r--r--src/pulsecore/rtkit.c189
-rw-r--r--src/pulsecore/rtkit.h62
-rw-r--r--src/pulsecore/rtpoll.c222
-rw-r--r--src/pulsecore/rtpoll.h3
-rw-r--r--src/pulsecore/sample-util.c12
-rw-r--r--src/pulsecore/sample-util.h59
-rw-r--r--src/pulsecore/sconv-s16le.c10
-rw-r--r--src/pulsecore/sconv.c2
-rw-r--r--src/pulsecore/semaphore-posix.c22
-rw-r--r--src/pulsecore/semaphore.h15
-rw-r--r--src/pulsecore/shm.c57
-rw-r--r--src/pulsecore/sink-input.c216
-rw-r--r--src/pulsecore/sink-input.h33
-rw-r--r--src/pulsecore/sink.c729
-rw-r--r--src/pulsecore/sink.h76
-rw-r--r--src/pulsecore/sndfile-util.c462
-rw-r--r--src/pulsecore/sndfile-util.h52
-rw-r--r--src/pulsecore/socket-client.c22
-rw-r--r--src/pulsecore/socket-client.h2
-rw-r--r--src/pulsecore/socket-server.c23
-rw-r--r--src/pulsecore/sound-file-stream.c51
-rw-r--r--src/pulsecore/sound-file.c102
-rw-r--r--src/pulsecore/sound-file.h2
-rw-r--r--src/pulsecore/source-output.c114
-rw-r--r--src/pulsecore/source-output.h14
-rw-r--r--src/pulsecore/source.c376
-rw-r--r--src/pulsecore/source.h45
-rw-r--r--src/pulsecore/strbuf.c7
-rw-r--r--src/pulsecore/strbuf.h1
-rw-r--r--src/pulsecore/strlist.c12
-rw-r--r--src/pulsecore/strlist.h6
-rw-r--r--src/pulsecore/time-smoother.c79
-rw-r--r--src/pulsecore/time-smoother.h14
-rw-r--r--src/t.csv2551
-rw-r--r--src/tests/alsa-time-test.c2
-rw-r--r--src/tests/gtk-test.c41
-rw-r--r--src/tests/interpol-test.c42
-rw-r--r--src/tests/ipacl-test.c6
-rw-r--r--src/tests/mainloop-test.c8
-rw-r--r--src/tests/memblockq-test.c18
-rw-r--r--src/tests/mix-test.c97
-rw-r--r--src/tests/proplist-test.c14
-rw-r--r--src/tests/resampler-test.c75
-rw-r--r--src/tests/rtpoll-test.c6
-rw-r--r--src/tests/rtstutter.c2
-rw-r--r--src/tests/sigbus-test.c70
-rw-r--r--src/tests/smoother-test.c8
-rw-r--r--src/tests/thread-mainloop-test.c8
-rw-r--r--src/tests/voltest.c3
-rw-r--r--src/tests/volume-ui.py29
-rw-r--r--src/utils/pabrowse.c7
-rw-r--r--src/utils/pacat.c738
-rw-r--r--src/utils/pacmd.c26
-rw-r--r--src/utils/pactl.c343
-rw-r--r--src/utils/pasuspender.c5
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, "&gt;");
+ else if (*p == '<')
+ pa_strbuf_puts(sb, "&lt;");
+ else
+ pa_strbuf_puts(sb, "&amp;");
+ }
+ }
+
+ 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) {